Вычислить интервал между изменениями логического столбца - PullRequest
1 голос
/ 01 февраля 2020

У меня есть таблица с измерениями погоды, вот ее упрощенная версия:

"station_id","measured_at","rainy"
-------------------------------------------------------------------------
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:35:35.27+00",FALSE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:36:33.976+00",FALSE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:37:33.864+00",FALSE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:38:34.767+00",TRUE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:39:36.076+00",TRUE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:40:29.776+00",FALSE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:41:35.579+00",FALSE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:42:34.274+00",TRUE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:43:23.842+00",TRUE
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485","2020-01-31 18:44:35.08+00",FALSE

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

SELECT 
    prev.station_id,
    prev.rainy,
    prev.measured_at AS started_at,
    COALESCE(LEAD(prev.measured_at) OVER (ORDER BY prev.measured_at ASC), NOW()) AS ended_at,
    (COALESCE(LEAD(prev.measured_at) OVER (ORDER BY prev.measured_at ASC), NOW()) - prev.measured_at) AS diff
FROM (
    SELECT
        m.station_id,
        m.measured_at,
        m.rainy,
        COALESCE(LEAD(m.rainy) OVER (ORDER BY m.measured_at ASC), m.rainy) AS prev_rainy
    FROM
        z_measurements m
    WHERE m.measured_at >= '2020-01-30T00:00:00.000Z'
    ORDER BY m.measured_at ASC
) prev
WHERE prev.rainy IS DISTINCT FROM prev.prev_rainy
ORDER BY prev.measured_at ASC;

этот запрос приводит к:

"station_id","rainy","started_at","ended_at","diff"
---------------------------------------------------
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485",FALSE,"2020-01-31 18:37:33.864","2020-01-31 18:39:36.076+00","00:02:02.212"
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485",TRUE,"2020-01-31 18:39:36.076","2020-01-31 18:41:35.579+00","00:01:59.503"
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485",FALSE,"2020-01-31 18:41:35.579","2020-01-31 18:43:23.842+00","00:01:48.263"
"b6b53561-dab5-4b9a-8d28-a7de1e4d1485",TRUE,"2020-01-31 18:43:23.842","2020-01-31 21:18:04.89333+00","02:34:41.05133"

, но если первая полоса этого результата rainy = False, то start_date должно быть таким же, как в запросе 2020-01-30T00:00:00.000Z (я предполагаю, что если на первом результате было солнечно, то все время было солнечно между началом моего диапазона и первым записанным измерением), а также отсутствует последнее строка, потому что последнее измеренное состояние солнечно. Так что started_at для этой последней строки должно быть 2020-01-31 18:44:35.08+00, а end_at должно быть NOW().

Может кто-нибудь помочь мне?

Я использую postgresql 12.1.

Ответы [ 2 ]

1 голос
/ 01 февраля 2020
SELECT t.station_id, t.rainy, t.started_at, t.ended_at, t.ended_at - t.started_at AS diff
FROM (
    SELECT
        prev.station_id,
        prev.rainy,
        CASE
            WHEN LAG(prev.measured_at) OVER measured_at_by_station_id IS NULL THEN '2020-01-30T00:00:00.000Z'
            ELSE prev.measured_at
        END AS started_at,
        LEAD(prev.measured_at, 1, NOW()) OVER measured_at_by_station_id AS ended_at
    FROM (
        SELECT
            m.station_id,
            m.measured_at,
            m.rainy,
            LAG(m.rainy, 1, NOT(m.rainy)) OVER (PARTITION BY m.station_id ORDER BY m.measured_at ASC) AS prev_rainy
        FROM z_measurements m
        WHERE m.measured_at >= '2020-01-30T00:00:00.000Z'
        ORDER BY m.station_id ASC, m.measured_at ASC
    ) prev
    WHERE prev.rainy IS DISTINCT FROM prev.prev_rainy
    WINDOW measured_at_by_station_id AS (PARTITION BY prev.station_id ORDER BY prev.measured_at ASC)
) t
ORDER BY t.station_id ASC, t.started_at ASC
0 голосов
/ 01 февраля 2020

Это проблема пробелов и островков, когда вы хотите сгруппировать серии смежных записей.

Вот один из способов ее решения с использованием row_number() для двух разных разделов: разница между рангами дает группа, к которой принадлежит каждая запись, которую можно использовать для агрегирования набора результатов.

select
    station_id,
    rainy,
    min(measured_at) started_at,
    max(measured_at) ended_at,
    max(measured_at) - min(measured_at) diff
from (
    select 
        t.*,
        row_number() over(partition by station_id order by measured_at) rn1,
        row_number() over(partition by station_id, rainy order by measured_at) rn2
    from mytable t
) t
group by station_id, rainy, rn1 - rn2
order by station_id, started_at
...