PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Interroga le ultime N righe correlate per riga

Supponendo almeno Postgres 9.3.

Indice

Innanzitutto, un indice multicolonna aiuterà:

CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)

created_at DESC si adatta leggermente meglio, ma l'indice verrebbe comunque scansionato all'indietro quasi alla stessa velocità senza DESC .

Assumendo created_at è definito NOT NULL , altrimenti considera DESC NULLS LAST nell'indice e domanda:

  • PostgreSQL ordina per datetime asc, null prima?

L'ultima colonna id è utile solo se ne ottieni una scansione solo indice, che probabilmente non funzionerà se aggiungi costantemente molte nuove righe. In questo caso, rimuovi id dall'indice.

Query più semplice (ancora lenta)

Semplifica la tua query, la sottoselezione interna non aiuta:

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;

Dovrebbe essere un po' più veloce, ma comunque lento.

Richiesta veloce

  • Supponendo che tu ne abbia relativamente pochi stazioni e relativamente molti osservazioni per stazione.
  • Supponendo anche station_id id definito come NOT NULL .

Per essere realmente velocemente, hai bisogno dell'equivalente di una scansione dell'indice allentata (non ancora implementato in Postgres). Risposta correlata:

  • Ottimizza la query GROUP BY per recuperare l'ultimo record per utente

Se hai una tabella separata di stations (cosa che sembra probabile), puoi emularlo 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;

Se non hai una tabella di stations , la cosa migliore da fare sarebbe crearne e mantenerne uno. Eventualmente aggiungere un riferimento a una chiave esterna per rafforzare l'integrità relazionale.

Se questa non è un'opzione, puoi distillare una tabella del genere al volo. Le opzioni semplici sarebbero:

SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;

Ma entrambi avrebbero bisogno di una scansione sequenziale e sarebbero lenti. Fai in modo che Postgres utilizzi l'indice sopra (o qualsiasi indice btree con station_id come colonna principale) con un CTE ricorsivo :

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

Usalo come sostituzione immediata per le stations tabella nella query semplice sopra:

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;

Dovrebbe essere comunque più veloce di quello che avevi per ordini di grandezza .

SQL Fiddle qui (9.6)
db<>Fiddle qui