मुझे नहीं लगता कि आप इसे सादे क्वेरी, सीटीई और विंडो फ़ंक्शंस के साथ सस्ते में कर सकते हैं - उनकी फ्रेम परिभाषा स्थिर है, लेकिन आपको एक डायनेमिक फ्रेम की आवश्यकता है (स्तंभ मानों के आधार पर)।
आम तौर पर, आपको अपनी विंडो की निचली और ऊपरी सीमा को सावधानीपूर्वक परिभाषित करना होगा:निम्नलिखित क्वेरी बहिष्कृत वर्तमान पंक्ति और शामिल करें निचली सीमा।
अभी भी एक मामूली अंतर है:फ़ंक्शन में वर्तमान पंक्ति के पिछले समकक्ष शामिल हैं, जबकि सहसंबद्ध उपश्रेणी उन्हें बाहर करती है ...
टेस्ट केस
ts
. का उपयोग करना आरक्षित शब्द के बजाय date
स्तंभ नाम के रूप में।
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - रोमन की क्वेरी
CTE का उपयोग करें, टाइमस्टैम्प को एक सरणी में एकत्रित करें, अननेस्ट करें, गिनें ...
सही होने पर, प्रदर्शन बेहद खराब हो जाता है पंक्तियों से भरे एक हाथ से अधिक के साथ। यहां कुछ प्रदर्शन हत्यारे हैं। नीचे देखें।
ARR - सरणी तत्वों की गणना करें
मैंने रोमन की क्वेरी ली और इसे थोड़ा सरल बनाने की कोशिश की:
- दूसरा सीटीई हटाएं जो जरूरी नहीं है।
- पहले सीटीई को सबक्वेरी में रूपांतरित करें, जो तेज है।
- प्रत्यक्ष
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$;
एक घंटे के डिफ़ॉल्ट अंतराल के साथ कॉल करें:
SELECT * FROM running_window_ct();
या किसी भी अंतराल के साथ:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>फिडल यहाँ
पुराना sqlfiddle
बेंचमार्क
ऊपर से तालिका के साथ मैंने अपने पुराने परीक्षण सर्वर पर एक त्वरित बेंचमार्क चलाया:(डेबियन पर पोस्टग्रेएसक्यूएल 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 ms
ARR:7.834 ms
COR:5.488 ms
FNC:1.115 ms
1000 पंक्तियां
ROM:2116.029 ms
ARR:189.679 ms
COR:65.802 ms
FNC:8.466 ms
5000 पंक्तियां
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000 पंक्तियां
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
समारोह स्पष्ट विजेता है। यह परिमाण और पैमानों के क्रम से सबसे तेज़ है।
सरणी प्रबंधन प्रतिस्पर्धा नहीं कर सकता।