5

我正在尝试找到与 PostgreSQL 查询中的 WINDOW 函数中的 PARTITION BY 子句中的当前行进行比较的方法。

想象一下,我在以下 5 个元素的查询中有一个短列表(在实际情况下,我有数千甚至数百万行)。我试图为每一行获取下一个不同元素(事件列)的 id,以及前一个不同元素的 id。

WITH events AS(
  SELECT 1 as id, 12 as event, '2014-03-19 08:00:00'::timestamp as date
  UNION SELECT 2 as id, 12 as event, '2014-03-19 08:30:00'::timestamp as date
  UNION SELECT 3 as id, 13 as event, '2014-03-19 09:00:00'::timestamp as date
  UNION SELECT 4 as id, 13 as event, '2014-03-19 09:30:00'::timestamp as date
  UNION SELECT 5 as id, 12 as event, '2014-03-19 10:00:00'::timestamp as date
)
SELECT lag(id)  over w as previous_different, event
     , lead(id) over w as next_different
FROM events ev
WINDOW w AS (PARTITION BY event!=ev.event ORDER BY date ASC);

我知道比较event!=ev.event是不正确的,但这就是我想要达到的目的。

我得到的结果是(就像我删除 PARTITION BY 子句一样):

 |12|2
1|12|3
2|13|4
3|13|5
4|12|

我想得到的结果是:

 |12|3
 |12|3
2|13|5
2|13|5
4|12|

任何人都知道这是否可能以及如何?非常感谢!

编辑:我知道我可以用两个JOINs、 aORDER BY和 a来做到这一点DISTINCT ON,但在数百万行的实际情况下,效率非常低:

WITH events AS(
  SELECT 1 as id, 12 as event, '2014-03-19 08:00:00'::timestamp as date
  UNION SELECT 2 as id, 12 as event, '2014-03-19 08:30:00'::timestamp as date
  UNION SELECT 3 as id, 13 as event, '2014-03-19 09:00:00'::timestamp as date
  UNION SELECT 4 as id, 13 as event, '2014-03-19 09:30:00'::timestamp as date
  UNION SELECT 5 as id, 12 as event, '2014-03-19 10:00:00'::timestamp as date
)
SELECT DISTINCT ON (e.id, e.date) e1.id, e.event, e2.id
FROM events e
LEFT JOIN events e1 ON (e1.date<=e.date AND e1.id!=e.id AND e1.event!=e.event) 
LEFT JOIN events e2 ON (e2.date>=e.date AND e2.id!=e.id AND e2.event!=e.event) 
ORDER BY e.date ASC, e.id ASC, e1.date DESC, e1.id DESC, e2.date ASC, e2.id ASC
4

1 回答 1

12

使用几个不同的窗口函数和两个子查询,这应该工作得很快:

WITH events(id, event, ts) AS (
  VALUES
   (1, 12, '2014-03-19 08:00:00'::timestamp)
  ,(2, 12, '2014-03-19 08:30:00')
  ,(3, 13, '2014-03-19 09:00:00')
  ,(4, 13, '2014-03-19 09:30:00')
  ,(5, 12, '2014-03-19 10:00:00')
   )
SELECT first_value(pre_id)  OVER (PARTITION BY grp ORDER BY ts)      AS pre_id
     , id, ts
     , first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM  (
   SELECT *, count(step) OVER w AS grp
   FROM  (
      SELECT id, ts
           , NULLIF(lag(event) OVER w, event) AS step
           , lag(id)  OVER w AS pre_id
           , lead(id) OVER w AS post_id
      FROM   events
      WINDOW w AS (ORDER BY ts)
      ) sub1
   WINDOW w AS (ORDER BY ts)
   ) sub2
ORDER  BY ts;

ts用作时间戳列的名称。
假设ts是唯一的 - 并被索引唯一约束会自动执行)。

在一个具有 50k 行的真实表的测试中,它只需要一次索引扫描。所以,即使是大桌子,也应该相当快。相比之下,您的 join / distinct 查询在一分钟后没有完成(如预期的那样)。
即使是优化的版本,一次处理一个交叉连接(几乎没有限制条件的左连接实际上是一个有限的交叉连接)在一分钟后也没有完成。

为了获得大表的最佳性能,请调整您的内存设置,尤其是work_mem(对于大排序操作)。如果您可以腾出 RAM,请考虑暂时为您的会话设置(很多)更高的值。在此处此处阅读更多信息。

如何?

  1. 在子查询sub1中查看前一行中的事件,并且仅在它已更改时才保留该事件,从而标记新组的第一个元素。同时,获取id上一行和下一行的 ( pre_id, post_id)。

  2. 在 subquerysub2中,count()只计算非空值。结果grp标记了连续相同事件块中的对等点。

  3. 在决赛中,每行取每组SELECT的第一个pre_id和最后一个,以达到所需的结果。 实际上,这在外部应该更快:post_id
    SELECT

     last_value(post_id) OVER (PARTITION BY grp ORDER BY ts
                               RANGE BETWEEN UNBOUNDED PRECEDING
                                     AND     UNBOUNDED FOLLOWING) AS post_id
    

    ...由于窗口的排序顺序与窗口一致pre_id,所以只需要一个排序。快速测试似乎证实了这一点。有关此框架定义的更多信息。

SQL小提琴。

于 2014-03-20T01:08:32.020 回答