假设/澄清
无需区分infinity
和打开上限(upper(range) IS NULL
)。(您可以使用任何一种方法,但是这种方法更简单。)
由于date
是离散类型,因此所有范围都有默认[)
范围。
每个文档:
内置范围类型int4range
,int8range
和daterange
都使用规范形式,包括下限,但不包括上限;即[)
。
对于其他类型(如tsrange
!),如果可能,我将强制执行相同的操作:
纯SQL解决方案
为了清晰起见,请使用CTE:
WITH a AS (
SELECT range
, COALESCE(lower(range),'-infinity') AS startdate
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
FROM test
)
, b AS (
SELECT *, lag(enddate) OVER (ORDER BY range) < startdate OR NULL AS step
FROM a
)
, c AS (
SELECT *, count(step) OVER (ORDER BY range) AS grp
FROM b
)
SELECT daterange(min(startdate), max(enddate)) AS range
FROM c
GROUP BY grp
ORDER BY 1;
或,与子查询相同,速度更快但不太容易阅读:
SELECT daterange(min(startdate), max(enddate)) AS range
FROM (
SELECT *, count(step) OVER (ORDER BY range) AS grp
FROM (
SELECT *, lag(enddate) OVER (ORDER BY range) < startdate OR NULL AS step
FROM (
SELECT range
, COALESCE(lower(range),'-infinity') AS startdate
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
FROM test
) a
) b
) c
GROUP BY grp
ORDER BY 1;
或减少一个子查询级别,但翻转排序顺序:
SELECT daterange(min(COALESCE(lower(range), '-infinity')), max(enddate)) AS range
FROM (
SELECT *, count(nextstart > enddate OR NULL) OVER (ORDER BY range DESC NULLS LAST) AS grp
FROM (
SELECT range
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
, lead(lower(range)) OVER (ORDER BY range) As nextstart
FROM test
) a
) b
GROUP BY grp
ORDER BY 1;
- 使用
ORDER BY range DESC NULLS LAST
(和NULLS LAST
)在第二步中对窗口进行排序,以获得完全相反的排序顺序。这应该更便宜(更易于生产,与建议索引的排序顺序完全匹配),并且对于带有角的情况准确rank IS NULL
。
说明
a
:在按排序时range
,使用窗口函数计算上限()的运行最大值enddate
。
用+/-代替NULL边界(无界)infinity
只是为了简化(没有特殊的NULL情况)。
b
:按照相同的排序顺序,如果前一个排序enddate
早于startdate
我们的间隔,请开始一个新的范围(step
)。
请记住,上限始终被排除。
c
:grp
通过计数另一个窗口功能的步骤来形成组()。
在外部SELECT
构建中,每组的范围从下限到上限。Voilá。
与SO密切相关的答案,有更多解释:
plpgsql的过程解决方案
适用于任何表/列名称,但仅适用于type daterange
。
带循环的过程解决方案通常较慢,但是在这种特殊情况下,我希望该函数会更快,因为它只需要一次顺序扫描:
CREATE OR REPLACE FUNCTION f_range_agg(_tbl text, _col text)
RETURNS SETOF daterange AS
$func$
DECLARE
_lower date;
_upper date;
_enddate date;
_startdate date;
BEGIN
FOR _lower, _upper IN EXECUTE
format($$SELECT COALESCE(lower(t.%2$I),'-infinity') -- replace NULL with ...
, COALESCE(upper(t.%2$I), 'infinity') -- ... +/- infinity
FROM %1$I t
ORDER BY t.%2$I$$
, _tbl, _col)
LOOP
IF _lower > _enddate THEN -- return previous range
RETURN NEXT daterange(_startdate, _enddate);
SELECT _lower, _upper INTO _startdate, _enddate;
ELSIF _upper > _enddate THEN -- expand range
_enddate := _upper;
-- do nothing if _upper <= _enddate (range already included) ...
ELSIF _enddate IS NULL THEN -- init 1st round
SELECT _lower, _upper INTO _startdate, _enddate;
END IF;
END LOOP;
IF FOUND THEN -- return last row
RETURN NEXT daterange(_startdate, _enddate);
END IF;
END
$func$ LANGUAGE plpgsql;
呼叫:
SELECT * FROM f_range_agg('test', 'range'); -- table and column name
逻辑类似于SQL解决方案,但是我们可以通过一次操作来完成。
SQL提琴。
有关:
在动态SQL中处理用户输入的常用方法:
指数
对于这些解决方案中的每一个,普通的(默认)btree索引range
将对大表的性能有所帮助:
CREATE INDEX foo on test (range);
btree索引仅用于范围类型,但我们可以获取预排序的数据,甚至可以进行仅索引的扫描。