Может ли оконная функция LAG ссылаться на столбец, значение которого вычисляется?

Мне нужно вычислить значение некоторого столбца X на основе некоторых других столбцов текущей записи и значения X для предыдущей записи (с использованием некоторого раздела и порядка). В основном мне нужно реализовать запрос в форме

SELECT <some fields>, 
  <some expression using LAG(X) OVER(PARTITION BY ... ORDER BY ...) AS X
FROM <table>

Это невозможно, потому что в оконной функции можно использовать только существующие столбцы, поэтому я ищу способ преодолеть это.

Вот пример. У меня есть таблица с событиями. Каждое событие имеет type и time_stamp.

create table event (id serial, type integer, time_stamp integer);

Я не хочу находить "повторяющиеся" события (пропустить их). Под дублированием я подразумеваю следующее. Упорядочим все события для данного type по time_stamp по возрастанию. потом

  1. первое событие не повторяется
  2. все события, которые не повторяются и находятся в пределах некоторого периода времени после него (то есть их time_stamp не больше, чем time_stamp предыдущего не дублированного плюс некоторая константа TIMEFRAME), являются дубликатами
  3. следующее событие, которое на time_stamp больше, чем предыдущее, не повторяющееся более чем на TIMEFRAME, не является дубликатом
  4. и так далее

По этим данным

insert into event (type, time_stamp) 
 values 
  (1, 1), (1, 2), (2, 2), (1,3), (1, 10), (2,10), 
  (1,15), (1, 21), (2,13), 
  (1, 40);

и TIMEFRAME=10 результат должен быть

time_stamp | type | duplicate
-----------------------------
        1  |    1 | false
        2  |    1 | true     
        3  |    1 | true 
       10  |    1 | true 
       15  |    1 | false 
       21  |    1 | true
       40  |    1 | false
        2  |    2 | false
       10  |    2 | true
       13  |    2 | false

Я мог бы вычислить значение поля duplicate на основе текущих time_stamp и time_stamp предыдущего неповторяющегося события следующим образом:

WITH evt AS (
  SELECT 
    time_stamp, 
    CASE WHEN 
      time_stamp - LAG(current_non_dupl_time_stamp) OVER w >= TIMEFRAME
    THEN 
      time_stamp
    ELSE
      LAG(current_non_dupl_time_stamp) OVER w
    END AS current_non_dupl_time_stamp
  FROM event
  WINDOW w AS (PARTITION BY type ORDER BY time_stamp ASC)
)
SELECT time_stamp, time_stamp != current_non_dupl_time_stamp AS duplicate

Но это не работает, потому что на вычисляемое поле нельзя ссылаться в LAG:

ERROR:  column "current_non_dupl_time_stamp" does not exist.

Итак, вопрос: можно ли переписать этот запрос, чтобы добиться нужного мне эффекта?


person Roman Konoval    schedule 17.12.2015    source источник
comment
Я не мог понять временные рамки. особенно эта часть: the next event which time_stamp if greater than previous non duplicate by more than TIMEFRAME is not duplicate. таймфрейм - это константа, поле или расчет?   -  person Jorge Campos    schedule 17.12.2015
comment
TIMEFRAME - некоторая константа. Обоснование заключается в том, что я хочу пропустить событие, если оно наступит в пределах заданного периода времени после предыдущего события, которое не было пропущено.   -  person Roman Konoval    schedule 17.12.2015
comment
Ваш желаемый результат содержит отметку времени 40, а ваш пример набора данных - нет? Не могли бы вы уточнить?   -  person Andomar    schedule 17.12.2015
comment
Вы правы, это была ошибка.   -  person Roman Konoval    schedule 17.12.2015


Ответы (3)


Альтернативой рекурсивному подходу является настраиваемый агрегат. Когда вы овладеете техникой написания собственных агрегатов, создание переходных и конечных функций станет простым и логичным.

Функция перехода между состояниями:

create or replace function is_duplicate(st int[], time_stamp int, timeframe int)
returns int[] language plpgsql as $$
begin
    if st is null or st[1] + timeframe <= time_stamp
    then 
        st[1] := time_stamp;
    end if;
    st[2] := time_stamp;
    return st;
end $$;

Конечная функция:

create or replace function is_duplicate_final(st int[])
returns boolean language sql as $$
    select st[1] <> st[2];
$$;

Совокупный:

create aggregate is_duplicate_agg(time_stamp int, timeframe int)
(
    sfunc = is_duplicate,
    stype = int[],
    finalfunc = is_duplicate_final
);

Запрос:

select *, is_duplicate_agg(time_stamp, 10) over w
from event
window w as (partition by type order by time_stamp asc)
order by type, time_stamp;

 id | type | time_stamp | is_duplicate_agg 
