sql >> Databasteknik >  >> RDS >> PostgreSQL

Fönsterfunktioner eller vanliga tabelluttryck:räkna tidigare rader inom intervallet

Jag tror inte att du kan göra detta billigt med en vanlig fråga, CTE:er och fönsterfunktioner - deras ramdefinition är statisk, men du behöver en dynamisk ram (beroende på kolumnvärden).

I allmänhet måste du definiera den nedre och övre gränsen för ditt fönster noggrant:Följande frågor utesluter den aktuella raden och inkludera den nedre gränsen.
Det finns fortfarande en mindre skillnad:funktionen inkluderar tidigare peers i den aktuella raden, medan den korrelerade underfrågan utesluter dem ...

Testfall

Använder ts istället för reserverat ord date som kolumnnamn.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);

ROM - Romans fråga

Använd CTE:er, samla tidsstämplar till en array, unnest, räkna ...
Även om det är korrekt, försämras prestanda drastiskt med mer än en hand full av rader. Det finns ett par prestationsdödare här. Se nedan.

ARR - räkna arrayelement

Jag tog Romans fråga och försökte effektivisera den lite:

  • Ta bort 2:a CTE som inte är nödvändigt.
  • Omvandla 1:a CTE till underfråga, vilket är snabbare.
  • Direkt count() istället för att återaggregera till en array och räkna med array_length() .

Men arrayhantering är dyrt, och prestandan försämras fortfarande dåligt med fler rader.

SELECT id, ts
     , (SELECT count(*)::int - 1
        FROM   unnest(dates) x
        WHERE  x >= sub.ts - interval '1h') AS ct
FROM (
   SELECT id, ts
        , array_agg(ts) OVER(ORDER BY ts) AS dates
   FROM   test
   ) sub;

COR - korrelerad underfråga

Du kunde lös det med en enkel korrelerad underfråga. Mycket snabbare, men ändå ...

SELECT id, ts
     , (SELECT count(*)
        FROM   test t1
        WHERE  t1.ts >= t.ts - interval '1h'
        AND    t1.ts < t.ts) AS ct
FROM   test t
ORDER  BY ts;

FNC - Funktion

Slinga över rader i kronologisk ordning med en row_number() i plpgsql-funktionen och kombinera det med en markör över samma fråga och spänner över den önskade tidsramen. Sedan kan vi bara subtrahera radnummer:

CREATE OR REPLACE FUNCTION running_window_ct(_intv interval = '1 hour')
  RETURNS TABLE (id bigint, ts timestamp, ct int)
  LANGUAGE plpgsql AS
$func$
DECLARE
   cur   CURSOR FOR
         SELECT t.ts + _intv AS ts1, row_number() OVER (ORDER BY t.ts) AS rn
         FROM   test t ORDER BY t.ts;
   rec   record;
   rn    int;

BEGIN
   OPEN cur;
   FETCH cur INTO rec;
   ct := -1;  -- init

   FOR id, ts, rn IN
      SELECT t.id, t.ts, row_number() OVER (ORDER BY t.ts)
      FROM   test t ORDER BY t.ts
   LOOP
      IF rec.ts1 >= ts THEN
         ct := ct + 1;
      ELSE
         LOOP
            FETCH cur INTO rec;
            EXIT WHEN rec.ts1 >= ts;
         END LOOP;
         ct := rn - rec.rn;
      END IF;

      RETURN NEXT;
   END LOOP;
END
$func$;

Ring med standardintervall på en timme:

SELECT * FROM running_window_ct();

Eller med valfritt intervall:

SELECT * FROM running_window_ct('2 hour - 3 second');

db<>spela här
Gammal sqlfiddle

Benchmark

Med tabellen ovanifrån körde jag en snabb benchmark på min gamla testserver:(PostgreSQL 9.1.9 på Debian).

-- TRUNCATE test;
INSERT INTO test
SELECT g, '2013-08-08'::timestamp
         + g * interval '5 min'
         + random() * 300 * interval '1 min' -- halfway realistic values
FROM   generate_series(1, 10000) g;

CREATE INDEX test_ts_idx ON test (ts);
ANALYZE test;  -- temp table needs manual analyze

Jag varierade fet del för varje körning och tog det bästa av 5 med EXPLAIN ANALYZE .

100 rader
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms

1 000 rader
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms

5 000 rader
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms

100 000 rader
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms

Funktionen är den klara segraren. Den är snabbast i en storleksordning och skalar bäst.
Arrayhantering kan inte konkurrera.



  1. TransactSQL för att köra ett annat TransactSQL-skript

  2. MariaDB SQL Set Operators

  3. Hur kan flera rader sammanfogas till en i Oracle utan att skapa en lagrad procedur?

  4. Kopiera en tabell (inklusive index) i postgres