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

Interroger les N dernières lignes associées par ligne

En supposant au moins Postgres 9.3.

Index

Tout d'abord, un index multicolonne vous aidera :

CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)

created_at DESC est un peu mieux ajusté, mais l'index serait toujours balayé vers l'arrière à peu près à la même vitesse sans DESC .

En supposant created_at est défini NOT NULL , sinon considérez DESC NULLS LAST dans l'index et requête :

  • PostgreSQL trier par datetime asc, null en premier ?

La dernière colonne id n'est utile que si vous obtenez une analyse d'index uniquement, ce qui ne fonctionnera probablement pas si vous ajoutez constamment de nombreuses nouvelles lignes. Dans ce cas, supprimez id de l'index.

Requête plus simple (toujours lente)

Simplifiez votre requête, la sous-sélection interne n'aide pas :

SELECT id
FROM  (
  SELECT station_id, id, created_at
       , row_number() OVER (PARTITION BY station_id
                            ORDER BY created_at DESC) AS rn
  FROM   observations
  ) s
WHERE  rn <= #{n}  -- your limit here
ORDER  BY station_id, created_at DESC;

Devrait être un peu plus rapide, mais toujours lent.

Requête rapide

  • En supposant que vous en ayez relativement peu gares et relativement beaucoup observations par station.
  • En supposant également station_id id défini comme NOT NULL .

Être vraiment rapide, vous avez besoin de l'équivalent d'une analyse d'index lâche (pas encore implémenté dans Postgres). Réponse connexe :

  • Optimiser la requête GROUP BY pour récupérer le dernier enregistrement par utilisateur

Si vous avez un tableau séparé de stations (ce qui semble probable), vous pouvez émuler cela avec JOIN LATERAL (Postgres 9.3+) :

SELECT o.id
FROM   stations s
CROSS  JOIN LATERAL (
   SELECT o.id
   FROM   observations o
   WHERE  o.station_id = s.station_id  -- lateral reference
   ORDER  BY o.created_at DESC
   LIMIT  #{n}  -- your limit here
   ) o
ORDER  BY s.station_id, o.created_at DESC;

Si vous n'avez pas de tableau des stations , la meilleure chose à faire serait d'en créer un et de le maintenir. Ajoutez éventuellement une référence de clé étrangère pour renforcer l'intégrité relationnelle.

Si ce n'est pas une option, vous pouvez distiller un tel tableau à la volée. Des options simples seraient :

SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;

Mais l'un ou l'autre aurait besoin d'un scan séquentiel et serait lent. Faites en sorte que Postgres utilise l'index ci-dessus (ou tout index btree avec station_id comme colonne de tête) avec un CTE récursif :

WITH RECURSIVE stations AS (
   (                  -- extra pair of parentheses ...
   SELECT station_id
   FROM   observations
   ORDER  BY station_id
   LIMIT  1
   )                  -- ... is required!
   UNION ALL
   SELECT (SELECT o.station_id
           FROM   observations o
           WHERE  o.station_id > s.station_id
           ORDER  BY o.station_id
           LIMIT  1)
   FROM   stations s
   WHERE  s.station_id IS NOT NULL  -- serves as break condition
   )
SELECT station_id
FROM   stations
WHERE  station_id IS NOT NULL;      -- remove dangling row with NULL

Utilisez-le comme remplacement immédiat pour les stations table dans la simple requête ci-dessus :

WITH RECURSIVE stations AS (
   (
   SELECT station_id
   FROM   observations
   ORDER  BY station_id
   LIMIT  1
   )
   UNION ALL
   SELECT (SELECT o.station_id
           FROM   observations o
           WHERE  o.station_id > s.station_id
           ORDER  BY o.station_id
           LIMIT  1)
   FROM   stations s
   WHERE  s.station_id IS NOT NULL
   )
SELECT o.id
FROM   stations s
CROSS  JOIN LATERAL (
   SELECT o.id, o.created_at
   FROM   observations o
   WHERE  o.station_id = s.station_id
   ORDER  BY o.created_at DESC
   LIMIT  #{n}  -- your limit here
   ) o
WHERE  s.station_id IS NOT NULL
ORDER  BY s.station_id, o.created_at DESC;

Cela devrait toujours être plus rapide que ce que vous aviez par ordres de grandeur .

SQL Fiddle ici (9.6)
db<>fiddle ici