PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Selecionando soma e saldo em execução nos últimos 18 meses com generate_series

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:

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 único item_id , você não precisa GROUP BY item_id ou PARTITION 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 com generate_series() chamadas por linha.

  • Deve ser LEFT JOIN LATERAL ... ON TRUE , não a forma abreviada de um JOIN 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.