在PostgreSQL

时间:2018-02-06 15:33:51

标签: sql postgresql date-range

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

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

考虑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就有一个。

我设法让其他线程的查询与我的setup / tables一起工作,但我不确定是否理解所有这些,尤其是“所有范围重叠的部分”。

感谢。

1 个答案:

答案 0 :(得分:0)

这有点难看,因为它使用递归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)