Démêlé, simplifié et corrigé, cela pourrait ressembler à ceci :
SELECT to_char(s.tag,'yyyy-mm') AS monat
, count(t.id) AS eintraege
FROM (
SELECT generate_series(min(date_from)::date
, max(date_from)::date
, interval '1 day'
)::date AS tag
FROM mytable t
) s
LEFT JOIN mytable t ON t.date_from::date = s.tag AND t.version = 1
GROUP BY 1
ORDER BY 1;
db<>jouez ici
Parmi tout le bruit, les identifiants trompeurs et le format non conventionnel, le problème réel était caché ici :
WHERE version = 1
Vous avez utilisé correctement RIGHT [OUTER] JOIN
. Mais en ajoutant un WHERE
clause qui nécessite une ligne existante de mytable
convertit la RIGHT [OUTER] JOIN
à un [INNER] JOIN
efficacement.
Déplacez ce filtre dans le JOIN
condition pour que cela fonctionne.
J'ai simplifié d'autres choses tout en y étant.
Mieux, encore
SELECT to_char(mon, 'yyyy-mm') AS monat
, COALESCE(t.ct, 0) AS eintraege
FROM (
SELECT date_trunc('month', date_from)::date AS mon
, count(*) AS ct
FROM mytable
WHERE version = 1
GROUP BY 1
) t
RIGHT JOIN (
SELECT generate_series(date_trunc('month', min(date_from))
, max(date_from)
, interval '1 mon')::date
FROM mytable
) m(mon) USING (mon)
ORDER BY mon;
db<>jouez ici
Il est beaucoup moins cher d'agréger d'abord et de rejoindre plus tard - rejoindre une ligne par mois au lieu d'une ligne par jour.
Il est moins cher de baser GROUP BY
et ORDER BY
à la date
valeur au lieu du text
rendu .
count(*)
est un peu plus rapide que count(id)
, alors qu'équivalent dans ceci requête.
generate_series()
est un peu plus rapide et plus sûr lorsqu'il est basé sur timestamp
au lieu de date
. Voir :
- Générer des séries temporelles entre deux dates dans PostgreSQL