sql >> Databasteknik >  >> RDS >> PostgreSQL

Fråga de sista N relaterade raderna per rad

Förutsatt minst Postgres 9.3.

Index

Först kommer ett index med flera kolumner att hjälpa:

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

created_at DESC passar något bättre, men indexet skulle fortfarande skannas bakåt i nästan samma hastighet utan DESC .

Förutsatt created_at är definierad NOT NULL , annars överväga DESC NULLS LAST i index och fråga:

  • PostgreSQL sortera efter datetime asc, null först?

Den sista kolumnen id är bara användbart om du får en index-skanning av den, vilket förmodligen inte kommer att fungera om du lägger till många nya rader hela tiden. Ta i så fall bort id från indexet.

Enklare fråga (fortfarande långsam)

Förenkla din fråga, det inre undervalet hjälper inte:

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;

Borde vara lite snabbare, men ändå långsamt.

Snabb fråga

  • Förutsatt att du har relativt stationer och relativt många observationer per station.
  • Antar även station_id id definierat som NOT NULL .

Att vara på riktigt snabbt, du behöver motsvarande en lös indexskanning (inte implementerat i Postgres, ännu). Relaterat svar:

  • Optimera GROUP BY-frågan för att hämta senaste posten per användare

Om du har en separat tabell över stations (vilket verkar troligt), du kan emulera detta med 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;

Om du inte har en tabell över stations , det näst bästa vore att skapa och underhålla en. Lägg eventuellt till en främmande nyckelreferens för att upprätthålla relationsintegritet.

Om det inte är ett alternativ kan du destillera ett sådant bord i farten. Enkla alternativ skulle vara:

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

Men båda skulle behöva en sekventiell genomsökning och vara långsam. Låt Postgres använda ovanstående index (eller vilket btree-index som helst med station_id som ledande kolumn) med en rekursiv CTE :

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

Använd det som drop-in-ersättning för stations tabell i ovanstående enkla fråga:

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;

Detta borde fortfarande vara snabbare än vad du hade i storleksordningar .

SQL Fiddle här (9.6)
db<>fiol här



  1. 'IF' i 'SELECT'-satsen - välj utdatavärde baserat på kolumnvärden

  2. Hur man kontrollerar om en tabell finns i ett givet schema

  3. VÄLJ * FRÅN flera tabeller. MySQL

  4. Trunc datumfält i mysql som Oracle