2017-02-14 1 views
3

У меня есть logins таблицу, которая выглядит следующим образом:Создание запроса разделов, сообщая первый NOT NULL появление внутри раздела перед текущей строкой (если таковые имеются)

person_id | login_at | points_won 
-----------+----------------+---------------------- 
1   | 2017-02-02 |  
1   | 2017-02-01 |  
2   | 2017-02-01 | 2 
1   | 2017-01-29 | 2 
2   | 2017-01-28 |  
2   | 2017-01-25 | 1 
3   | 2017-01-22 |  
3   | 2017-01-21 |  
1   | 2017-01-10 | 3 
1   | 2017-01-01 | 1 

Я хочу, чтобы создать результирующий набор, содержащий points_won столбец, который должен работать примерно так: для каждого раздела строки, основанного на person_id, укажите раздел на login_at desc, затем сообщите о первом вхождении (но не в null) last_points_won упорядоченных строк в разделе (если есть).

Это должно привести к чему-то вроде этого:

person_id | login_at | points_won  | last_points_won 
-----------+----------------+----------------------+---------------------- 
1   | 2017-02-02 |      | 2 
1   | 2017-02-01 |      | 2 
2   | 2017-02-01 |  2    | 2 
1   | 2017-01-29 |  2    | 2 
2   | 2017-01-28 |      | 1 
2   | 2017-01-25 |  1    | 1 
3   | 2017-01-22 |      |  
3   | 2017-01-21 |      |  
1   | 2017-01-10 |  3    | 3 
1   | 2017-01-01 |  1    | 1 

Или простыми словами:

для каждой строки, дай мне либо очки, выигранные во время этого входа или, если ничего, дайте Я получил очки, выигранные у лиц, прошедших предыдущий вход, где он фактически сделал очков.

+0

Не уверен, что я понимаю ваш вопрос? –

+0

'select l1. *, (Выберите max (made_confirmation_at) из логинов l2, где l1.person_id = l2.person_id), как last_confirmation_at, из логинов l1' – jarlh

+0

Хм мой пример плох, Can not use MAX() Я исправлю его –

ответ

1

Это может быть достигнуто в одном окне тоже с IGNORE NULLS option of the last_value() window function. Но это еще не поддерживается в PostgreSQL. Одним из вариантов является FILTER (WHERE ...) clause, но это будет работать только тогда, когда функция окна является агрегированной функцией (что неверно для last_value(), но something similar could be created easily с CREATE AGGREGATE). Чтобы решить эту проблему только с встроенными агрегатами, вы можете использовать array_agg() тоже:

SELECT (tbl).*, 
     all_points_won[array_upper(all_points_won, 1)] last_points_won 
FROM (SELECT tbl, 
       array_agg(points_won) 
       FILTER (WHERE points_won IS NOT NULL) 
       OVER (PARTITION BY person_id ORDER BY login_at) all_points_won 
     FROM tbl) s 

Примечание: суб-запроса не требуется, если создать специальный last_agg() агрегат, как:

CREATE FUNCTION last_val(anyelement, anyelement) 
    RETURNS anyelement 
    LANGUAGE SQL 
    IMMUTABLE 
    CALLED ON NULL INPUT 
    AS 'SELECT $2'; 

CREATE AGGREGATE last_agg(anyelement) (
    SFUNC = last_val, 
    STYPE = anyelement 
); 

SELECT tbl.*, 
     last_agg(points_won) 
     FILTER (WHERE points_won IS NOT NULL) 
     OVER (PARTITION BY person_id ORDER BY login_at) last_points_won 
FROM tbl; 

Rextester sample

Edit: раз опция IGNORE NULLS будет поддерживаться на PostgreSQL, вы можете использовать следующий запрос (который должен работать в Amazon Redshift тоже):

SELECT tbl.*, 
     last_value(points_won IGNORE NULLS) 
     OVER (PARTITION BY person_id ORDER BY login_at ROW BETWEEN UNBOUNCED PRECEDING AND CURRENT ROW) last_points_won 
FROM tbl; 
+0

Wohooooooo отлично! –

+0

Теперь возникает следующий вопрос. Там, где это нормально работает с моей установкой 9.3, это не работает для другой базы данных, которую я имею в Amazon Redshift (по существу, постгреск 8 под капотом). Похоже, что предложение FILTER не поддерживается. Любые другие идеи? :-) –

+0

@NielsKristian Amazon Redshift основан на действительно древней версии PostgreSQL, тем самым очень мало поддерживая то, что может достичь PostgreSQL. (И также эта версия сильно изменена с помощью настраиваемых функций и отключенных функций). Обычно это считается совершенно другой базой данных. – pozs

1
select  * 
      ,min(points_won) over 
      (
       partition by person_id,group_id 
      ) as last_points_won 

from  (select  * 
         ,count(points_won) over 
         (
          partition by person_id 
          order by  login_at 
         ) as group_id 

      from  mytable 
      ) t 

+-----------+------------+------------+----------+-----------------+ 
| person_id | login_at | points_won | group_id | last_points_won | 
+-----------+------------+------------+----------+-----------------+ 
| 1   | 2017-01-01 | 1   | 1  | 1    | 
+-----------+------------+------------+----------+-----------------+ 
| 1   | 2017-01-10 | 3   | 2  | 3    | 
+-----------+------------+------------+----------+-----------------+ 
| 1   | 2017-01-29 | 2   | 3  | 2    | 
+-----------+------------+------------+----------+-----------------+ 
| 1   | 2017-02-01 | (null)  | 3  | 2    | 
+-----------+------------+------------+----------+-----------------+ 
| 1   | 2017-02-02 | (null)  | 3  | 2    | 
+-----------+------------+------------+----------+-----------------+ 
| 2   | 2017-01-25 | 1   | 1  | 1    | 
+-----------+------------+------------+----------+-----------------+ 
| 2   | 2017-01-28 | (null)  | 1  | 1    | 
+-----------+------------+------------+----------+-----------------+ 
| 2   | 2017-02-01 | 2   | 2  | 2    | 
+-----------+------------+------------+----------+-----------------+ 
| 3   | 2017-01-21 | (null)  | 0  | (null)   | 
+-----------+------------+------------+----------+-----------------+ 
| 3   | 2017-01-22 | (null)  | 0  | (null)   | 
+-----------+------------+------------+----------+-----------------+ 
+0

Извините, я просто обновлял свой пример, это было плохо, и я не могу использовать MAX –

+0

См. Обновленный ответ. Я оставил его group_id, так что было бы легче понять –