Nieskomplikowany, uproszczony i naprawiony, może wyglądać tak:
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<>graj tutaj
Wśród całego szumu, wprowadzających w błąd identyfikatorów i niekonwencjonalnego formatu, prawdziwy problem krył się tutaj:
WHERE version = 1
Poprawnie użyłeś RIGHT [OUTER] JOIN . Ale dodanie WHERE klauzula, która wymaga istniejącego wiersza z mytable konwertuje RIGHT [OUTER] JOIN do [INNER] JOIN skutecznie.
Przenieś ten filtr do JOIN warunek, aby to zadziałało.
W tym czasie uprościłem kilka innych rzeczy.
Lepiej, ale
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<>graj tutaj
O wiele taniej jest agregować najpierw i dołączyć później — dołączanie do jednego rzędu na miesiąc zamiast do jednego rzędu dziennie.
Taniej jest bazować GROUP BY i ORDER BY w dniu date wartość zamiast renderowanego text .
count(*) jest trochę szybszy niż count(id) , będąc odpowiednikiem w tym zapytanie.
generate_series() jest nieco szybszy i bezpieczniejszy, gdy jest oparty na timestamp zamiast date . Zobacz:
- Generowanie szeregów czasowych między dwiema datami w PostgreSQL