PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Selezione della somma e del saldo corrente per gli ultimi 18 mesi con generate_series

Soluzione di base

Genera un elenco completo di mesi e LEFT JOIN il resto:

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

Risposte correlate con più spiegazioni:

Soluzione avanzata per il tuo caso

La tua domanda è più complicata di quanto avessi capito all'inizio. Hai bisogno della somma corrente su tutti righe dell'elemento selezionato, quindi desideri tagliare le righe precedenti a una data minima e compilare i mesi mancanti con la somma precalcolata del mese precedente.

Ottengo questo ora con 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 con un test case minimo.

Punti principali:

  • Ho rimosso completamente il tuo VIEW dalla query. Tanto costo senza alcun guadagno.

  • Poiché selezioni un single item_id , non è necessario GROUP BY item_id o PARTITION BY item_id .

  • Usa gli alias delle tabelle brevi e rendi inequivocabili tutti i riferimenti, specialmente quando pubblichi post in un forum pubblico.

  • Le parentesi nei tuoi join erano solo rumore. I join vengono eseguiti comunque da sinistra a destra per impostazione predefinita.

  • Limiti di data semplificati (dal momento che lavoro con timestamp):

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

    equivalente, ma più semplice e veloce di:

    current_date - date_part('day',current_date)::integer - 540
    current_date - date_part('day',current_date)::integer
  • Ora riempio i mesi mancanti dopo tutti i calcoli con generate_series() chiamate per riga.

  • Deve essere LEFT JOIN LATERAL ... ON TRUE , non la forma abbreviata di un JOIN LATERAL per prendere il caso d'angolo dell'ultima riga. Spiegazione dettagliata:

Note a margine importanti:

character(22) è un terribile tipo di dati per una chiave primaria (o qualsiasi colonna). Dettagli:

Idealmente questo sarebbe un int o bigint colonna, o eventualmente un UUID .

Inoltre, la memorizzazione di importi di denaro come money digita o integer (che rappresenta i centesimi) si comporta complessivamente molto meglio.

A lungo termine , le prestazioni sono destinate a peggiorare, poiché è necessario includere tutte le righe sin dall'inizio nel calcolo. Dovresti tagliare le vecchie righe e materializzare il saldo di on_hold su base annuale o qualcosa del genere.