Generieren von Zeitreihen zwischen zwei Daten in PostgreSQL

91

Ich habe eine Abfrage wie diese, die eine Reihe von Daten zwischen zwei angegebenen Daten generiert:

select date '2004-03-07' + j - i as AllDate 
from generate_series(0, extract(doy from date '2004-03-07')::int - 1) as i,
     generate_series(0, extract(doy from date '2004-08-16')::int - 1) as j

Es generiert 162 Daten zwischen 2004-03-07und 2004-08-16und dem, was ich will. Das Problem mit diesem Code ist, dass er nicht die richtige Antwort gibt, wenn die beiden Daten aus unterschiedlichen Jahren stammen, zum Beispiel wenn ich es versuche 2007-02-01und 2008-04-01.

Gibt es eine bessere Lösung?

f.ashouri
quelle
Mögliches Duplikat der Liste
Mišo

Antworten:

170

Kann ohne Konvertierung in / von int (aber stattdessen in / von Zeitstempel) durchgeführt werden

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;
Wildplasser
quelle
2
warum wird date_truncbenötigt?
Idefixx
1
Es ist nur eine Präsentation. Das Drucken des Zeitteils des Zeitstempels, der in diesem Fall immer Nullen ist, entfällt.
Beemtee
1
Sie haben jetzt einen Ruf von 33.333
Tony Tannous
72

Um eine Reihe von Daten zu generieren, ist dies der optimale Weg:

SELECT t.day::date 
FROM   generate_series(timestamp '2004-03-07'
                     , timestamp '2004-08-16'
                     , interval  '1 day') AS t(day);
  • Zusätzliches date_trunc()wird nicht benötigt. Die Besetzung von date( day::date) macht das implizit.

  • Es macht aber auch keinen Sinn, Datumsliterale dateals Eingabeparameter zu verwenden. Au contraire timestampist die beste Wahl . Der Leistungsvorteil ist gering, aber es gibt keinen Grund, ihn nicht zu nutzen. Und Sie müssen nicht unnötig DST-Regeln (Sommerzeit) in Verbindung mit der Konvertierung von datenach timestamp with time zoneund zurück verwenden. Siehe unten.

Äquivalente, weniger explizite kurze Syntax:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;

Oder mit der Set-Return-Funktion in der SELECTListe:

SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;

Das ASSchlüsselwort ist erforderlich in der letzten Variante würde Postgres die Spalte alias falsch interpretieren daysonst. Und ich würde nicht diese Variante vor Postgres 10 raten - zumindest nicht mit mehr als einem Satz wiederkehr Funktion in der gleichen SELECTListe:

(Abgesehen davon ist die letzte Variante normalerweise mit einem winzigen Vorsprung am schnellsten.)

Warum timestamp [without time zone]?

Es gibt eine Reihe überladener Varianten von generate_series(). Derzeit (Postgres 11):

SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
where  proname = 'generate_series';
Funktionssignatur | return_type                
: --------------------------------------------- ------------------------------- | : --------------------------
generate_series (Ganzzahl, Ganzzahl, Ganzzahl) | ganze Zahl                    
generate_series (Ganzzahl, Ganzzahl) | ganze Zahl                    
generate_series (bigint, bigint, bigint) | Bigint                     
generate_series (bigint, bigint) | Bigint                     
generate_series (numerisch, numerisch, numerisch) | numerisch                    
generate_series (numerisch, numerisch) | numerisch                    
generate_series (Zeitstempel ohne Zeitzone, Zeitstempel ohne Zeitzone, Intervall) | Zeitstempel ohne Zeitzone
generate_series (Zeitstempel mit Zeitzone, Zeitstempel mit Zeitzone, Intervall) | Zeitstempel mit Zeitzone

( numericVarianten wurden mit Postgres 9,5 hinzugefügt.) Die relevanten diejenigen Die letzten beiden sind in bold Mitnahmen und Zurückkehren timestamp/ timestamptz.

Es gibt keine Variante, die nimmt oder zurückgibtdate . Für die Rückkehr ist eine explizite Besetzung erforderlich date. Der Aufruf mit timestampArgumenten wird direkt in die beste Variante aufgelöst, ohne in Auflösungsregeln für Funktionstypen abzusteigen und ohne zusätzliche Umwandlung für die Eingabe.

timestamp '2004-03-07'ist übrigens vollkommen gültig. Der ausgelassene Zeitteil ist standardmäßig im 00:00ISO-Format.

Dank der Auflösung des Funktionstyps können wir immer noch bestehen date. Das erfordert jedoch mehr Arbeit von Postgres. Es gibt eine implizite Besetzung von datebis timestampsowie eine von datebis timestamptz. Wäre nicht eindeutig, aber timestamptzist „bevorzugt“ unter „Datum / Zeit - Typen“. Das Match wird also in Schritt 4d entschieden. ::

Führen Sie alle Kandidaten durch und bewahren Sie diejenigen, die bevorzugte Typen (der Typkategorie des Eingabedatentyps) akzeptieren, an den meisten Positionen auf, an denen eine Typkonvertierung erforderlich ist. Behalten Sie alle Kandidaten, wenn keine bevorzugte Typen akzeptieren. Wenn nur noch ein Kandidat übrig ist, verwenden Sie ihn. Andernfalls fahren Sie mit dem nächsten Schritt fort.

Zusätzlich zu der zusätzlichen Arbeit bei der Auflösung von Funktionstypen wird eine zusätzliche Besetzung hinzugefügt, timestamptzdie nicht nur die Kosten erhöht, sondern auch Probleme mit der Sommerzeit verursachen kann, die in seltenen Fällen zu unerwarteten Ergebnissen führen. (DST ist übrigens ein schwachsinniges Konzept, kann dies nicht genug betonen.) Verwandte:

Ich habe der Geige Demos hinzugefügt, die den teureren Abfrageplan zeigen:

db <> hier fummeln

Verbunden:

Erwin Brandstetter
quelle
7
Noch kürzere Version:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
Václav Kužel
35

Sie können Serien direkt mit Datumsangaben generieren. Keine Verwendung von Ints oder Zeitstempeln:

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;
Fbonetti
quelle
Abhängig von Ihrer Zeitzone kann dies zu einem unerwarteten Ergebnis führen. Ich hatte dieses Problem. Verwenden Sie stattdessen den Zeitstempel. SET session TIME zone 'America / Sao_Paulo' SELECT d :: date FROM generate_series ('2019-11-01' :: date, '2019-11-03' :: date, '1 day') d SELECT d :: date FROM generate_series ('2019-11-01' :: Datum, '2019-11-04' :: Datum, '1 Tag') d
palhares
1

Sie können dies auch verwenden.

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
Meyyappan
quelle