sql >> Base de Datos >  >> RDS >> PostgreSQL

Consulta las últimas N filas relacionadas por fila

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 como NOT 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í