PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Auswahl der Summe und des laufenden Saldos für die letzten 18 Monate mit generate_series

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:

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 nicht GROUP BY item_id oder PARTITION 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 eines JOIN 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.