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 mitarray_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.