Tim*_*ane 4 sql postgresql plpgsql common-table-expression window-functions
我想使用窗口函数为每一行确定满足特定条件的先前记录的总数.
一个具体的例子:
clone=# \d test
Table "pg_temp_2.test"
Column | Type | Modifiers
--------+-----------------------------+-----------
id | bigint |
date | timestamp without time zone |
Run Code Online (Sandbox Code Playgroud)
我想知道每个date'之前1小时'内的行数date.
我可以使用窗口功能吗?或者我需要调查CTE吗?
我真的希望能够写出像(不工作)的东西:
SELECT id, date, count(*) OVER (HAVING previous_rows.date >= (date - '1 hour'::interval))
FROM test;
Run Code Online (Sandbox Code Playgroud)
我可以通过加入测试来编写这个,如下所示 - 但这不会扩展到特别大的表.
SELECT a.id, a.date, count(b.*)-1
FROM test a, test b
WHERE (b.date >= a.date - '1 hour'::interval AND b.date < a.date)
GROUP BY 1,2
ORDER BY 2;
Run Code Online (Sandbox Code Playgroud)
这是我可以用递归查询做的事吗?还是定期的CTE?CTE不仅仅是我所知道的很多东西.我有一种感觉,我很快就会去.:)
我认为你不能用简单的查询,CTE和窗口函数来做到这一点 - 它们的帧定义是静态的,但你需要一个动态的框架.
通常,您必须仔细定义窗口的下边框和上边框:以下查询排除当前行并包含下边框.
仍然存在细微差别:该函数包括当前行的先前对等,而相关子查询排除它们......
使用ts而不是保留字date作为列名.
CREATE TEMP TABLE test (
id bigint
,ts timestamp
);
Run Code Online (Sandbox Code Playgroud)
使用CTE,将时间戳聚合到一个数组中,不需要,计数......
虽然正确,但性能会急剧下降,而且只需要一个满满的行.这里有几个性能杀手.见下文.
我接受了罗马的询问并试图简化它:
- 删除第二个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;
Run Code Online (Sandbox Code Playgroud)
您可以使用简单,简单,丑陋的相关子查询来解决此问题.快得多,但还是......
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;
Run Code Online (Sandbox Code Playgroud)
遍历与按时间顺序排row_number()在PLPGSQL功能和结合起来,与一个光标在同一查询,跨越所期望的时间帧.然后我们可以减去行数.应该表现得很好.
CREATE OR REPLACE FUNCTION running_window_ct()
RETURNS TABLE (id bigint, ts timestamp, ct int) AS
$func$
DECLARE
i CONSTANT interval = '1h'; -- given interval for time frame
cur CURSOR FOR
SELECT t.ts + i AS ts1 -- incremented by given interval
, row_number() OVER (ORDER BY t.ts) AS rn
FROM test t
ORDER BY t.ts; -- in chronological order
rec record; -- for current row from cursor
rn int;
BEGIN
OPEN cur; FETCH cur INTO rec; -- open cursor, fetch first row
ct := -1; -- init; -1 covers special case at start
FOR id, ts, rn IN
SELECT t.id, t.ts, row_number() OVER (ORDER BY t.ts)
FROM test t
ORDER BY t.ts -- in same chronological order as cursor
LOOP
IF rec.ts1 >= ts THEN -- still in range ...
ct := ct + 1; -- ... just increment
ELSE -- out of range ...
LOOP -- ... advance cursor
FETCH cur INTO rec;
EXIT WHEN rec.ts1 >= ts; -- earliest row within time frame
END LOOP;
ct := rn - rec.rn; -- new count
END IF;
RETURN NEXT;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
呼叫:
SELECT * FROM running_window_ct();
Run Code Online (Sandbox Code Playgroud)
通过上面的表格,我在旧的测试服务器上运行了一个快速基准测试: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
Run Code Online (Sandbox Code Playgroud)
我为每次跑步改变了大胆的部分,并且使用了最好的5 EXPLAIN ANALYZE.
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毫秒!!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000行
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
该功能是明确的胜利者.它是最快的一个数量级,并且最佳.
阵列处理无法竞争.
| 归档时间: |
|
| 查看次数: |
2584 次 |
| 最近记录: |