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 commeNOT 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