Как пометить группы в postgresql, когда принадлежность к группе зависит от предыдущей строки?

Я хочу, чтобы в запросе все значения Null были заполнены последним известным значением. Когда он находится в таблице, а не в запросе, это просто:

Если я определю и заполню свою таблицу следующим образом:

CREATE TABLE test_fill_null (
    date INTEGER,
    value INTEGER
);

INSERT INTO test_fill_null VALUES 
    (1,2),
    (2, NULL), 
    (3, 45), 
    (4,NULL), 
    (5, null);

SELECT * FROM test_fill_null ;
 date | value 
------+-------
    1 |     2
    2 |      
    3 |    45
    4 |      
    5 |      

Тогда мне просто нужно вот так заполнить:

UPDATE test_fill_null t1
SET value = (
    SELECT t2.value 
    FROM test_fill_null t2 
    WHERE t2.date <= t1.date AND value IS NOT NULL 
    ORDER BY t2.date DESC 
    LIMIT 1
);

SELECT * FROM test_fill_null;
 date | value 
------+-------
    1 |     2
    2 |     2
    3 |    45
    4 |    45
    5 |    45

Но сейчас у меня такой запрос:

WITH
    pre_table AS(
        SELECT 
            id1,
            id2,
            tms,
            CASE 
                WHEN tms - lag(tms) over w < interval '5 minutes' THEN NULL
                ELSE id2
            END as group_id
        FROM
            table0 
        window w as (partition by id1 order by tms)
    )

Если для group_id установлено значение id2, когда предыдущая точка удалена более чем на 5 минут, в противном случае - null. Поступая таким образом, я хочу получить группу точек, которые следуют друг за другом менее чем на 5 минут, и промежутки между группами более 5 минут.

Тогда я не знаю, что делать дальше. Я пытался:

    SELECT distinct on (id1, id2)
        t0.id1,
        t0.id2,
        t0.tms,
        t1.group_id
    FROM
        pre_table t0
        LEFT JOIN (
            select
                id1,
                tms,
                group_id
            from pre_table t2
            where t2.group_id is not null
            order by tms desc
        ) t1
        ON 
            t1.tms <= t0.tms AND
            t1.id1 = t0.id1  
    WHERE 
        t0.id1 IS NOT NULL
    ORDER BY
        id1,
        id2,
        t1.tms DESC

Но в конечном итоге у меня есть группа с двумя последовательными очками, которые отстают более чем от 5 минут. В этом случае их должно быть две разные группы.


person Borbag    schedule 16.12.2015    source источник
comment
Значит, с одним значением NULL каждые 5 минут одна и та же группа может сохраняться бесконечно, верно? Как всегда: пожалуйста, укажите свою версию Postgres. И хороший тестовый пример в самом начале оказывается неприменим к вашей реальной проблеме. Было бы гораздо разумнее предоставить тестовый пример для вашей реальной проблемы.   -  person Erwin Brandstetter    schedule 16.12.2015
comment
Да, точно. у меня версия 9.3.10. Я не могу предоставить данные в том виде, в каком они есть. Если хотите, я сделаю несколько поддельных данных с той же моделью.   -  person Borbag    schedule 16.12.2015
comment
Предполагаемый базовый порядок строк также не определен. Вы заказываете по id1, по tms или по id1, id2, tms?   -  person Erwin Brandstetter    schedule 16.12.2015
comment
@Erwin Brandstetter: Больше всего мне помогло то, что я мог использовать счетчик по окну, и он будет увеличиваться для каждого ненулевого значения. Должен ли я отредактировать заголовок моего вопроса, чтобы отразить это?   -  person Borbag    schedule 18.12.2015
comment
Если вы можете придумать заголовок, который более точно отражает суть вашего вопроса, вперед!   -  person Erwin Brandstetter    schedule 19.12.2015


Ответы (2)


«выбрать в выбранном» чаще называют «подзапросом» или «подзапросом». В вашем конкретном случае это коррелированный подзапрос. LATERAL объединения (новое в postgres 9.3) могут в значительной степени заменить коррелированные подзапросы более гибкими решениями:

