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

Vinduesfunktioner eller almindelige tabeludtryk:tæl tidligere rækker inden for rækkevidde

Jeg tror ikke, du kan gøre dette billigt med en almindelig forespørgsel, CTE'er og vinduesfunktioner - deres rammedefinition er statisk, men du har brug for en dynamisk ramme (afhængigt af kolonneværdier).

Generelt skal du omhyggeligt definere nedre og øvre grænse for dit vindue:Følgende forespørgsler ekskluderer den aktuelle række og inkluder den nederste kant.
Der er stadig en mindre forskel:Funktionen inkluderer tidligere peers i den aktuelle række, mens den korrelerede underforespørgsel udelukker dem ...

Testcase

Brug af ts i stedet for reserveret ord date som kolonnenavn.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);
 

ROM - Romans forespørgsel

Brug CTE'er, samle tidsstempler i et array, unnest, tæl ...
Selvom det er korrekt, forringes ydeevnen drastisk med mere end en hånd fuld af rækker. Der er et par præstationsdræbere her. Se nedenfor.

ARR - tæl array-elementer

Jeg tog Romans forespørgsel og prøvede at strømline den lidt:

  • Fjern 2. CTE, som ikke er nødvendigt.
  • Omdan 1. CTE til underforespørgsel, hvilket er hurtigere.
  • Direkte count() i stedet for at re-aggregere til en matrix og tælle med array_length() .

Men array-håndtering er dyrt, og ydeevnen forringes stadig dårligt med flere rækker.

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 - korreleret underforespørgsel

Du kunne løse det med en simpel korreleret underforespørgsel. Meget hurtigere, men alligevel ...

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

Loop over rækker i kronologisk rækkefølge med en row_number() i plpgsql-funktionen og kombiner det med en markør over den samme forespørgsel, der strækker sig over den ønskede tidsramme. Så kan vi bare trække rækkenumre fra:

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$;
 

Opkald med standardinterval på en time:

SELECT * FROM running_window_ct();
 

Eller med et hvilket som helst interval:

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

db<>spil her
Gamle sqlfiddle

Benchmark

Med tabellen ovenfra kørte jeg et hurtigt benchmark på min gamle 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

Jeg varierede fed del for hvert løb og tog det bedste af 5 med EXPLAIN ANALYZE .

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

1000 rækker
ROM:2116.029 ms
ARR:189.679 ms
COR:65.802 ms
FNC:8.466 ms

5000 rækker
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms

100.000 rækker
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms

Funktionen er den klare sejrherre. Den er hurtigst i en størrelsesorden og skalerer bedst.
Arrayhåndtering kan ikke konkurrere.



  1. Gyldige tidsstrengformater for SQLite dato/tidsfunktioner

  2. Hvorfor bruge Vælg Top 100 procent?

  3. Sådan krypteres data i Oracle ved hjælp af PL SQL

  4. Konfigurationssystemet kunne ikke initialiseres