Postgres 9.x 在重叠日期和时间范围内加入

Posted

技术标签:

【中文标题】Postgres 9.x 在重叠日期和时间范围内加入【英文标题】:Postgres 9.x Joining on Overlaping Date & Time Range 【发布时间】:2013-06-27 00:40:04 【问题描述】:

我有几个表,其中包含TIMESTAMP WITH TIME ZONE 列以及一些附加信息。我需要能够加入这些表,以便每一行上的所有信息在给定的日期和时间窗口内都是“有效的”(在所需的结果集中标记为 beginend)。

我目前的做法:

选项#1

    创建唯一的时间列表。 将每个时间点转换为唯一列表(“有效”时间窗口)和每个原始表的时间窗口。 [LEAD(...) OVER (...)] 将原始表加入唯一的时间列表。

选项 #2

    将每个时间点 (TIMESTAMP WITH TIME ZONE) 转换为每个表的时间窗口。 [LEAD(...) OVER (...)] 加入窗口重叠的表。 从每个窗口返回GREATEST(foo.start, bar.start) & LEAST(foo.stop, bar.stop) 以找到真正的“有效”窗口。

例如:

表:foo

  fooid  |  description  |       datetime
---------|---------------|-----------------------
    1    |   Varsion 1   |  2010-01-01 00:00:00  
    2    |   Varsion 2   |  2010-07-01 00:00:00 

表格:条形

  barid  |  fooid  |  description  |       datetime
---------|---------|---------------|-----------------------
    1    |    1    |   Varsion A   |  2010-01-01 00:00:00
    2    |    1    |   Varsion B   |  2010-02-01 00:00:00
    3    |    1    |   Varsion C   |  2010-03-01 00:00:00
    4    |    1    |   Varsion D   |  2010-04-01 00:00:00
    5    |    1    |   Varsion E   |  2010-05-01 00:00:00
    6    |    1    |   Varsion F   |  2010-06-01 00:00:00
    7    |    2    |   Varsion A   |  2010-07-01 00:00:00
    8    |    2    |   Varsion B   |  2010-08-01 00:00:00
    9    |    2    |   Varsion C   |  2010-09-01 00:00:00
    10   |    2    |   Varsion D   |  2010-10-01 00:00:00
    11   |    2    |   Varsion E   |  2010-11-01 00:00:00
    12   |    2    |   Varsion F   |  2010-12-01 00:00:00

简化的期望结果

        begin          |          end          |  fooid  |   foo_desc    |       foostart        |        foostop        |  barid  |   bar_desc   |       foostart        |        foostop        
-----------------------|-----------------------|---------|---------------|-----------------------|-----------------------|---------|--------------|-----------------------|-----------------------
         ...           |         ...           |   ...   |      ...      |         ...           |         ...           |   ...   |     ...      |         ...           |         ...           
  2010-05-01 00:00:00  |  2010-06-01 00:00:00  |    1    |   Varsion 1   |  2010-01-01 00:00:00  |  2010-07-01 00:00:00  |    5    |  Varsion E   |  2010-05-01 00:00:00  |  2010-06-01 00:00:00
  2010-06-01 00:00:00  |  2010-07-01 00:00:00  |    1    |   Varsion 1   |  2010-01-01 00:00:00  |  2010-07-01 00:00:00  |    6    |  Varsion F   |  2010-06-01 00:00:00  |        infinity
  2010-07-01 00:00:00  |  2010-08-01 00:00:00  |    2    |   Varsion 2   |  2010-07-01 00:00:00  |       infinity        |    7    |  Varsion A   |  2010-07-01 00:00:00  |  2010-08-01 00:00:00
  2010-08-01 00:00:00  |  2010-09-01 00:00:00  |    2    |   Varsion 2   |  2010-07-01 00:00:00  |       infinity        |    8    |  Varsion B   |  2010-08-01 00:00:00  |  2010-09-01 00:00:00
         ...           |         ...           |   ...   |      ...      |         ...           |         ...           |   ...   |     ...      |         ...           |         ...           

