sql >> Databasteknik >  >> RDS >> PostgreSQL

Välj summa och löpande saldo för de senaste 18 månaderna med generera_serier

Grundläggande lösning

Skapa en komplett lista över månader och LEFT JOIN resten till det:

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);

Relaterade svar med mer förklaring:

Avancerad lösning för ditt fall

Din fråga är mer komplicerad än jag först förstod. Du behöver den löpande summan över alla rader av det valda objektet, då vill du trimma rader äldre än ett minimidatum och fylla i saknade månader med den förberäknade summan av föregående månad.

Jag uppnår detta nu med 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 med ett minimum av testfall.

Huvudpunkter:

  • Jag tog bort din VIEW från frågan helt. Mycket kostnad utan vinst.

  • Eftersom du väljer en singel item_id , du behöver inte GROUP BY item_id eller PARTITION BY item_id .

  • Använd alias för korta tabeller och gör alla referenser entydiga - speciellt när du skriver i ett offentligt forum.

  • Parenteser i dina sammanfogningar var bara buller. Joins körs ändå från vänster till höger som standard.

  • Förenklade datumgränser (eftersom jag arbetar med tidsstämplar):

    date_trunc('year', current_date)  - interval '540 days'
    date_trunc('month', current_date) - interval '1 day'
    

    motsvarande, men enklare och snabbare än:

    current_date - date_part('day',current_date)::integer - 540
    current_date - date_part('day',current_date)::integer
  • Jag fyller nu i saknade månader efter alla beräkningar med generate_series() samtal per rad.

  • Det måste vara LEFT JOIN LATERAL ... ON TRUE , inte den korta formen av en JOIN LATERAL för att fånga hörnet på den sista raden. Detaljerad förklaring:

Viktig sidoanteckning:

character(22) är en hemsk datatyp för en primärnyckel (eller vilken som helst kolumn). Detaljer:

Helst skulle detta vara en int eller bigint kolumn, eller möjligen en UUID .

Lagrar också pengar som money typ eller integer (representerar Cents) presterar mycket bättre totalt sett.

På lång sikt , kommer prestandan att försämras, eftersom du måste inkludera alla rader från början i din beräkning. Du bör skära av gamla rader och materialisera balansen i on_hold på årsbasis eller något.




  1. Lägg till sekunder i tidsstämpeln

  2. Undantaget "kunde inte hitta drivrutinen" vid migrering i yii2

  3. PHP - Kapslad lista uppdelad i jämna kolumner (fix och uppdateringar)

  4. Bulkinsert från csv i postgres med golang utan att använda for loop