Mam takie zapytanie, które ładnie generuje serię dat między 2 podanymi datami:
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
Generuje 162 daty od 2004-03-07
i 2004-08-16
do tego czego chcę. Problem z tym kodem polega na tym, że nie dałby on prawidłowej odpowiedzi, gdy dwie daty pochodzą z różnych lat, na przykład gdy próbuję 2007-02-01
i 2008-04-01
.
Czy jest lepsze rozwiązanie?
postgresql
date
time-series
postgresql-9.1
generate-series
f.ashouri
źródło
źródło
Odpowiedzi:
Można to zrobić bez konwersji do / z int (ale zamiast tego do / z timestamp)
SELECT date_trunc('day', dd):: date FROM generate_series ( '2007-02-01'::timestamp , '2008-04-01'::timestamp , '1 day'::interval) dd ;
źródło
date_trunc
potrzebny?Aby wygenerować serię dat, jest to optymalny sposób:
SELECT t.day::date FROM generate_series(timestamp '2004-03-07' , timestamp '2004-08-16' , interval '1 day') AS t(day);
Dodatkowe
date_trunc()
nie są potrzebne. Rzutowanie dodate
(day::date
) robi to niejawnie.Ale nie ma też sensu rzutowanie literałów daty na
date
parametr wejściowy. Au contraire,timestamp
to najlepszy wybór . Zaleta wydajności jest niewielka, ale nie ma powodu, aby jej nie brać. I nie trzeba niepotrzebnie stosować reguł czasu letniego (DST) w połączeniu z konwersją zdate
natimestamp with time zone
iz powrotem. Zobacz poniżej.Równoważna, mniej jednoznaczna krótka składnia:
SELECT day::date FROM generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;
Lub z funkcją zwracania zestawu na
SELECT
liście:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;
Słowo
AS
kluczowe jest wymagane w ostatnim wariancie, wday
przeciwnym razie Postgres błędnie zinterpretowałby alias kolumny . I nie radziłbym tego wariantu przed Postgresem 10 - przynajmniej nie z więcej niż jedną funkcją zwracającą set na tej samejSELECT
liście:(Pomijając to, ostatni wariant jest zwykle najszybszy z niewielkim marginesem).
Dlaczego
timestamp [without time zone]
?Istnieje wiele przeciążonych wariantów
generate_series()
. Obecnie (Postgres 11):(
numeric
warianty zostały dodane z Postgres 9.5.) Odpowiednie są dwa ostatnie wytłuszczone i powracającetimestamp
/timestamptz
.Nie ma wariantu przyjmowania ani zwrotu
date
. Aby powrócić, potrzebne jest wyraźne rzutowaniedate
. Wywołanie ztimestamp
argumentami jest rozwiązywane bezpośrednio do najlepszego wariantu bez schodzenia do reguł rozpoznawania typu funkcji i bez dodatkowego rzutowania danych wejściowych.timestamp '2004-03-07'
jest całkowicie ważny, przy okazji. Pominięta część czasu ma domyślnie00:00
format ISO.Dzięki rozdzielczości typu funkcji nadal możemy przejść
date
. Ale to wymaga więcej pracy od Postgres. Istnieje niejawne rzutowanie oddate
dotimestamp
oraz oddate
dotimestamptz
. Byłoby niejednoznaczne, aletimestamptz
jest „preferowane” wśród „typów daty / godziny”. Tak więc mecz jest rozstrzygany w kroku 4d. :Oprócz dodatkowej pracy związanej z rozwiązywaniem typów funkcji, dodaje to dodatkowe rzutowanie
timestamptz
- co nie tylko zwiększa koszty, ale może również powodować problemy z czasem letnim, prowadząc w rzadkich przypadkach do nieoczekiwanych wyników. (Czas letni to kretyński pomysł, nawiasem mówiąc, nie można tego wystarczająco podkreślić).Dodałem dema do skrzypiec pokazujące droższy plan zapytań:
db <> skrzypce tutaj
Związane z:
źródło
SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
AS t(day)
inSELECT * FROM func() AS t(day)
to alias tabeli i kolumny. WAS
tym kontekście słowo kluczowe to opcjonalny szum. Zobacz: stackoverflow.com/a/20230716/939860Możesz generować serie bezpośrednio z datami. Nie ma potrzeby używania numerów int ani sygnatur czasowych:
select date::date from generate_series( '2004-03-07'::date, '2004-08-16'::date, '1 day'::interval ) date;
źródło
Możesz również użyć tego.
select generate_series ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date
źródło