sql >> Base de Datos >  >> RDS >> PostgreSQL

Seleccionar suma y saldo corriente de los últimos 18 meses con generate_series

Solución básica

Genere una lista completa de meses y LEFT JOIN el resto a ello:

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);

Respuestas relacionadas con más explicaciones:

Solución avanzada para su caso

Su consulta es más complicada de lo que entendí al principio. Necesita la suma acumulada sobre todos filas del elemento seleccionado, desea recortar las filas anteriores a una fecha mínima y completar los meses faltantes con la suma precalculada del mes anterior.

Logro esto ahora 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 caso de prueba mínimo.

Puntos principales:

  • Eliminé su VISTA de la consulta por completo. Mucho costo sin ganancia.

  • Dado que selecciona un sencillo item_id , no necesita GROUP BY item_id o PARTITION BY item_id .

  • Use alias de tablas cortas y haga que todas las referencias no sean ambiguas, especialmente cuando publique en un foro público.

  • Los paréntesis en tus uniones eran solo ruido. Las uniones se ejecutan de izquierda a derecha de todos modos de forma predeterminada.

  • Límites de fecha simplificados (ya que opero con marcas de tiempo):

    date_trunc('year', current_date)  - interval '540 days'
    date_trunc('month', current_date) - interval '1 day'
    

    equivalente, pero más simple y más rápido que:

    current_date - date_part('day',current_date)::integer - 540
    current_date - date_part('day',current_date)::integer
  • Ahora completé los meses faltantes después de todos los cálculos con generate_series() llamadas por fila.

  • Debe ser LEFT JOIN LATERAL ... ON TRUE , no la forma abreviada de JOIN LATERAL para atrapar el caso de la esquina de la última fila. Explicación detallada:

Notas importantes al margen:

character(22) es un terrible tipo de datos para una clave principal (o cualquiera columna). Detalles:

Idealmente, esto sería un int o bigint columna, o posiblemente un UUID .

Además, almacenar cantidades de dinero como money tipo o integer (que representa a Centavos) se desempeña mucho mejor en general.

A la larga , es probable que el rendimiento se deteriore, ya que debe incluir todas las filas desde el principio en su cálculo. Debe eliminar las filas antiguas y materializar el saldo de on_hold sobre una base anual o algo así.