Para apenas 400 estações, essa consulta será massivamente mais 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 aqui
(comparando planos para esta consulta, a alternativa de Abelisto e o seu original)
Resultante EXPLAIN ANALYZE
conforme fornecido pelo OP:
Loop aninhado (custo = 0,56..356,65 linhas = 102 largura = 20) (tempo real = 0,034..0,979 linhas = 98 loops = 1)
-> Varredura Seq nas estações s (custo = 0,00..3,02 linhas = 102 largura = 4) (tempo real = 0,009..0,016 linhas = 102 loops = 1)
-> Limite (custo = 0,56..3,45 linhas = 1 largura = 16) (tempo real = 0,009..0,009 linhas = 1 loops = 102)
-> Varredura de índice usando station_id__submitted_at em station_logs (custo = 0,56..664062,38 linhas = 230223 largura = 16) (tempo real = 0,009 $
Cond do índice: (station_id = s.id)
Tempo de planejamento: 0,542 ms
Tempo de execução: 1,013 ms - !!
O único índice que você precisa é o que você criou: station_id__submitted_at
. A UNIQUE
restrição uniq_sid_sat
também faz o trabalho, basicamente. Manter os dois parece um desperdício de espaço em disco e desempenho de gravação.
Eu adicionei NULLS LAST
a ORDER BY
na consulta porque submitted_at
não está definida NOT NULL
. Idealmente, se aplicável !, adicione uma NOT NULL
restrição à coluna submitted_at
, descarte o índice adicional e remova NULLS LAST
da consulta.
Se submitted_at
possível NULL
, crie esse UNIQUE
índice para substituir o índice atual e a restrição exclusiva:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Considerar:
Isso pressupõe uma tabela separadastation
com uma linha por relevante station_id
(normalmente a PK) - que você deve ter de qualquer maneira. Se você não tiver, crie-o. Novamente, muito rápido com 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;
Eu uso isso no violino também. Você pode usar uma consulta semelhante para resolver sua tarefa diretamente, sem station
tabela - se não estiver convencido de criá-la.
Instruções detalhadas, explicações e alternativas:
Otimizar índice
Sua consulta deve ser muito rápida agora. Somente se você ainda precisar otimizar o desempenho de leitura ...
Pode fazer sentido adicionar a level_sensor
última coluna ao índice para permitir verificações apenas de índice , como joanolo comentou .
Contras: Aumenta o índice - o que adiciona um pequeno custo a todas as consultas que o utilizam.
Pro: se você realmente obtiver apenas verificações de índice, a consulta em questão não precisará visitar páginas de heap, o que o torna duas vezes mais rápido. Mas isso pode ser um ganho insubstancial para a consulta muito rápida agora.
No entanto , não espero que isso funcione para o seu caso. Você mencionou:
... cerca de 20 mil linhas por dia por dia station_id
.
Normalmente, isso indicaria carga de gravação incessante (1 a station_id
cada 5 segundos). E você está interessado na última linha. As verificações apenas de índice funcionam apenas para páginas de heap visíveis a todas as transações (o bit no mapa de visibilidade está definido). Você precisaria executar VACUUM
configurações extremamente agressivas para que a tabela acompanhasse o carregamento de gravação e ainda assim não funcionaria na maioria das vezes. Se minhas suposições estiverem corretas, as varreduras somente de índice estiverem fora, não adicione level_sensor
ao índice.
OTOH, se minhas suposições forem válidas e sua tabela estiver crescendo muito , um índice BRIN pode ajudar. Relacionado:
Ou, ainda mais especializado e mais eficiente: um índice parcial para apenas as mais recentes adições para cortar a maior parte das linhas 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';
Escolha um carimbo de data e hora para o qual você sabe que linhas mais jovens devem existir. Você deve adicionar uma WHERE
condição correspondente a todas as consultas, como:
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
Você precisa adaptar o índice e a consulta periodicamente.
Respostas relacionadas com mais detalhes: