窗口函数或公用表表达式:计算范围内的前一行

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不仅仅是我所知道的很多东西.我有一种感觉,我很快就会去.:)

Erw*_*ter 6

我认为你不能用简单的查询,CTE和窗口函数来做到这一点 - 它们的帧定义是静态的,但你需要一个动态的框架.

通常,您必须仔细定义窗口的下边框和上边框:以下查询排除当前行并包含下边框.
仍然存在细微差别:该函数包括当前行的先前对等,而相关子查询排除它们......

测试用例

使用ts而不是保留字date作为列名.

CREATE TEMP TABLE test (
  id  bigint
 ,ts  timestamp
);
Run Code Online (Sandbox Code Playgroud)

ROM - 罗马的查询

使用CTE,将时间戳聚合到一个数组中,不需要,计数......
虽然正确,但性能会急剧下降,而且只需要一个满满的行.这里有几个性能杀手.见下文.

ARR - 计算数组元素

我接受了罗马的询问并试图简化它:
- 删除第二个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)

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;
Run Code Online (Sandbox Code Playgroud)

FNC - 功能

遍历与按时间顺序排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)

SQL小提琴.

基准

通过上面的表格,我在旧的测试服务器上运行了一个快速基准测试: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

该功能是明确的胜利者.它是最快的一个数量级,并且最佳.
阵列处理无法竞争.