Para solo 400 estaciones, esta consulta será masivamente más rápida:
SELECT s.station_id, l.submitted_at, l.level_sensor
FROM station s
CROSS JOIN LATERAL (
SELECT submitted_at, level_sensor
FROM station_logs
WHERE station_id = s.station_id
ORDER BY submitted_at DESC NULLS LAST
LIMIT 1
) l;
dbfiddle aquí
(comparando planes para esta consulta, la alternativa de Abelisto y su original)
Resultando EXPLAIN ANALYZE
según lo dispuesto por el OP:
Bucle anidado (costo = 0.56..356.65 filas = 102 ancho = 20) (tiempo real = 0.034..0.979 filas = 98 bucles = 1)
-> Seq Scan en estaciones s (costo = 0.00..3.02 filas = 102 ancho = 4) (tiempo real = 0.009..0.016 filas = 102 bucles = 1)
-> Límite (costo = 0.56..3.45 filas = 1 ancho = 16) (tiempo real = 0.009..0.009 filas = 1 bucles = 102)
-> Escaneo de índice usando station_id__submitted_at en station_logs (costo = 0.56..664062.38 filas = 230223 ancho = 16) (tiempo real = 0.009 $
Índice Cond: (station_id = s.id)
Tiempo de planificación: 0.542 ms
Tiempo de ejecución: 1.013 ms - !!
El único índice que necesita es la que ha creado: station_id__submitted_at
. La UNIQUE
restricción uniq_sid_sat
también hace el trabajo, básicamente. Mantener ambos parece una pérdida de espacio en disco y rendimiento de escritura.
Agregué NULLS LAST
a ORDER BY
en la consulta porque submitted_at
no está definido NOT NULL
. Idealmente, si corresponde, agregue una NOT NULL
restricción a la columna submitted_at
, suelte el índice adicional y elimínelo NULLS LAST
de la consulta.
Si submitted_at
puede NULL
, cree este UNIQUE
índice para reemplazar tanto su índice actual como su restricción única:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Considerar:
Esto supone una tabla separadastation
con una fila por relevante station_id
(generalmente el PK), que debería tener en cualquier caso. Si no lo tiene, créelo. Nuevamente, muy rápido con esta técnica rCTE:
CREATE TABLE station AS
WITH RECURSIVE cte AS (
(
SELECT station_id
FROM station_logs
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT l.station_id
FROM cte c
, LATERAL (
SELECT station_id
FROM station_logs
WHERE station_id > c.station_id
ORDER BY station_id
LIMIT 1
) l
)
TABLE cte;
Lo uso en el violín también. Puede utilizar una consulta similar para resolver su tarea directamente, sin station
tabla, si no puede convencerse de crearla.
Instrucciones detalladas, explicación y alternativas:
Optimizar índice
Su consulta debería ser muy rápida ahora. Solo si aún necesita optimizar el rendimiento de lectura ...
Puede tener sentido agregar level_sensor
como última columna al índice para permitir escaneos de solo índice , como comentó joanolo .
Con: hace que el índice sea más grande, lo que agrega un pequeño costo a todas las consultas que lo usan.
Pro: si realmente obtiene escaneos de índice, la consulta en cuestión no tiene que visitar páginas de montón, lo que hace que sea aproximadamente el doble de rápido. Pero eso puede ser una ganancia insustancial para la consulta muy rápida ahora.
Sin embargo , no espero que eso funcione para su caso. Mencionaste:
... alrededor de 20k filas por día por station_id
.
Típicamente, eso indicaría una carga de escritura incesante (1 por station_id
cada 5 segundos). Y estás interesado en la última fila. Los escaneos de solo índice solo funcionan para páginas de montón que son visibles para todas las transacciones (se establece el bit en el mapa de visibilidad). Tendría que ejecutar VACUUM
configuraciones extremadamente agresivas para que la tabla se mantuviera al día con la carga de escritura, y aún así no funcionaría la mayor parte del tiempo. Si mis suposiciones son correctas, los escaneos de solo índice están fuera, no agregue level_sensor
al índice.
OTOH, si mis suposiciones se mantienen y su tabla está creciendo muy grande , un índice BRIN podría ayudar. Relacionado:
O, incluso más especializado y más eficiente: un índice parcial de solo las últimas incorporaciones para cortar la mayor parte de las filas irrelevantes:
CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';
Elija una marca de tiempo para la que sepa que deben existir filas más jóvenes. Debe agregar una WHERE
condición coincidente a todas las consultas, como:
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
Debe adaptar el índice y la consulta de vez en cuando.
Respuestas relacionadas con más detalles: