Postgres - come restituire righe con 0 conteggi per dati mancanti?
-
19-08-2019 - |
Domanda
Ho distribuito in modo non uniforme dati (data scritta) per alcuni anni (2003-2008). Voglio interrogare i dati per un determinato set di date di inizio e fine, raggruppando i dati per uno qualsiasi degli intervalli supportati (giorno, settimana, mese, trimestre, anno) in PostgreSQL 8.3 ( http://www.postgresql.org/docs/8.3/static/functions-datetime.html # FUNZIONI-DATETIME-TRUNC ).
Il problema è che alcune query forniscono risultati continui per il periodo richiesto, come questo:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 77 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 64
2008-01-01 | 31
2008-02-01 | 14
2008-03-01 | 21
2008-04-01 | 28
2008-05-01 | 44
2008-06-01 | 100
2008-07-01 | 72
2008-08-01 | 91
2008-09-01 | 92
2008-10-01 | 79
2008-11-01 | 65
(12 rows)
ma alcuni di loro mancano alcuni intervalli perché non sono presenti dati, come questo:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 75 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-03-01 | 1
2008-04-01 | 2
2008-06-01 | 1
2008-08-01 | 3
2008-10-01 | 2
(7 rows)
dove si trova il set di risultati richiesto:
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-02-01 | 0
2008-03-01 | 1
2008-04-01 | 2
2008-05-01 | 0
2008-06-01 | 1
2008-07-01 | 0
2008-08-01 | 3
2008-09-01 | 0
2008-10-01 | 2
2008-11-01 | 0
(12 rows)
Un conteggio di 0 per le voci mancanti.
Ho visto discussioni precedenti su Stack Overflow ma a quanto pare non risolvono il mio problema, dal momento che il mio periodo di raggruppamento è uno di (giorno, settimana, mese, trimestre, anno) e ho deciso il runtime dall'applicazione. Quindi un approccio come il join sinistro con una tabella di calendario o una tabella di sequenza non aiuta a indovinare.
La mia attuale soluzione a questo è quella di colmare queste lacune in Python (in un'app Turbogears) usando il modulo calendario.
C'è un modo migliore per farlo.
Soluzione
Puoi creare l'elenco di tutti i primi giorni dell'ultimo anno (diciamo) con
select distinct date_trunc('month', (current_date - offs)) as date
from generate_series(0,365,28) as offs;
date
------------------------
2007-12-01 00:00:00+01
2008-01-01 00:00:00+01
2008-02-01 00:00:00+01
2008-03-01 00:00:00+01
2008-04-01 00:00:00+02
2008-05-01 00:00:00+02
2008-06-01 00:00:00+02
2008-07-01 00:00:00+02
2008-08-01 00:00:00+02
2008-09-01 00:00:00+02
2008-10-01 00:00:00+02
2008-11-01 00:00:00+01
2008-12-01 00:00:00+01
Quindi puoi unirti a quella serie.
Altri suggerimenti
Questa domanda è vecchia. Ma poiché gli altri utenti l'hanno scelto come master per un nuovo duplicato, sto aggiungendo una risposta adeguata.
Soluzione corretta
SELECT *
FROM (
SELECT day::date
FROM generate_series(timestamp '2007-12-01'
, timestamp '2008-12-01'
, interval '1 month') day
) d
LEFT JOIN (
SELECT date_trunc('month', date_col)::date AS day
, count(*) AS some_count
FROM tbl
WHERE date_col >= date '2007-12-01'
AND date_col <= date '2008-12-06'
-- AND ... more conditions
GROUP BY 1
) t USING (day)
ORDER BY day;
-
Usa
LEFT JOIN
, ovviamente. -
generate_series ()
può produrre una tabella di timestamp al volo e molto velocemente. -
In genere è più veloce aggregare prima di aderire. Di recente ho fornito un caso di prova su sqlfiddle.com in questa risposta correlata:
-
Trasmetti il ??
timestamp
indate
(:: date
) per un formato di base. Per ulteriori informazionito_char ()
. GROUP BY 1
è una scorciatoia di sintassi per fare riferimento alla prima colonna di output. Potrebbe essere ancheGROUP BY day
, ma potrebbe essere in conflitto con una colonna esistente con lo stesso nome. OppureGROUP BY date_trunc ('month', date_col) :: date
ma è troppo lungo per i miei gusti.-
Funziona con gli argomenti intervallo disponibili per
date_trunc ()
. -
count ()
mai produceNULL
(0
per nessuna riga), ma ilLEFT JOIN
lo fa.
Per restituire0
anzichéNULL
nelSELECT
esterno, utilizzareCOALESCE (some_count, 0) AS some_count
. Il manuale. -
Per una soluzione più generica o intervalli di tempo arbitrari considera questa risposta strettamente correlata:
È possibile creare una tabella temporanea in fase di runtime e lasciare un join su quella. Sembra avere più senso.