Basislösung
Generieren Sie eine vollständige Liste der Monate und LEFT JOIN
der Rest dazu:
SELECT *
FROM (
SELECT to_char(m, 'YYYY-MON') AS yyyymmm
FROM generate_series(<start_date>, <end_date>, interval '1 month') m
) m
LEFT JOIN ( <your query here> ) q USING (yyyymmm);
Verwandte Antworten mit mehr Erklärung:
- Nehmen Sie an einer Zählabfrage für eine generate_series in Postgres teil und rufen Sie auch Nullwerte als "0" ab
- Beste Methode zum Zählen von Datensätzen nach beliebigen Zeitintervallen in Rails+Postgres
Erweiterte Lösung für Ihren Fall
Ihre Anfrage ist komplizierter, als ich zuerst verstanden habe. Sie benötigen die laufende Summe über alle Zeilen des ausgewählten Elements, dann möchten Sie Zeilen kürzen, die älter als ein Mindestdatum sind, und fehlende Monate mit der vorberechneten Summe des Vormonats auffüllen.
Das erreiche ich jetzt mit LEFT JOIN LATERAL
.
SELECT COALESCE(m.yearmonth, c.yearmonth)::date, sold_qty, on_hand
FROM (
SELECT yearmonth
, COALESCE(sold_qty, 0) AS sold_qty
, sum(on_hand_mon) OVER (ORDER BY yearmonth) AS on_hand
, lead(yearmonth) OVER (ORDER BY yearmonth)
- interval '1 month' AS nextmonth
FROM (
SELECT date_trunc('month', c.change_date) AS yearmonth
, sum(c.sold_qty / s.qty)::numeric(18,2) AS sold_qty
, sum(c.on_hand) AS on_hand_mon
FROM item_change c
LEFT JOIN item i USING (item_id)
LEFT JOIN item_size s ON s.item_id = i.item_id AND s.name = i.sell_size
LEFT JOIN item_plu p ON p.item_id = i.item_id AND p.seq_num = 0
WHERE c.change_date < date_trunc('month', now()) - interval '1 day'
AND c.item_id = (SELECT item_id FROM item_plu WHERE number = '51515')
GROUP BY 1
) sub
) c
LEFT JOIN LATERAL generate_series(c.yearmonth
, c.nextmonth
, interval '1 month') m(yearmonth) ON TRUE
WHERE c.yearmonth > date_trunc('year', now()) - interval '540 days'
ORDER BY COALESCE(m.yearmonth, c.yearmonth);
SQL-Fiddle mit einem minimalen Testfall.
Hauptpunkte:
-
Ich habe Ihre Ansicht vollständig aus der Abfrage entfernt. Viel Kosten für keinen Gewinn.
-
Da Sie eine Single auswählen
item_id
, brauchen Sie nichtGROUP BY item_id
oderPARTITION BY item_id
. -
Verwenden Sie kurze Tabellenaliase und machen Sie alle Verweise unzweideutig – insbesondere, wenn Sie in einem öffentlichen Forum posten.
-
Klammern in Ihren Joins waren nur Rauschen. Joins werden standardmäßig ohnehin von links nach rechts ausgeführt.
-
Vereinfachte Datumsgrenzen (da ich mit Zeitstempeln arbeite):
date_trunc('year', current_date) - interval '540 days' date_trunc('month', current_date) - interval '1 day'
Äquivalent, aber einfacher und schneller als:
current_date - date_part('day',current_date)::integer - 540 current_date - date_part('day',current_date)::integer -
Fehlende Monate fülle ich jetzt nach allen Berechnungen mit
generate_series()
aus Aufrufe pro Reihe. -
Es muss
LEFT JOIN LATERAL ... ON TRUE
sein , nicht die Kurzform einesJOIN LATERAL
um den Eckfall der letzten Reihe zu fangen. Ausführliche Erklärung:
Wichtige Randnotizen:
character(22)
ist schrecklich Datentyp für einen Primärschlüssel (oder beliebige Säule). Einzelheiten:
Idealerweise wäre dies ein int
oder bigint
-Spalte oder möglicherweise eine UUID
.
Auch das Speichern von Geldbeträgen als money
Typ oder integer
(repräsentiert Cents) schneidet insgesamt viel besser ab.
Langfristig , verschlechtert sich zwangsläufig die Performance, da Sie alle Zeilen von Anfang an in Ihre Berechnung einbeziehen müssen. Sie sollten alte Zeilen abschneiden und den Rest von on_hold
materialisieren auf jährlicher Basis oder so.