【问题标题】:Find rare events in flowing window on timestamp在时间戳的流动窗口中查找罕见事件
【发布时间】:2013-09-06 18:48:33
【问题描述】:

给定下表:

CREATE TABLE table
(
 "id" serial NOT NULL,
 "timestamp" timestamp without time zone NOT NULL,
 "count" integer NOT NULL DEFAULT 0
)

我正在搜索“罕见事件”。罕见事件是拥有以下属性的行:

  • 简单:count = 1
  • Hard:在 10 分钟时间跨度内(当前行的时间戳之前和之后)的所有行都具有 count = 0(当然,给定行除外)。

例子:

id   timestamp  count
0    08:00      0    
1    08:11      0    
2    08:15      2     <== not rare event (count!=1)   
3    08:19      0    
4    08:24      0    
5    08:25      0   
6    08:29      1     <== not rare event (see 8:35)
7    08:31      0    
8    08:35      1    
9    08:40      0    
10   08:46      1     <== rare event!  
10   08:48      0   
10   08:51      0   
10   08:55      0   
10   08:58      1     <== rare event!  
10   09:02      0   
10   09:09      1

现在,我有以下 PL/pgSQL 函数:

SELECT curr.* 
    FROM gm_inductionloopdata curr
    WHERE curr.count = 1
    AND (
      SELECT SUM(count)
      FROM gm_inductionloopdata
      WHERE timestamp BETWEEN curr.timestamp + '10 minutes'::INTERVAL
      AND curr.timestamp - '10 minutes'::INTERVAL
    )<2

这太慢了。 :-(

关于如何提高性能的任何建议?我在这里处理 > 1 mio 行,可能需要定期查找那些“罕见事件”。

【问题讨论】:

  • 您的查询错误,没有 WHERE 并且 BETWEEN 被翻转(应该是 `- '10 min...' AND ... + '10 min...')...
  • 另外,您是否尝试过在时间戳列上建立索引以查看您当前的查询是否正常?
  • 真的有七行ID相同吗? “时间戳”真的意味着“时间”吗? (在 SQL 数据库中,timestamp 通常表示日期和时间,而不仅仅是时间,这在 PostgreSQL 中是完全不同的数据类型。)

标签: sql performance postgresql timestamp window-functions


【解决方案1】:

我认为这是使用 lead and lag window functions 的好案例 - 此查询过滤所有 count = 1 的记录,然后获取上一行和下一行,看看它是否接近 10 分钟:

with cte as (
  select
      "id", "timestamp", "count",
      lag("timestamp") over(w) + '10 minutes'::interval as "lag_timestamp",
      lead("timestamp") over(w) - '10 minutes'::interval as "lead_timestamp"
  from gm_inductionloopdata as curr
  where curr."count" <> 0
  window w as (order by "timestamp")
)
select "id", "timestamp"
from cte
where
    "count" = 1 and
    ("lag_timestamp" is null or "lag_timestamp" < "timestamp") and
    ("lead_timestamp" is null or "lead_timestamp" > "timestamp")

sql fiddle demo

或者你可以试试这个,并确保你在表的timestamp 列上有索引:

select *
from gm_inductionloopdata as curr
where
    curr."count" = 1 and
    not exists (
        select *
        from gm_inductionloopdata as g
        where 
           -- you can change this to between, I've used this just for readability
           g."timestamp" <= curr."timestamp" + '10 minutes'::interval and
           g."timestamp" >= curr."timestamp" - '10 minutes'::interval and
           g."id" <> curr."id" and
           g."count" = 1
    );

sql fiddle demo

顺便说一句,请不要将您的列称为"count""timestamp" 或其他关键字、函数名称和类型名称。

【讨论】:

  • 您的第一个示例很好地利用了领先和滞后,但并没有解决问题。正如问题所述,计数 0 时不能有接近 10 分钟的行,这并不意味着这些行接近一行(如领先和滞后所假设的那样)。
  • @MatheusOl 我的第一个查询过滤了 count = 1 的所有行,然后检查最近的行是否超过 10 分钟,所以它应该可以解决问题
  • 是的,但是如果有一行超过 10 分钟并且距离 2 行或更多行更远,例如 08:29 的情况(来自问题的示例),它的领先是 08:31,但 08:35 也在 10 分钟之内,你不考虑这个。
  • @MatheusOl 你检查过 SQL 小提琴吗?有 2 个 cte,我从 ALREADY FILTERED 列表中得到滞后和领先
  • 好的,我现在得到了“已经过滤”...但是还有一个小问题,如果附近的计数不是恰好 1。但是很容易解决:sqlfiddle.com/#!12/cea93/1/0(注意我改了08:35的行)。
【解决方案2】:

这可以更快,但是(改进1st solution of @Roman)。

SELECT id, ts, ct
FROM  (
    SELECT id, ts, ct
        ,lag (ts, 1, '-infinity') OVER (ORDER BY ts) as prev_ts
        ,lead(ts, 1,  'infinity') OVER (ORDER BY ts) as next_ts
    FROM   tbl
    WHERE  ct <> 0
    ) sub
WHERE  ct = 1
AND    prev_ts < ts - interval '10 min'
AND    next_ts > ts + interval '10 min'
ORDER  BY ts;
  • 使用以下两条信息可以大大简化“无前行/后行”极端情况的处理:

  • 子查询通常比 CTE 更有效(有些例外情况适用),因为 CTE 引入了优化障碍(通过设计和故意)。如果性能很重要,请仅在需要时使用 CTE。

还有:

  • 我使用正确的列名,而不是timestampcount,因此不需要双引号标识符。切勿使用保留字或基本类型或函数名称作为标识符。

  • 这一切都与 无关,后者是 Postgres 的默认程序语言。

SQL Fiddle.

索引

由于我们正在处理一个大桌子 (&gt; 1 mio rows),并且只对“罕见事件”感兴趣,因此对于性能而言重要的是partial index 喜欢以下:

CREATE INDEX tbl_rare_idx ON tbl(ts) WHERE ct <> 0;

如果您使用的是 Postgres 9.2 或更高版本,并且有一些先决条件,请将其设为 covering index for index-only scans

CREATE INDEX tbl_rare_covering_idx ON tbl(ts, ct, id)
WHERE ct <> 0;

使用EXPLAIN ANALYZE 进行测试以查看哪个查询更快以及是否使用了索引。

【讨论】:

    猜你喜欢
    • 2011-03-02
    • 2011-04-19
    • 1970-01-01
    • 2022-12-08
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-30
    相关资源
    最近更新 更多