2016-02-18 2 views
6

Используя Postgres 9.3, я пытаюсь подсчитать количество смежных дней определенного типа погоды. Если мы предположим, что мы имеем регулярные временные ряды и метеосводки:Postgres windowing (определить смежные дни)

date|weather 
"2016-02-01";"Sunny" 
"2016-02-02";"Cloudy" 
"2016-02-03";"Snow" 
"2016-02-04";"Snow" 
"2016-02-05";"Cloudy" 
"2016-02-06";"Sunny" 
"2016-02-07";"Sunny" 
"2016-02-08";"Sunny" 
"2016-02-09";"Snow" 
"2016-02-10";"Snow" 

Я хочу что-то рассчитывать смежные дни той же погоды. Результаты должны выглядеть следующим образом:

date|weather|contiguous_days 
"2016-02-01";"Sunny";1 
"2016-02-02";"Cloudy";1 
"2016-02-03";"Snow";1 
"2016-02-04";"Snow";2 
"2016-02-05";"Cloudy";1 
"2016-02-06";"Sunny";1 
"2016-02-07";"Sunny";2 
"2016-02-08";"Sunny";3 
"2016-02-09";"Snow";1 
"2016-02-10";"Snow";2 

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

Вот что я пытался ...

Select date, weather, Row_Number() Over (partition by weather order by date) 
    from t_weather 

было бы лучше просто легче сравнивать текущую строку к следующему? Как бы вы это сделали, сохранив счет? Любые мысли, идеи или даже решения были бы полезны! -Kip

+0

не могли бы вы просто сделать подсчет (дата) группы по погоде – hd1

ответ

2

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

Один у вас есть группировка, остальное row_number():

Select date, weather, 
     Row_Number() Over (partition by weather, grp order by date) 
from (select w.*, 
      (date - row_number() over (partition by weather order by date) * interval '1 day') as grp 
     from t_weather w 
    ) w; 

SQL, скрипка here.

+0

Это не работает: [SQL Скрипки] (HTTP: //www.sqlfiddle .com/#! 15/a0bcd/1). (Я не спустил вниз.) – Travis

+1

Я всегда забываю странности о арифметике даты Postgres. Работа редактирования.Я полагаю, что сам downvote просто злой; ошибка была скорее опечаткой в ​​коде, чем логической ошибкой, и логика должна быть правильной. –

+0

Пробовал запрос, но он неверен: 'row_number()' подсчитывает общее появление конкретной «погоды», без сброса. Например, «5 февраля, облачный» должен иметь row_number() 1, потому что «Feb 4» - «Снег». – Kenney

1

Вы можете сделать это с помощью рекурсивных CTE следующим образом:

WITH RECURSIVE CTE_ConsecutiveDays AS 
(
    SELECT 
     my_date, 
     weather, 
     1 AS consecutive_days 
    FROM My_Table T 
    WHERE 
     NOT EXISTS (SELECT * FROM My_Table T2 WHERE T2.my_date = T.my_date - INTERVAL '1 day' AND T2.weather = T.weather) 
    UNION ALL 
    SELECT 
     T.my_date, 
     T.weather, 
     CD.consecutive_days + 1 
    FROM 
     CTE_ConsecutiveDays CD 
    INNER JOIN My_Table T ON 
     T.my_date = CD.my_date + INTERVAL '1 day' AND 
     T.weather = CD.weather 
) 
SELECT * 
FROM CTE_ConsecutiveDays 
ORDER BY my_date; 

Вот SQL скрипку тест: http://www.sqlfiddle.com/#!15/383e5/3

+0

это * почти * правильный. (размещение точки с запятой равно * после * заявления в Postgres) – wildplasser

+0

Исправлено, спасибо –

2

Я не уверен, что двигатель запрос будет делать при сканировании нескольких раз через тот же набор данных (вроде как вычисления площади под кривой), но это работает ...

WITH v(date, weather) AS (
VALUES 
('2016-02-01'::date,'Sunny'::text), 
('2016-02-02','Cloudy'), 
('2016-02-03','Snow'), 
('2016-02-04','Snow'), 
('2016-02-05','Cloudy'), 
('2016-02-06','Sunny'), 
('2016-02-07','Sunny'), 
('2016-02-08','Sunny'), 
('2016-02-09','Snow'), 
('2016-02-10','Snow')), 
changes AS (
SELECT date, 
    weather, 
    CASE WHEN lag(weather) OVER() = weather THEN 1 ELSE 0 END change 
FROM v) 
SELECT date 
    , weather 
    ,(SELECT count(weather) -- number of times the weather didn't change 
     FROM changes v2 
     WHERE v2.date <= v1.date AND v2.weather = v1.weather 
     AND v2.date >= (-- bounded between changes of weather 
      SELECT max(date) 
      FROM changes v3 
      WHERE change = 0 
      AND v3.weather = v1.weather 
      AND v3.date <= v1.date) --<-- here's the expensive part 
    ) curve 
FROM changes v1 
1

Вот еще один подход, основанный прочь this answer.

Сначала мы добавим столбец change, то есть 1 или 0, в зависимости от того, отличается ли погода от предыдущего дня.
Затем вводится столбец group_nr путем суммирования change по order by date. Это дает уникальный номер группы для каждой последовательности последовательных дней с одинаковой погодой, так как сумма только увеличивается в первый день каждой последовательности.
Наконец, мы делаем row_number() over (partition by group_nr order by date), чтобы произвести подсчет количества операций в группе.

select date, weather, row_number() over (partition by group_nr order by date) 
from (
    select *, sum(change) over (order by date) as group_nr 
    from (
    select *, (weather != lag(weather,1,'') over (order by date))::int as change 
    from tmp_weather 
) t1 
) t2; 

sqlfiddle (используется эквивалентный WITH синтаксис)