我正在尝试组合多个日期范围(我的负载大约为最大 500,大多数情况下为 10),这些日期范围可能会或可能不会重叠到最大可能的连续日期范围中。例如:
数据:
CREATE TABLE test (
id SERIAL PRIMARY KEY NOT NULL,
range DATERANGE
);
INSERT INTO test (range) VALUES
(DATERANGE('2015-01-01', '2015-01-05')),
(DATERANGE('2015-01-01', '2015-01-03')),
(DATERANGE('2015-01-03', '2015-01-06')),
(DATERANGE('2015-01-07', '2015-01-09')),
(DATERANGE('2015-01-08', '2015-01-09')),
(DATERANGE('2015-01-12', NULL)),
(DATERANGE('2015-01-10', '2015-01-12')),
(DATERANGE('2015-01-10', '2015-01-12'));
表看起来像:
id | range
----+-------------------------
1 | [2015-01-01,2015-01-05)
2 | [2015-01-01,2015-01-03)
3 | [2015-01-03,2015-01-06)
4 | [2015-01-07,2015-01-09)
5 | [2015-01-08,2015-01-09)
6 | [2015-01-12,)
7 | [2015-01-10,2015-01-12)
8 | [2015-01-10,2015-01-12)
(8 rows)
期望的结果:
combined
--------------------------
[2015-01-01, 2015-01-06)
[2015-01-07, 2015-01-09)
[2015-01-10, )
视觉表现:
1 | =====
2 | ===
3 | ===
4 | ==
5 | =
6 | =============>
7 | ==
8 | ==
--+---------------------------
| ====== == ===============>
假设/澄清
infinity
和打开上界 (upper(range) IS NULL
)。(无论哪种方式都可以,但这种方式更简单。)infinity
PostgreSQL 范围类型date
是离散类型,所有范围都有默认[)
范围。 手册:对于其他类型(例如
tsrange
!),如果可能,我会执行相同的操作:纯 SQL 的解决方案
为了清楚起见,使用 CTE:
或者,与子查询相同,更快但不太容易阅读:
如何?
a
:在按 排序时,使用窗口函数range
计算上限 ( ) 的运行最大值。 将 NULL bounds (unbounded) 替换为 +/-只是为了简化(没有特殊的 NULL 情况)。enddate
infinity
b
:在相同的排序顺序中,如果前一个enddate
比startdate
我们有一个间隙并开始一个新的范围(step
)。请记住,上限始终被排除在外。
c
: 通过使用另一个窗口函数计算步数来形成组 (grp
)。在外部
SELECT
构建中,每个组的范围从下限到上限。瞧。或者少一个子查询级别,但翻转排序顺序:
在第二步中使用
ORDER BY range DESC NULLS LAST
(withNULLS LAST
) 对窗口进行排序,以获得完全颠倒的排序顺序。这应该更便宜(更容易生产,完美匹配建议索引的排序顺序)并且对于带有rank IS NULL
. 看:相关答案和更多解释:
使用 plpgsql 的程序解决方案
适用于任何表/列名称,但仅适用于 type
daterange
。带有循环的程序解决方案通常较慢,但在这种特殊情况下,我希望该功能大大加快,因为它只需要一次顺序扫描:
称呼:
逻辑类似于 SQL 解决方案,但我们可以只通过一次。
SQL小提琴。
有关的:
在动态 SQL 中处理用户输入的常用练习:
指数
对于这些解决方案中的每一个,一个普通的(默认)btree 索引
range
将有助于大表中的性能:btree 索引对范围类型的用途有限,但我们可以获得预先排序的数据,甚至可能是仅索引扫描。
我想出了这个:
仍然需要一些磨练,但想法如下:
+
) 失败时,返回已经构建的范围并重新初始化几年前,我测试了不同的解决方案(其中一些类似于来自@ErwinBrandstetter 的解决方案)用于合并 Teradata 系统上的重叠时段,我发现以下最有效的解决方案(使用分析函数,较新版本的 Teradata 具有内置函数那个任务)。
maxEnddate
maxEnddate
,LEAD
你就差不多完成了。仅对于最后一行LEAD
返回 aNULL
,以解决此问题,计算步骤 2 中分区的所有行的最大结束日期及其COALESCE
。为什么它更快?根据实际数据,第 2 步可能会大大减少行数,因此下一步只需对一小部分子集进行操作,此外它还删除了聚合。
小提琴
由于这在 Teradata 上是最快的,我不知道 PostgreSQL 是否也一样,如果能得到一些实际的性能数据会很好。
为了好玩,我试了一下。我发现这是最快、最干净的方法。首先,我们定义一个函数,如果有重叠或两个输入相邻,则合并,如果没有重叠或邻接,我们只需返回第一个日期范围。提示
+
是范围上下文中的范围联合。然后我们像这样使用它,