プレーンクエリ、CTE、ウィンドウ関数でこれを安く行うことはできないと思います。これらのフレーム定義は静的ですが、動的フレームが必要です。 (列の値によって異なります)。
通常、ウィンドウの下限と上限を慎重に定義する必要があります。次のクエリは除外します。 現在の行とinclude 下の境界線。
まだ小さな違いがあります。関数には現在の行の以前のピアが含まれますが、相関サブクエリではそれらが除外されます...
テストケース
ts
の使用 予約語の代わりにdate
列名として。
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM-ローマの質問
CTEを使用し、タイムスタンプを配列に集約し、ネストを解除し、カウントします...
正しい一方で、パフォーマンスは大幅に低下します 行でいっぱいの手以上で。ここにはパフォーマンスキラーが2人います。以下を参照してください。
ARR-配列要素をカウント
私はRomanのクエリを受け取り、それを少し合理化しようとしました:
- 不要な2番目のCTEを削除します。
- 最初のCTEをサブクエリに変換します。これはより高速です。
- 直接
count()
配列に再集計してarray_length()
でカウントする代わりに 。
ただし、配列の処理にはコストがかかり、パフォーマンスは依然として大幅に低下します より多くの行があります。
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-相関サブクエリ
あなたはできた 単純な相関サブクエリでそれを解決します。はるかに高速ですが、それでも...
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-機能
row_number()
を使用して、行を時系列でループします plpgsql関数で それをカーソルと組み合わせます 同じクエリで、目的の時間枠にまたがります。次に、行番号を引くだけです。
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$;
デフォルトの間隔1時間で電話をかける:
SELECT * FROM running_window_ct();
または任意の間隔で:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>ここでフィドル
古いsqlfiddle
ベンチマーク
上記の表を使用して、古いテストサーバーで簡単なベンチマークを実行しました:(Debian上のPostgreSQL 9.1.9)。
-- 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
太字を変更しました 各実行の一部であり、EXPLAIN ANALYZE
で5つのうちのベストを取りました 。
100行
ROM:27.656ミリ秒
ARR:7.834ミリ秒
COR:5.488ミリ秒
FNC:1.115ミリ秒
1000行
ROM:2116.029ミリ秒
ARR:189.679ミリ秒
COR:65.802ミリ秒
FNC:8.466ミリ秒
5000行
ROM:51347ミリ秒!!
ARR:3167ミリ秒
COR:333ミリ秒
FNC:42ミリ秒
100000行
ROM:DNF
ARR:DNF
COR:6760ミリ秒
FNC:828ミリ秒
機能は明確な勝利者です。桁違いに高速で、最適なスケーリングが可能です。
配列処理は競合できません。