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:
- Unisciti a una query di conteggio su una generate_series in postgres e recupera anche valori Null come "0"
- Il modo migliore per contare i record in base a intervalli di tempo arbitrari in Rails+Postgres
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 è necessarioGROUP BY item_id
oPARTITION 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 unJOIN 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.