4

给定下表:

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

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

  • 简单的:count = 1
  • 硬: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 行,可能需要定期查找那些“罕见事件”。

4

2 回答 2

3

我认为这是使用超前和滞后窗口函数的一个很好的例子——这个查询过滤所有 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"

于 2013-09-03T14:04:10.480 回答
2

这可以更快,但是(改进@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 。

还:

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

  • 这些都与无关,后者是 Postgres 的默认过程语言。

SQL小提琴。

指数

由于我们正在处理一个大表( > 1 mio rows) 并且只对“罕见事件”感兴趣,因此对性能而言重要的是部分索引,如下所示:

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

如果您使用的是Postgres 9.2 或更高版本并给出了一些先决条件,请将其设为仅索引扫描的覆盖索引

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

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

于 2013-09-04T20:05:50.203 回答