我的问题:

实现这一目标的最佳方法是什么?我创建了一个fiddle,展示了两种不同的解决方案,我想听听关于每种解决方案的想法以及可能的解决方案。

更新 #1:

在示例中,只有两个表需要连接...但是,在某些情况下,我可能需要连接多个表 3、4 或更多。

更新 #2:

使用选项#1,我的问题是当我有大型结果集时,我发现初始子查询可能很大并且postgres 不能使用索引。这会导致很大的性能损失。另一方面,我发现它是最准确的,因为我可以LEFT OUTER JOIN 反对它并获取相关的NULL 数据。

通过选项#2,查询规划器能够使用TIMESTAMP WITH TIME ZONE 列上的索引;但是,在FROM 子句中连接两个以上的表变得更加复杂。我可以将(table1.start, table1.stop) OVERLAPS (table2.start, table2.stop) 移动到WHERE 子句中,但随后我会丢失相关的NULL 数据。

所有这些都让我想知道是否有更好的方法......

【问题讨论】:

提示你改写一下?因为您的示例数据中的任何地方都没有 barid 5,6,7,8。此外,这些是具有 valid_until 日期时间的审计表,对吧? @Denis - 抱歉,我没有留下 ID...我已经更新了问题。这也更像是一个日志(而不是一个审计表)。每条记录从其创建时间戳开始就有效,并且在被下一条记录覆盖之前一直有效。 fooid 是 foo 的主键。它指向“2010-01-01”日期。现在,foostop 日期“2010-07-01”从何而来?是否有一些内置的 6 个月要求,或者是从无关(恕我直言) fooid=2 记录继承的?另外:为什么条形表中有一个 fooid (FK) 列?请澄清。 @joop - 是的,fooid 是表 foo 的 PK。 fooid 2 也是 fooid 1 的结束点(这与 bar 表的配置相同...下一个值是当前时间窗口的结束。另外,bar 表使用 fooid 引用 foo 表...但是不能保证表之间的时间戳是一致的......所以真正的重点是加入重叠时间。 那么,事实上 fooid 和 barid 都是代理键,所有你想要执行的匹配都应该基于时间戳? (欧文的回答说明了这一点,其中 fooid,barid 从连接中完全省略) 【参考方案1】: 对窗口函数 lead() 和 lag() 使用默认值,而不是 coalesce()

lead(datetime, 1, 'infinity') OVER (ORDER BY fooid, datetime) AS stop

而不是

COALESCE(LEAD(datetime) OVER (ORDER BY fooid, datetime), 'infinity'::TIMESTAMP) AS stop
当你PARTITION BY fooid 时,ORDER BY fooid 毫无意义。

PARTITION BY fooid ORDER BY datetime

代替:

(PARTITION BY fooid ORDER BY fooid, datetime)
您(或某些工具)将每个标识符都用双引号引起来,即使它们在没有引号的情况下都是合法的。使查询更难阅读。降低噪音。

除此之外:您的问题对于 *** 而言过于宽泛,而且描述很难理解。

考虑:https://codereview.stackexchange.com/

【讨论】:

谢谢@erwin...所有这些都是好点。我对codereview不熟悉,我会检查一下。我已经在 SO 上看到了您的 cmets,感谢您成为如此强大的贡献者。您对我目前使用的两种技术有意见吗?

以上是关于Postgres 9.x 在重叠日期和时间范围内加入的主要内容,如果未能解决你的问题,请参考以下文章

Postgres:避免与其他条件重叠范围的最佳方法

PHP:选择时间范围重叠日期的重叠日期时间范围

在 SQL 中检测和合并日期范围的连续重叠

Postgres tsrange,按日期和时间过滤

如何在 SQL 中选择重叠的日期范围

SQL 重叠日期范围