Grundlæggende løsning
Generer en komplet liste over måneder og LEFT JOIN
resten til 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);
Relaterede svar med mere forklaring:
- Deltag i en optællingsforespørgsel på en generate_series i postgres og hent også Null-værdier som "0"
- Bedste måde at tælle poster efter vilkårlige tidsintervaller i Rails+Postgres
Avanceret løsning til dit tilfælde
Din forespørgsel er mere kompliceret, end jeg først forstod. Du skal bruge den løbende sum over alle rækker af det valgte element, så vil du trimme rækker, der er ældre end en minimumsdato, og udfylde manglende måneder med den forudberegnede sum af den foregående måned.
Jeg opnår dette 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 et minimum af testtilfælde.
Vigtigste punkter:
-
Jeg fjernede din VIEW fra forespørgslen fuldstændigt. Mange omkostninger uden gevinst.
-
Da du vælger en enkelt
item_id
, du behøver ikke atGROUP BY item_id
ellerPARTITION BY item_id
. -
Brug short table aliaser og gør al reference utvetydig - især når du poster i et offentligt forum.
-
Parenteser i dine joins var bare støj. Joins udføres alligevel fra venstre mod højre som standard.
-
Forenklede datogrænser (da jeg arbejder med tidsstempler):
date_trunc('year', current_date) - interval '540 days' date_trunc('month', current_date) - interval '1 day'
tilsvarende, men enklere og hurtigere end:
current_date - date_part('day',current_date)::integer - 540 current_date - date_part('day',current_date)::integer -
Jeg udfylder nu manglende måneder efter alle beregninger med
generate_series()
opkald pr. række. -
Det skal være
LEFT JOIN LATERAL ... ON TRUE
, ikke den korte form af enJOIN LATERAL
for at fange hjørnekassen i den sidste række. Detaljeret forklaring:
Vigtige sidebemærkninger:
character(22)
er en forfærdelig datatype for en primær nøgle (eller en hvilken som helst). kolonne). Detaljer:
Ideelt set ville dette være en int
eller bigint
kolonne eller muligvis en UUID
.
Også lagring af pengebeløb som money
type eller integer
(repræsenterer Cents) klarer sig generelt meget bedre.
I det lange løb , er ydeevnen forringet, da du skal inkludere alle rækker helt fra begyndelsen i din beregning. Du bør afskære gamle rækker og materialisere saldoen i on_hold
på årsbasis eller noget.