Generazione di serie temporali tra due date in PostgreSQL


92

Ho una query come questa che genera piacevolmente una serie di date tra 2 date date:

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

Genera 162 date tra 2004-03-07e 2004-08-16e questo quello che voglio. Il problema con questo codice è che non darebbe la risposta giusta quando le due date sono di anni diversi, ad esempio quando provo 2007-02-01e 2008-04-01.

C'è una soluzione migliore?


Risposte:


175

Può essere fatto senza conversione da / a int (ma invece a / da timestamp)

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;

3
perché è date_truncnecessario?
Idefixx

2
È solo presentazione. Elimina la stampa della parte temporale del timestamp che in questo caso è sempre zeri.
beemtee

73

Per generare una serie di date questo è il modo ottimale :

SELECT t.day::date 
FROM   generate_series(timestamp '2004-03-07'
                     , timestamp '2004-08-16'
                     , interval  '1 day') AS t(day);
  • Ulteriori date_trunc()non è necessaria. Il cast di date( day::date) lo fa implicitamente.

  • Ma non ha nemmeno senso eseguire il cast di valori letterali di data datecome parametro di input. Al contrario, timestampè la scelta migliore . Il vantaggio in termini di prestazioni è piccolo, ma non c'è motivo per non prenderlo. E non è necessario coinvolgere inutilmente le regole DST (ora legale) accoppiate con la conversione da datea timestamp with time zonee viceversa. Vedi sotto.

Sintassi breve equivalente, meno esplicita:

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

O con la funzione set-return SELECTnell'elenco:

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

La ASparola chiave è obbligatoria nell'ultima variante, dayaltrimenti Postgres interpreterebbe erroneamente l'alias della colonna . E non consiglierei quella variante prima di Postgres 10, almeno non con più di una funzione di restituzione di set nella stessa SELECTlista:

(A parte questo, l'ultima variante è in genere più veloce con un piccolo margine.)

Perché timestamp [without time zone]?

Esistono numerose varianti sovraccariche di generate_series(). Attualmente (Postgres 11):

SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
where  proname = 'generate_series';
function_signature | tipo_ritorno                
: ------------------------------------------------- ------------------------------- | : --------------------------
generate_series (integer, integer, integer) | numero intero                    
generate_series (integer, integer) | numero intero                    
generate_series (bigint, bigint, bigint) | bigint                     
generate_series (bigint, bigint) | bigint                     
generate_series (numeric, numeric, numeric) | numerico                    
generate_series (numerico, numerico) | numerico                    
generate_series (timestamp senza fuso orario, timestamp senza fuso orario, intervallo) | timestamp senza fuso orario
generate_series (timestamp con fuso orario, timestamp con fuso orario, intervallo) | timestamp con fuso orario

( numericsono state aggiunte varianti con Postgres 9.5.) Quelle rilevanti sono le ultime due in grassetto prendere e restituire timestamp/ timestamptz.

Non è possibile prendere o restituire variantidate . È necessario un cast esplicito per tornare date. La chiamata con timestampargomenti si risolve direttamente nella variante migliore senza scendere nelle regole di risoluzione del tipo di funzione e senza cast aggiuntivo per l'input.

timestamp '2004-03-07'è perfettamente valido, btw. La parte temporale omessa viene impostata per impostazione predefinita 00:00con il formato ISO.

Grazie alla risoluzione del tipo di funzione possiamo ancora passare date. Ma questo richiede più lavoro da Postgres. C'è un cast implicito da datea timestampe uno da datea timestamptz. Sarebbe ambiguo, ma timestamptzè "preferito" tra i "tipi di data / ora". Quindi la partita viene decisa al passaggio 4d. :

Scorri tutti i candidati e mantieni quelli che accettano i tipi preferiti (della categoria del tipo del tipo di dati di input) nella maggior parte delle posizioni in cui sarà richiesta la conversione del tipo. Mantieni tutti i candidati se nessuno accetta i tipi preferiti. Se rimane un solo candidato, usalo; altrimenti vai al passaggio successivo.

Oltre al lavoro extra nella risoluzione del tipo di funzione, questo aggiunge un cast extra a timestamptz- che non solo aggiunge più costi, ma può anche introdurre problemi con DST che portano a risultati imprevisti in rari casi. (L'ora legale è un concetto stupido, btw, non posso sottolinearlo abbastanza.)

Ho aggiunto demo al violino che mostrano il piano di query più costoso:

db <> fiddle qui

Relazionato:


7
Versione ancora più breve:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
Václav Kužel

Cosa significa la sintassi t (giorno)?
rendang

@rendang: AS t(day)in SELECT * FROM func() AS t(day)sono tabella e la colonna alias. La ASparola chiave è rumore opzionale in questo contesto. Vedi: stackoverflow.com/a/20230716/939860
Erwin Brandstetter

35

Puoi generare serie direttamente con le date. Non è necessario utilizzare int o timestamp:

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;

A seconda del fuso orario, ciò potrebbe restituire un risultato imprevisto. Ho avuto questo problema. Utilizza invece il timestamp. 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' :: date, '2019-11-04' :: date, '1 day') d
palhares

1

Puoi anche usare questo.

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.