PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Fensterfunktionen oder allgemeine Tabellenausdrücke:vorherige Zeilen innerhalb des Bereichs zählen

Ich glaube nicht, dass Sie dies mit einer einfachen Abfrage, CTEs und Fensterfunktionen billig tun können - ihre Rahmendefinition ist statisch, aber Sie benötigen einen dynamischen Rahmen (abhängig von Spaltenwerten).

Im Allgemeinen müssen Sie die Unter- und Obergrenze Ihres Fensters sorgfältig definieren:Die folgenden Abfragen schließen aus die aktuelle Zeile und include unteren Rand.
Es gibt noch einen kleinen Unterschied:Die Funktion schließt vorherige Peers der aktuellen Zeile ein, während die korrelierte Unterabfrage sie ausschließt ...

Testfall

Verwenden von ts anstelle des reservierten Wortes date als Spaltenname.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);

ROM - Romans Abfrage

Verwenden Sie CTEs, aggregieren Sie Zeitstempel in einem Array, entschachteln Sie, zählen Sie ...
Obwohl das richtig ist, verschlechtert sich die Leistung drastisch mit mehr als einer Hand voll Reihen. Hier gibt es ein paar Performance-Killer. Siehe unten.

ARR - Array-Elemente zählen

Ich habe Romans Anfrage genommen und versucht, sie ein wenig zu straffen:

  • Entfernen Sie den 2. CTE, der nicht erforderlich ist.
  • Ersten CTE in Unterabfrage umwandeln, was schneller ist.
  • Direkte count() anstatt erneut in ein Array zu aggregieren und mit array_length() zu zählen .

Aber die Handhabung von Arrays ist teuer, und die Leistung lässt immer noch stark nach mit mehr Zeilen.

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 - korrelierte Unterabfrage

Sie könnten lösen Sie es mit einer einfachen korrelierten Unterabfrage. Viel schneller, aber immer noch ...

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

Durchlaufen Sie die Zeilen in chronologischer Reihenfolge mit einem row_number() in der plpgsql-Funktion und kombiniere das mit einem Cursor über dieselbe Abfrage, die den gewünschten Zeitrahmen überspannt. Dann können wir einfach Zeilennummern subtrahieren:

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

Aufruf mit Standardintervall von einer Stunde:

SELECT * FROM running_window_ct();

Oder mit beliebigem Intervall:

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

db<>hier fummeln
Altes sqlfiddle

Benchmark

Mit der obigen Tabelle habe ich einen schnellen Benchmark auf meinem alten Testserver durchgeführt:(PostgreSQL 9.1.9 auf 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

Ich habe das Fett variiert nahm an jedem Lauf teil und gewann mit EXPLAIN ANALYZE das Beste aus 5 .

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

1000 Zeilen
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms

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

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

Die Funktion ist der klare Sieger. Es ist um eine Größenordnung am schnellsten und lässt sich am besten skalieren.
Array-Handling kann da nicht mithalten.