获取PostgreSQL中多个时间范围的所有可能交集

Get all possible intersections of multiple time ranges in PostgreSQL

我正在尝试解决与此类似的问题Find all intersections of all sets of ranges in PostgreSQL

不同之处在于,在这个线程中,它获取 所有 范围重叠的范围,而在我的用例中,我所有可能的重叠:

考虑4个范围如

[2018-01-01, 2018-01-15]
[2018-01-01, 2018-01-02]
[2018-01-07, 2018-01-20]
[2018-01-12, 2018-01-30]

创建这样的时间轴

 A [==============]
 B [=]
 C     [=============]
 D          [===============]

我想获取所有发生的重叠,所以:

{ entities: [A, B], period: [2018-01-01, 2018-01-02] }
{ entities: [A, C], period: [2018-01-07, 2018-01-11] }
{ entities: [A, C, D], period: [2018-01-12, 2018-01-15] }
{ entities: [C, D], period: [2018-01-15, 2018-01-20] }

另一件事,在结果中我需要同一组中最可能的重叠,这解释了为什么没有 A, D 重叠。 我已经在同一时期得到 A, C, D 并且没有只有 A, D 重叠的时期,而有一个 A, C 例如。

我设法使来自其他线程的查询与我的设置/表一起工作,但我不确定是否理解所有这些,尤其是 "where all ranges overlap".

的哪一部分

谢谢。

这有点难看,因为它同时使用了递归 CTE 以及 daterange 和 Postgres Range 函数,但我认为它能让您大致了解:

CREATE TEMP TABLE dr (id CHAR(1), range daterange);

INSERT INTO dr VALUES
    ('A', '2018-01-01', '2018-01-15'),
    ('B', '2018-01-01', '2018-01-02'),
    ('C', '2018-01-07', '2018-01-20'),
    ('D', '2018-01-12', '2018-01-30');

WITH RECURSIVE recRange AS
(
    SELECT id,
        range,
        CAST(id as varchar(100)) as path,
        1 as depth
    FROM drrange
    UNION ALL
    SELECT drrange.id,
        drrange.range * recRange.range,
        CAST(recrange.path || ',' || drrange.id AS VARCHAR(100)),
        recRange.depth + 1
    FROM recRange INNER JOIN drrange ON
            recRange.range && drrange.range 
            AND recRange.id < drrange.id                
    --Prevent cycling (which shouldn't happen with that join)
    WHERE depth < 20
),
drrange AS
(
    SELECT 
        id,
        daterange(from_date, to_date + 1) as range
    FROM dr
)
SELECT path as entities, range as period FROM recRange t1
WHERE depth > 1
    --  Keep the range only if it is NOT contained by
    --+ any other range that has a deeper depth then it   
    --+ and has the same ending id (id). This isn't the
    --+ best logic and could make false positives, but...
    --+ it's in the ballpark           
    AND NOT range <@ ANY(SELECT range FROM recRange WHERE depth > 1 AND depth > t1.depth AND id = t1.id);

 entities |         period
----------+-------------------------
 A,B      | [2018-01-01,2018-01-03)
 A,C      | [2018-01-07,2018-01-16)
 C,D      | [2018-01-12,2018-01-21)
 A,C,D    | [2018-01-12,2018-01-16)