PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Agrégation des enregistrements joints les plus récents par semaine

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é.

SQL Fiddle.

Connexe :