Necesita un elemento de datos por semana y objetivo (antes de agregar recuentos por empresa). Eso es un simple CROSS JOIN
entre generate_series()
y goals
. La parte (posiblemente) costosa es obtener el state
actual de updates
para cada. Me gusta @Paul ya sugirió
, un LATERAL
join parece la mejor herramienta. Hazlo solo para updates
, sin embargo, y use una técnica más rápida con LIMIT 1
.
Y simplifique el manejo de fechas con date_trunc()
.
SELECT w_start
, g.company_id
, count(*) FILTER (WHERE u.status = 'green') AS green_count
, count(*) FILTER (WHERE u.status = 'amber') AS amber_count
, count(*) FILTER (WHERE u.status = 'red') AS red_count
FROM generate_series(date_trunc('week', NOW() - interval '2 months')
, date_trunc('week', NOW())
, interval '1 week') w_start
CROSS JOIN goals g
LEFT JOIN LATERAL (
SELECT status
FROM updates
WHERE goal_id = g.id
AND created_at < w_start
ORDER BY created_at DESC
LIMIT 1
) u ON true
GROUP BY w_start, g.company_id
ORDER BY w_start, g.company_id;
Para hacer esto rápido necesitas un índice de varias columnas :
CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);
Orden descendente para created_at
es lo mejor, pero no estrictamente necesario. Postgres puede escanear índices hacia atrás casi exactamente igual de rápido. ( Sin embargo, no se aplica al orden invertido de varias columnas.
)
Índice de columnas en eso ordenar. ¿Por qué?
Y la tercera columna status
solo se agrega para permitir rápidos escaneos de solo índice
en updates
. Caso relacionado:
Los objetivos de 1k durante 9 semanas (su intervalo de 2 meses se superpone con al menos 9 semanas) solo requieren búsquedas de índice de 9k para la segunda tabla de solo 1k filas. Para mesas pequeñas como esta, el rendimiento no debería ser un gran problema. Pero una vez que tenga un par de miles más en cada tabla, el rendimiento se deteriorará con los análisis secuenciales.
w_start
representa el comienzo de cada semana. En consecuencia, los conteos son para el inicio de la semana. Tu puedes aún extraiga el año y la semana (o cualquier otro detalle que represente su semana), si insiste:
EXTRACT(isoyear from w_start) AS year
, EXTRACT(week from w_start) AS week
Mejor con ISOYEAR
, como explicó @Paul.
Relacionado:
- ¿Cuál es la diferencia entre LATERAL y una subconsulta en PostgreSQL?
- Optimizar la consulta GROUP BY para recuperar el último registro por usuario
- Seleccionar primero fila en cada grupo GROUP BY?
- PostgreSQL:conteo continuo de filas para una consulta 'por minuto'