Vous avez besoin d'un élément de données par semaine et par objectif (avant d'agréger les décomptes par entreprise). C'est un simple CROSS JOIN
entre generate_series()
et goals
. La partie (éventuellement) coûteuse consiste à obtenir le state
actuel à partir de updates
pour chaque. Comme @Paul déjà suggéré
, un LATERAL
join semble être le meilleur outil. Ne le faites que pour les updates
, cependant, et utilisez une technique plus rapide avec LIMIT 1
.
Et simplifiez la gestion des dates avec 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;
Pour rendre cela rapide vous avez besoin d'un index multicolonne :
CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);
Ordre décroissant pour created_at
est le meilleur, mais pas strictement nécessaire. Postgres peut analyser les index en arrière presque aussi rapidement. ( Cependant, cela ne s'applique pas à l'ordre de tri inversé de plusieurs colonnes.
)
Indexer les colonnes dans cela ordre. Pourquoi ?
Et la troisième colonne status
est uniquement ajouté pour permettre des analyses d'index uniquement
rapides sur les updates
. Cas lié :
1k objectifs pendant 9 semaines (votre intervalle de 2 mois chevauche au moins 9 semaines) ne nécessite que des recherches d'index 9k pour la 2ème table de seulement 1k lignes. Pour les petites tables comme celle-ci, les performances ne devraient pas poser de problème. Mais une fois que vous en avez quelques milliers de plus dans chaque table, les performances se détériorent avec des analyses séquentielles.
w_start
représente le début de chaque semaine. Par conséquent, les comptes sont pour le début de la semaine. Vous pouvez toujours extraire l'année et la semaine (ou tout autre détail représentant votre semaine), si vous insistez :
EXTRACT(isoyear from w_start) AS year
, EXTRACT(week from w_start) AS week
Meilleur avec ISOYEAR
, comme @Paul l'a expliqué.
Connexe :
- Quelle est la différence entre LATERAL et une sous-requête dans PostgreSQL ?
- Optimiser la requête GROUP BY pour récupérer le dernier enregistrement par utilisateur
- Sélectionner d'abord rangée dans chaque groupe GROUP BY ?
- PostgreSQL :nombre de lignes en cours pour une requête 'par minute'