Desenredado, simplificado y arreglado, podría verse así:
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<>violín aquí
Entre todo el ruido, los identificadores engañosos y el formato poco convencional, el problema real estaba oculto aquí:
WHERE version = 1
Hiciste un uso correcto de RIGHT [OUTER] JOIN . Pero agregando un WHERE cláusula que requiere una fila existente de mytable convierte RIGHT [OUTER] JOIN a un [INNER] JOIN efectivamente.
Mueve ese filtro a JOIN condición para que funcione.
Simplifiqué algunas otras cosas mientras estaba en ello.
Mejor, aún
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<>violín aquí
Es mucho más económico agregar primero y unirse después:unirse a una fila por mes en lugar de una fila por día.
Es más barato basar GROUP BY y ORDER BY en la date valor en lugar del text representado .
count(*) es un poco más rápido que count(id) , mientras que es equivalente en este consulta.
generate_series() es un poco más rápido y seguro cuando se basa en timestamp en lugar de date . Ver:
- Generando series de tiempo entre dos fechas en PostgreSQL