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