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.

È stato utile?

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 in date ( :: date ) per un formato di base. Per ulteriori informazioni to_char () .

  • GROUP BY 1 è una scorciatoia di sintassi per fare riferimento alla prima colonna di output. Potrebbe essere anche GROUP BY day , ma potrebbe essere in conflitto con una colonna esistente con lo stesso nome. Oppure GROUP 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 produce NULL ( 0 per nessuna riga), ma il LEFT JOIN lo fa.
    Per restituire 0 anziché NULL nel SELECT esterno, utilizzare COALESCE (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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top