Я не думаю, что тебе здесь тоже нужно.

Однако для вашего первого случая этот запрос, вероятно, будет быстрее и проще:

SELECT date, max(value) OVER (PARTITION BY grp) AS value
FROM  (
   SELECT *, count(value) OVER (ORDER BY date) AS grp
   FROM   test_fill_null
   ) sub;

count() считает только ненулевые значения, поэтому grp увеличивается с каждым ненулевым value, тем самым формируя группы по желанию. Просто выбрать один ненулевой value на grp во внешнем SELECT.


Для вашего второго случая я предполагаю, что начальный порядок строк определяется (id1, id2, tms), как указано в одном из ваших запросов.

SELECT id1, id2, tms
     , count(step) OVER (ORDER BY id1, id2, tms) AS group_id
FROM  (
   SELECT *, CASE WHEN lag(tms, 1, '-infinity') OVER (PARTITION BY id1 ORDER BY id2, tms)
                       < tms - interval '5 min'
                  THEN true END AS step
   FROM   table0
   ) sub
ORDER  BY id1, id2, tms;

Адаптируйтесь к вашему реальному заказу. Один из них может покрыть это:

PARTITION BY id1 ORDER BY id2  -- ignore tms
PARTITION BY id1 ORDER BY tms  -- ignore id2

SQL Fiddle с расширенным примером.

Связанный:

person Erwin Brandstetter    schedule 16.12.2015
comment
Я попробовал SQL Fiddle, и мне пришлось исправить данные, чтобы они отражали мои. Сделав это, я увидел, что id1 - плохое имя, и ввожу вас в заблуждение. id1 можно рассматривать как внешний ключ. Я исправил следующее: INSERT INTO table0 VALUES (1,2, '2015-12-16 16: 09: 00 + 01'), (1,3, '2015-12-16 16: 11: 00 + 01') , (2,4, '2015-12-16 16: 10: 00 + 01'), (2,5, '2015-12-16 16: 12: 00 + 01'), (1,6, '2015 -12-16 16: 13: 01 + 01 '); После исправления ваше решение, похоже, не работает, но я буду работать над этим. Если я полностью пойму ваш ответ, я смогу его исправить. - person Borbag; 18.12.2015
comment
На самом деле для моего второго случая вы дали мне другой способ получить мой «запрос контекста», а не «запрос маркировки». (не так уж и много, вы просто передаете tms на другую сторону неравенства). С помощью вашего ответа по первому делу я завершил его, так что он подойдет мне. [sql Fiddle) (sqlfiddle.com/#!15/af4e64/9) Что ты думаешь об этом? - person Borbag; 18.12.2015
comment
Я прошел с 6 минут с моим запросом до 5 секунд с вашим, спасибо! Просто отредактируйте свой ответ второй частью запроса «В моей скрипке», и я проверю ваш ответ. - person Borbag; 18.12.2015
comment
Его можно даже упростить следующим образом: sql fiddle - person Borbag; 18.12.2015
comment
@RemiDelassus: Я добавил обновленный запрос, немного упростил. Подзапрос обычно выполняется быстрее, чем CTE в Postgres, и нам здесь не нужен CTE. - person Erwin Brandstetter; 18.12.2015

Редактируя свой вопрос, я нашел решение. Однако он довольно низкий, намного ниже, чем в моем примере в таблице. Есть предложения по его улучшению?

    SELECT
        t2.id1,
        t2.id2,
        t2.tms,
        (
            SELECT t1.group_id 
            FROM pre_table t1 
            WHERE 
                t1.tms <= t2.tms 
                AND t1.group_id IS NOT NULL 
                AND t2.id1 = t2.id1
            ORDER BY t1.tms DESC 
            LIMIT 1
        ) as group_id
    FROM
        pre_table t2
    ORDER BY
        t2.id1
        t2.id2
        t2.tms

Итак, как я уже сказал, выбор в пределах выбора

person Borbag    schedule 16.12.2015