----+------+------------+------------------
  1 |    1 |          1 | f
  2 |    1 |          2 | t
  4 |    1 |          3 | t
  5 |    1 |         10 | t
  7 |    1 |         15 | f
  8 |    1 |         21 | t
 10 |    1 |         40 | f
  3 |    2 |          2 | f
  6 |    2 |         10 | t
  9 |    2 |         13 | f
(10 rows)   

Прочтите документацию: 37.10. Пользовательские агрегаты и СОЗДАТЬ АГРЕГАТ.

person klin    schedule 09.06.2018

Наивная рекурсивная вязальщица цепочки:


        -- temp view to avoid nested CTE
CREATE TEMP VIEW drag AS
        SELECT e.type,e.time_stamp
        , ROW_NUMBER() OVER www as rn                   -- number the records
        , FIRST_VALUE(e.time_stamp) OVER www as fst     -- the "group leader"
        , EXISTS (SELECT * FROM event x
                WHERE x.type = e.type
                AND x.time_stamp < e.time_stamp) AS is_dup
        FROM event e
        WINDOW www AS (PARTITION BY type ORDER BY time_stamp)
        ;

WITH RECURSIVE ttt AS (
        SELECT d0.*
        FROM drag d0 WHERE d0.is_dup = False -- only the "group leaders"
    UNION ALL
        SELECT d1.type, d1.time_stamp, d1.rn
          , CASE WHEN d1.time_stamp - ttt.fst > 20 THEN d1.time_stamp
                 ELSE ttt.fst END AS fst   -- new "group leader"
          , CASE WHEN d1.time_stamp - ttt.fst > 20 THEN False
                 ELSE True END AS is_dup
        FROM drag d1
        JOIN ttt ON d1.type = ttt.type AND d1.rn = ttt.rn+1
        )
SELECT * FROM ttt
ORDER BY type, time_stamp
        ;

Результаты:


CREATE TABLE
INSERT 0 10
CREATE VIEW
 type | time_stamp | rn | fst | is_dup 
------+------------+----+-----+--------
    1 |          1 |  1 |   1 | f
    1 |          2 |  2 |   1 | t
    1 |          3 |  3 |   1 | t
    1 |         10 |  4 |   1 | t
    1 |         15 |  5 |   1 | t
    1 |         21 |  6 |   1 | t
    1 |         40 |  7 |  40 | f
    2 |          2 |  1 |   2 | f
    2 |         10 |  2 |   2 | t
    2 |         13 |  3 |   2 | t
(10 rows)
person wildplasser    schedule 07.01.2017

Это больше похоже на рекурсивную проблему, чем на оконную функцию. Следующий запрос дал желаемые результаты:

WITH RECURSIVE base(type, time_stamp) AS (

  -- 3. base of recursive query
  SELECT x.type, x.time_stamp, y.next_time_stamp
    FROM 
         -- 1. start with the initial records of each type   
         ( SELECT type, min(time_stamp) AS time_stamp
             FROM event
             GROUP BY type
         ) x
         LEFT JOIN LATERAL
         -- 2. for each of the initial records, find the next TIMEFRAME (10) in the future
         ( SELECT MIN(time_stamp) next_time_stamp
             FROM event
             WHERE type = x.type
               AND time_stamp > (x.time_stamp + 10)
         ) y ON true

  UNION ALL

  -- 4. recursive join, same logic as base
  SELECT e.type, e.time_stamp, z.next_time_stamp
    FROM event e
    JOIN base b ON (e.type = b.type AND e.time_stamp = b.next_time_stamp)
    LEFT JOIN LATERAL
    ( SELECT MIN(time_stamp) next_time_stamp
       FROM event
       WHERE type = e.type
         AND time_stamp > (e.time_stamp + 10)
    ) z ON true

)

-- The actual query:

-- 5a. All records from base are not duplicates
SELECT time_stamp, type, false
  FROM base

UNION

-- 5b. All records from event that are not in base are duplicates
SELECT time_stamp, type, true
  FROM event
  WHERE (type, time_stamp) NOT IN (SELECT type, time_stamp FROM base) 

ORDER BY type, time_stamp

С этим есть много нюансов. Он не предполагает дублирования time_stamp для данного type. На самом деле объединения должны основываться на уникальном идентификаторе, а не на type и time_stamp. Я так много не тестировал, но, по крайней мере, это может предложить подход.

Я впервые пытаюсь присоединиться к LATERAL . Так что может быть способ упростить это мо. На самом деле я хотел сделать рекурсивный CTE с рекурсивной частью, использующей MIN(time_stamp) на основе time_stamp > (x.time_stamp + 10), но агрегатные функции не разрешены в CTE таким образом. Но похоже, что в CTE можно использовать боковое соединение.

person Glenn    schedule 07.01.2017