web-dev-qa-db-fra.com

Requête PostgreSQL pour compter / grouper par jour et afficher les jours sans données

J'ai besoin de créer une requête PostgreSQL qui renvoie

  • un jour
  • le nombre d'objets trouvés pour cette journée

Il est important que chaque jour apparaisse dans les résultats , même si aucun objet n'a été trouvé ce jour-là. (Cela a été discuté auparavant, mais je n'ai pas pu faire fonctionner les choses dans mon cas spécifique.)

Tout d'abord, j'ai trouvé une requête SQL pour générer une plage de jours , avec laquelle je peux rejoindre:

SELECT to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD')
AS date 
FROM generate_series(0, 365, 1) 
AS offs

Résulte en:

    date    
------------
 2013-03-28
 2013-03-27
 2013-03-26
 2013-03-25
 ...
 2012-03-28
(366 rows)

Maintenant, j'essaye de joindre cela à une table nommée 'sharer_emailshare' qui a une colonne 'créée':

Table 'public.sharer_emailshare'
column    |   type  
-------------------
id        | integer
created   | timestamp with time zone
message   | text
to        | character varying(75)

Voici le meilleur GROUP BY requête que j'ai jusqu'à présent:

SELECT d.date, count(se.id) FROM (
    select to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD')
    AS date 
    FROM generate_series(0, 365, 1) 
    AS offs
    ) d 
JOIN sharer_emailshare se 
ON (d.date=to_char(date_trunc('day', se.created), 'YYYY-MM-DD'))  
GROUP BY d.date;

Les resultats:

    date    | count 
------------+-------
 2013-03-27 |    11
 2013-03-24 |     2
 2013-02-14 |     2
(3 rows)

Résultats désirés:

    date    | count 
------------+-------
 2013-03-28 |     0
 2013-03-27 |    11
 2013-03-26 |     0
 2013-03-25 |     0
 2013-03-24 |     2
 2013-03-23 |     0
 ...
 2012-03-28 |     0
(366 rows)

Si je comprends bien, c'est parce que j'utilise un simple (implicite INNER) JOIN, et c'est le comportement attendu, comme discuté dans les documents postgres .

J'ai parcouru des dizaines de solutions StackOverflow, et toutes celles avec des requêtes de travail semblent spécifiques à MySQL/Oracle/MSSQL et j'ai du mal à les traduire en PostgreSQL.

Le gars qui a demandé cette question a trouvé sa réponse, avec Postgres, mais l'a mise sur un lien Pastebin qui a expiré il y a quelque temps.

J'ai essayé de passer à LEFT OUTER JOIN, RIGHT JOIN, RIGHT OUTER JOIN, CROSS JOIN, utilisez une instruction CASE pour saisir une autre valeur si null, COALESCE pour fournir une valeur par défaut, etc., mais je n'ai pas pu les utiliser d'une manière qui me permette Ce dont j'ai besoin.

Toute aide est appréciée! Et je promets que je vais bientôt lire ce livre géant de PostgreSQL;)

53
Marcel Chastain

Vous avez juste besoin d'un left outer join au lieu d'une jointure interne:

SELECT d.date, count(se.id)
FROM (SELECT to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD') AS date 
      FROM generate_series(0, 365, 1) AS offs
     ) d LEFT OUTER JOIN
     sharer_emailshare se 
     ON d.date = to_char(date_trunc('day', se.created), 'YYYY-MM-DD'))  
GROUP BY d.date;
47
Gordon Linoff

Prolongeant la réponse utile de Gordon Linoff, je suggérerais quelques améliorations telles que:

  • Utilisez ::date Au lieu de date_trunc('day', ...)
  • Rejoignez un type de date plutôt qu'un type de caractère (c'est plus propre).
  • Utilisez des plages de dates spécifiques pour les modifier plus facilement plus tard. Dans ce cas, je sélectionne un an avant l'entrée la plus récente dans le tableau - quelque chose qui n'aurait pas pu être fait facilement avec l'autre requête.
  • Calculez les totaux pour une sous-requête arbitraire (à l'aide d'un CTE). Il vous suffit de convertir la colonne d'intérêt en type de date et de l'appeler date_column.
  • Inclure une colonne pour le total cumulé. (Pourquoi pas?)

Voici ma requête:

WITH dates_table AS (
    SELECT created::date AS date_column FROM sharer_emailshare WHERE showroom_id=5
)
SELECT series_table.date, COUNT(dates_table.date_column), SUM(COUNT(dates_table.date_column)) OVER (ORDER BY series_table.date) FROM (
    SELECT (last_date - b.offs) AS date
        FROM (
            SELECT GENERATE_SERIES(0, last_date - first_date, 1) AS offs, last_date from (
                 SELECT MAX(date_column) AS last_date, (MAX(date_column) - '1 year'::interval)::date AS first_date FROM dates_table
            ) AS a
        ) AS b
) AS series_table
LEFT OUTER JOIN dates_table
    ON (series_table.date = dates_table.date_column)
GROUP BY series_table.date
ORDER BY series_table.date

J'ai testé la requête, et elle produit les mêmes résultats, plus la colonne pour le total cumulé.

26
Travis

Sur la base de la réponse de Gordon Linoff, j'ai réalisé qu'un autre problème était que j'avais une clause WHERE que je n'avais pas mentionnée dans la question d'origine.

Au lieu d'un WHERE nu, j'ai fait une sous-requête:

SELECT d.date, count(se.id) FROM (
    select to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD')
    AS date 
    FROM generate_series(0, 365, 1) 
    AS offs
    ) d 
LEFT OUTER JOIN (
    SELECT * FROM sharer_emailshare 
    WHERE showroom_id=5
) se
ON (d.date=to_char(date_trunc('day', se.created), 'YYYY-MM-DD')) 
GROUP BY d.date;
7
Marcel Chastain