递归 CTE
使用recursive CTE 的纯 SQL 可以工作:
WITH RECURSIVE cte AS (
SELECT t.dt, m.measure
FROM (SELECT dt FROM measures ORDER BY 1 LIMIT 1) t -- no lower bound
JOIN measures m ON m.dt < t.dt + interval '1h' -- excl. upper bound
UNION ALL
SELECT t.dt, m.measure
FROM (
SELECT m.dt
FROM (SELECT dt FROM cte LIMIT 1) c
JOIN measures m ON m.dt >= c.dt + interval '1h'
ORDER BY 1
LIMIT 1
) t
JOIN measures m ON m.dt >= t.dt -- incl. lower bound
AND m.dt < t.dt + interval '1h' -- excl. upper bound
)
SELECT dt AS hour_start
, round(avg(measure), 2) AS avg_measure, count(*) AS ct
FROM cte
GROUP BY 1
ORDER BY 1;
返回:
hour_start | avg_measure | ct
--------------------+-------------+----
2015-01-13 13:05:00 | 11.33 | 3
2015-01-13 14:30:00 | 9.50 | 2
dbfiddle here(在带有索引和选定时间范围的大表上添加了测试)
旧 sqlfiddle
它在 dt 上的索引表现不错 - 或者更好的是 multicolumn index 以允许在 Postgres 9.2+ 中使用 index-only scans:
CREATE INDEX measures_foo_idx ON measures (dt, measure);
这是标准 SQL including the recursive CTE,LIMIT 除外。 Postgres 也支持标准关键字FETCH FIRST,如果你需要它所有的标准 SQL。
窗口函数?
单窗口函数无法实现
虽然窗口函数的结果是对窗口框架的聚合,但框架定义本身不能引用其他行。在您的情况下,粒度是通过从第一到最后考虑 all 行来动态确定的。这对于单个窗口函数是不可能的。
但是!
我们仍然可以使用 window frame with the RANGE clause bounded by an interval 获得每一行的滚动小时平均值 - 需要 Postgres 11 或更高版本。
SELECT *, avg(measure) OVER (ORDER BY dt
RANGE BETWEEN CURRENT ROW AND '1 hour' FOLLOWING)
FROM measures;
这样可以廉价地为每一行生成聚合。然后我们需要动态过滤新周期的每个开始。我们可以使用行数并按每个小时的行数向前跳过 - PL/pgSQL cursor 自然地适合这项任务:
CREATE OR REPLACE FUNCTION f_dynamic_hourly_avg()
RETURNS TABLE(hour_start timestamp, avg_measure numeric, ct int)
LANGUAGE plpgsql AS
$func$
DECLARE
_cursor CURSOR FOR
SELECT dt, round(avg(measure) OVER w, 2), count(*) OVER w
FROM measures
WINDOW w AS (ORDER BY dt RANGE BETWEEN CURRENT ROW AND '1 hour' FOLLOWING);
BEGIN
OPEN _cursor;
FETCH _cursor INTO hour_start, avg_measure, ct;
WHILE FOUND
LOOP
RETURN NEXT;
FETCH RELATIVE ct FROM _cursor INTO hour_start, avg_measure, ct;
END LOOP;
END
$func$;
呼叫:
SELECT * FROM f_dynamic_hourly_avg();
事实证明这是非常有效,每个周期只有 很少 行。每个周期的行数过多 行。很难确定一个数字。在每个周期 快 1000 倍。
db小提琴here
我们甚至可以使用 dynamic cursor 并传递表和列名称以使其适用于任何表...
优化性能
您基本上需要遍历所有行,使用过程解决方案可以更快:plpgsql 函数中的 FOR 循环。哪个会更快?
-
几小时的递归查询,每行有很多行。
-
很多小时的函数,每行只有几行。
-
更新:将光标悬停在带有窗口函数的查询上的添加函数远胜于其余函数(虽然每个周期没有太多行?)
相关的PL/pgSQL解决方案: