Основно решение
Генерирайте пълен списък с месеци и LEFT JOIN
останалото към него:
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);
Свързани отговори с повече обяснения:
- Присъединете се към заявка за преброяване на generate_series в postgres и също така извлечете Null-стойности като "0"
- Най-добрият начин за преброяване на записи по произволни интервали от време в Rails+Postgres
Усъвършенствано решение за вашия случай
Вашето запитване е по-сложно, отколкото разбрах първоначално. Имате нужда от текущата сума за всички редове на избрания елемент, тогава искате да отрежете редове, по-стари от минимална дата, и да попълните липсващите месеци с предварително изчислената сума от предходния месец.
Постигам това сега с 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 с минимален тестов случай.
Основни точки:
-
Премахнах напълно вашия VIEW от заявката. Много разходи за никаква печалба.
-
Тъй като избирате сингъл
item_id
, не е необходимо даGROUP BY item_id
илиPARTITION BY item_id
. -
Използвайте кратки псевдоними на таблици и направете всички препратки недвусмислени - особено когато публикувате в публичен форум.
-
Скобите във вашите съединения бяха просто шум. Съединяванията така или иначе се изпълняват отляво надясно по подразбиране.
-
Опростени граници на датата (тъй като работя с клеймото за време):
date_trunc('year', current_date) - interval '540 days' date_trunc('month', current_date) - interval '1 day'
еквивалентен, но по-прост и по-бърз от:
current_date - date_part('day',current_date)::integer - 540 current_date - date_part('day',current_date)::integer -
Сега попълвам липсващите месеци след всички изчисления с
generate_series()
обаждания на ред. -
Трябва да е
LEFT JOIN LATERAL ... ON TRUE
, а не кратката форма наJOIN LATERAL
за да хване ъгловия корпус на последния ред. Подробно обяснение:
Важни странични бележки:
character(22)
е ужасно тип данни за първичен ключ (или всеки колона). Подробности:
В идеалния случай това би било int
или bigint
колона или евентуално UUID
.
Също така, съхраняване на парични суми като money
тип или integer
(представлява центове) се представя много по-добре като цяло.
В дългосрочен план производителността непременно ще се влоши, тъй като трябва да включите всички редове от самото начало в изчислението си. Трябва да отрежете старите редове и да материализирате баланса на on_hold
на годишна база или нещо подобно.