sql >> Database >  >> RDS >> PostgreSQL

Vensterfuncties of algemene tabeluitdrukkingen:tel vorige rijen binnen bereik

Ik denk niet dat je dit goedkoop kunt doen met een simpele query, CTE's en vensterfuncties - hun framedefinitie is statisch, maar je hebt een dynamisch frame nodig (afhankelijk van kolomwaarden).

Over het algemeen moet u de onder- en bovengrens van uw venster zorgvuldig definiëren:De volgende zoekopdrachten uitsluiten de huidige rij en opnemen de onderste rand.
Er is nog een klein verschil:de functie omvat eerdere peers van de huidige rij, terwijl de gecorreleerde subquery ze uitsluit ...

Testcase

Met behulp van ts in plaats van gereserveerd woord date als kolomnaam.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);

ROM - Roman's vraag

Gebruik CTE's, verzamel tijdstempels in een array, verwijder, tel ...
Hoewel correct, prestaties verslechteren drastisch met meer dan een hand vol rijen. Er zijn hier een paar prestatiemoordenaars. Zie hieronder.

ARR - tel array-elementen

Ik nam de vraag van Roman en probeerde het een beetje te stroomlijnen:

  • Verwijder de 2e CTE die niet nodig is.
  • Transformeer de eerste CTE in een subquery, wat sneller is.
  • Directe count() in plaats van opnieuw te aggregeren in een array en te tellen met array_length() .

Maar het hanteren van arrays is duur en de prestaties verslechteren nog steeds erg met meer rijen.

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 - gecorreleerde subquery

Je zou los het op met een eenvoudige gecorreleerde subquery. Een stuk sneller, maar toch ...

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 - Functie

Loop over rijen in chronologische volgorde met een row_number() in plpgsql-functie en combineer dat met een cursor over dezelfde query, verspreid over het gewenste tijdsbestek. Dan kunnen we rijnummers gewoon aftrekken:

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

Bellen met standaardinterval van één uur:

SELECT * FROM running_window_ct();

Of met een interval:

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

db<>viool hier
Oude sqlfiddle

Benchmark

Met de bovenstaande tabel heb ik een snelle benchmark uitgevoerd op mijn oude testserver:(PostgreSQL 9.1.9 op 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

Ik heb de vet . gevarieerd deel voor elke run en haalde het beste van 5 met EXPLAIN ANALYZE .

100 rijen
ROM:27.656 ms
ARR:7.834 ms
COR:5.488 ms
FNC:1.115 ms

1000 rijen
ROM:2116.029 ms
ARR:189.679 ms
COR:65.802 ms
FNC:8.466 ms

5000 rijen
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms

100000 rijen
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms

De functie is de duidelijke overwinnaar. Het is een orde van grootte het snelst en schaalt het best.
Arrayverwerking kan niet concurreren.



  1. Android pusht updates in Play Store

  2. Een gids voor MySQL-indexen

  3. MySQL Basic Database Administration Commando's - Deel I

  4. De functie Oracle REPLACE() verwerkt geen regelterugloop en regelinvoer