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:
- Únase a una consulta de conteo en un generate_series en postgres y también recupere valores nulos como "0"
- La mejor manera de contar registros por intervalos de tiempo arbitrarios en Rails+Postgres
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 necesitaGROUP BY item_id
oPARTITION 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 deJOIN 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í.