Solução básica
Gere uma lista completa de meses e
LEFT JOIN
o resto para ele: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);
Respostas relacionadas com mais explicações:
- Junte-se a uma consulta de contagem em um generate_series no postgres e também recupere valores nulos como "0"
- Melhor maneira de contar registros por intervalos de tempo arbitrários em Rails+Postgres
Solução avançada para o seu caso
Sua consulta é mais complicada do que eu entendi. Você precisa da soma corrente sobre todos linhas do item selecionado, você deseja aparar as linhas anteriores a uma data mínima e preencher os meses ausentes com a soma pré-calculada do mês anterior.
Eu consigo isso agora com
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 com um caso de teste mínimo.
Pontos principais:
-
Eu removi sua VIEW da consulta completamente. Muito custo para nenhum ganho.
-
Como você seleciona um únicoitem_id
, você não precisaGROUP BY item_id
ouPARTITION BY item_id
.
-
Use aliases de tabela curtos e torne todas as referências inequívocas - especialmente ao postar em um fórum público.
-
Parênteses em suas junções eram apenas ruído. As junções são executadas da esquerda para a direita por padrão.
-
Limites de data simplificados (já que opero com timestamps):
date_trunc('year', current_date) - interval '540 days' date_trunc('month', current_date) - interval '1 day'
equivalente, mas mais simples e mais rápido que:
current_date - date_part('day',current_date)::integer - 540 current_date - date_part('day',current_date)::integer -
Agora preencho os meses que faltam após todos os cálculos comgenerate_series()
chamadas por linha.
-
Deve serLEFT JOIN LATERAL ... ON TRUE
, não a forma abreviada de umJOIN LATERAL
para pegar a caixa de canto da última linha. Explicação detalhada:
Observações importantes:
character(22)
é um terrível tipo de dados para uma chave primária (ou qualquer coluna). Detalhes:Idealmente, isso seria um
int
ou bigint
coluna, ou possivelmente um UUID
. Além disso, armazenar valores em dinheiro como
money
tipo ou integer
(representando Cents) tem um desempenho muito melhor no geral. A longo prazo , o desempenho tende a se deteriorar, pois você precisa incluir todas as linhas desde o início em seu cálculo. Você deve cortar linhas antigas e materializar o saldo de
on_hold
anualmente ou algo assim.