Question

J'ai des données inégalement distribuées (par date) pour quelques années (2003-2008). Je souhaite interroger des données pour un ensemble donné de dates de début et de fin, en les regroupant selon l'un des intervalles pris en charge (jour, semaine, mois, trimestre, année) dans PostgreSQL 8.3 ( http://www.postgresql.org/docs/8.3/static/functions-datetime.html # FUNCTIONS-DATETIME-TRUNC ).

Le problème est que certaines des requêtes donnent des résultats continus sur la période requise, comme celui-ci:

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)

mais certains d'entre eux manquent des intervalles car il n'y a pas de données présentes, comme celle-ci:

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)

où l'ensemble de résultats requis est:

  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 nombre de 0 pour les entrées manquantes.

J'ai déjà vu des discussions sur Stack Overflow, mais elles ne résolvent pas mon problème, semble-t-il, car ma période de regroupement correspond à celle du (jour, semaine, mois, trimestre, année) et a été définie à l'exécution par l'application. Donc, une approche comme une jointure à gauche avec une table de calendrier ou une table de séquence ne sera d'aucun secours, je suppose.

Ma solution actuelle consiste à combler ces lacunes en Python (dans une application Turbogears) à l'aide du module de calendrier.

Existe-t-il un meilleur moyen de le faire?

Était-ce utile?

La solution

Vous pouvez créer la liste de tous les premiers jours de la dernière année (par exemple) avec

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

Ensuite, vous pouvez rejoindre cette série.

Autres conseils

Cette question est ancienne. Mais comme d'autres utilisateurs l'ont choisi comme maître pour un nouveau duplicata, j'ajoute une réponse appropriée.

Solution appropriée

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;
  • Utilisez LEFT JOIN , bien sûr.

  • generate_series () peut produire une table d'horodatages à la volée et très rapidement.

  • Il est généralement plus rapide d'agréger avant de vous rejoindre. J'ai récemment fourni un scénario de test sur sqlfiddle.com dans cette réponse:

  • Convertissez horodatage en date ( :: date ) pour un format de base. Pour plus d'informations, utilisez to_char () .

  • GROUP BY 1 est un raccourci de la syntaxe pour référencer la première colonne de sortie. Peut-être aussi GROUP BY jour , mais cela pourrait entrer en conflit avec une colonne existante du même nom. Ou bien GROUP BY date_trunc ('mois', date_col) :: date mais c'est trop long à mon goût.

  • Fonctionne avec les arguments d'intervalle disponibles pour date_trunc () .

  • count () jamais produit NULL ( 0 sans lignes), mais LEFT JOIN le fait.
    Pour renvoyer 0 au lieu de NULL dans le SELECT extérieur, utilisez COALESCE (some_count, 0) AS some_count . Le manuel.

  • Pour une solution plus générique ou des intervalles de temps arbitraires , tenez compte de cette réponse étroitement liée:

Vous pouvez créer une table temporaire au moment de l’exécution et y joindre à gauche. Cela semble avoir le plus de sens.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top