2016-02-18 11 views
6

Mit Postgres 9.3 versuche ich die Anzahl der zusammenhängenden Tage eines bestimmten Wettertyps zu zählen. Wenn wir annehmen, haben wir eine regelmäßige Zeitreihe und Wetterbericht:Postgres windowing (Festlegung zusammenhängender Tage)

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" 

Ich mag etwas die zusammenhängenden Tage aus dem gleichen Wetter zählen. Die Ergebnisse sollen in etwa so aussehen:

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 

Ich habe auf diesem meinen Kopf hämmern worden für eine Weile versucht, Windowing-Funktionen zu verwenden. Zuerst scheint es, als wäre es kein Kinderspiel, aber dann fand ich heraus, es ist viel schwieriger als erwartet.

Hier ist, was ich versucht habe ...

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

Wäre es einfach leichter, besser sein, die aktuelle Zeile zur nächsten zu vergleichen? Wie würden Sie das tun, während Sie eine Zählung beibehalten? Irgendwelche Gedanken, Ideen oder sogar Lösungen wären hilfreich! -Kip

+0

konnte nicht Sie einfach tun, um eine Zählung (Datum) Gruppe von Wetter – hd1

Antwort

2

Sie müssen das zusammenhängende identifizieren, wo das Wetter das gleiche ist. Sie können dies tun, indem Sie eine Gruppierungs-ID hinzufügen. Es gibt eine einfache Methode: Subtrahiere eine Folge von steigenden Zahlen von den Daten und sie ist für zusammenhängende Daten konstant.

One Sie die Gruppierung haben, ist der Rest 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; 

Die SQL Fiddle ist here.

+0

dies nicht funktioniert: [SQL Fiddle] (http: //www.sqlfiddle .com/#! 15/a0bcd/1). (Ich habe nicht runtergestimmt.) – Travis

+1

Ich vergesse immer die Seltsamkeiten bezüglich der Postgres Date Arithmetik. Die Bearbeitung funktioniert.Ich stelle mir vor, dass der Downvote selbst einfach bösartig ist; Der Fehler war eher ein Tippfehler im Code als ein logischer Fehler, und die Logik sollte korrekt sein. –

+0

Die Abfrage wurde versucht, aber es ist falsch: Die 'row_number()' zählt das gesamte Auftreten eines bestimmten 'Wetter', ohne zurückgesetzt zu werden. Zum Beispiel sollte "Feb 5, Cloudy" row_number() 1 haben, weil "Feb 4" "Snow" ist. – Kenney

1

Sie können dies mit einem rekursiven CTE erreichen wie folgt:

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; 

Hier ist die SQL-Fiddle zu testen: http://www.sqlfiddle.com/#!15/383e5/3

+0

das ist * fast * korrekt. (die Platzierung des Semikolons ist * nach * der Aussage in Postgres) – wildplasser

+0

Korrigiert, danke –

2

Ich bin mir nicht sicher, was die Abfrage-Engine tun wird, wenn mehrere Scannen mal über die gleiche Datenmenge (ein bisschen wie Fläche unter einer Kurve Berechnung), aber das funktioniert ...

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

Hier ist ein weiterer Ansatz basiert weg von this answer.

Zuerst fügen wir eine Spalte hinzu, die 1 oder 0 ist, abhängig davon, ob das Wetter anders ist oder nicht vom vorherigen Tag.
Dann führen wir eine group_nr Spalte durch Summierung der über eine order by date. Dies erzeugt eine eindeutige Gruppennummer für jede Sequenz aufeinanderfolgender Tage gleichen Wetters, da die Summe nur am ersten Tag jeder Sequenz inkrementiert wird.
Schließlich machen wir eine row_number() over (partition by group_nr order by date), um die laufende Anzahl pro Gruppe zu produzieren.

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 (verwendet äquivalente WITH Syntax)