Asumiendo al menos Postgres 9.3.
Índice
Primero, un índice de varias columnas ayudará:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
es un ajuste ligeramente mejor, pero el índice aún se escanearía hacia atrás casi a la misma velocidad sin DESC
.
Asumiendo created_at
se define NOT NULL
, de lo contrario considere DESC NULLS LAST
en el índice y consulta:
- Ordenar PostgreSQL por fechahora asc, ¿null primero?
La última columna id
solo es útil si obtiene un escaneo de solo índice, lo que probablemente no funcionará si agrega muchas filas nuevas constantemente. En este caso, elimine id
del índice.
Consulta más simple (todavía lenta)
Simplifique su consulta, la subselección interna no ayuda:
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;
Debería ser un poco más rápido, pero lento.
Consulta rápida
- Suponiendo que tiene relativamente pocos estaciones y relativamente muchos observaciones por estación.
- Asumiendo también
station_id
id definido comoNOT NULL
.
Ser realmente rápido, necesita el equivalente a un escaneo de índice suelto (no implementado en Postgres, todavía). Respuesta relacionada:
- Optimizar la consulta GROUP BY para recuperar el último registro por usuario
Si tiene una tabla separada de stations
(lo que parece probable), puede emular esto con 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 no tienes una tabla de stations
, lo siguiente mejor sería crear y mantener uno. Posiblemente agregue una referencia de clave externa para hacer cumplir la integridad relacional.
Si esa no es una opción, puede destilar dicha tabla sobre la marcha. Las opciones simples serían:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Pero cualquiera necesitaría un escaneo secuencial y sería lento. Haga que Postgres use el índice anterior (o cualquier índice btree con station_id
como columna principal) con un CTE recursivo :
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
Úselo como reemplazo directo para las stations
tabla en la consulta simple anterior:
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;
Esto aún debería ser más rápido que lo que tenía por órdenes de magnitud .
SQL Fiddle aquí (9.6)
db<>fiddle aquí