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