查找具有延期日期范围的行并累积其持续时间

Posted

技术标签:

【中文标题】查找具有延期日期范围的行并累积其持续时间【英文标题】:Find rows with adjourning date ranges and accumulate their durations 【发布时间】:2020-02-07 10:37:25 【问题描述】:

我的 PostgreSQL 数据库存储学校假期、公共假期和周末日期,供父母计划假期。很多时候,学校假期会被周末或公共假期推迟。我想显示学校假期的非上学天数。这应该包括任何延期的周末或公共假期。

示例数据

位置

SELECT id, name, is_federal_state 
FROM locations 
WHERE is_federal_state = true;
| id | name              | is_federal_state |
|----|-------------------|------------------|
| 2  | Baden-Württemberg | true             |
| 3  | Bayern            | true             |

holiday_or_vacation_types

SELECT id, name FROM holiday_or_vacation_types;
| id | name                  |
|----|-----------------------|
| 1  | Herbst                |
| 8  | Wochenende            |

“Herbst”是德语的“秋天”,“Wochenende”是德语的“周末”。

周期

SELECT id, starts_on, ends_on, holiday_or_vacation_type_id 
FROM periods 
WHERE location_id = 2 
ORDER BY starts_on;
| id  | starts_on    | ends_on      | holiday_or_vacation_type_id |
|-----|--------------|--------------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 8                           |

任务

我想select all periods 其中location_id 等于2。我想计算每个时期的持续时间(以天为单位)。这可以通过这个 SQL 查询来完成:

SELECT id, starts_on, ends_on, 
       (ends_on - starts_on + 1) AS duration, 
       holiday_or_vacation_type_id 
FROM periods
| id  | starts_on    | ends_on      | duration | holiday_or_vacation_type_id |
|-----|--------------|--------------|----------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 2        | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 3        | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1        | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 2        | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 2        | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 2        | 8                           |

任何查看日历的人都会看到 ID 670(周末)、532(秋季假期)和 533(秋季假期)被延期。所以他们加起来有 6 天的假期。 到目前为止,我用一个计算这个的程序来做这个。但这需要相当多的资源(实际表包含大约 500,000 个项目)。

问题 1

哪个 SQL 查询会产生以下输出(添加了 real_duration 列)?用 SQL 也能做到吗?

| id  | starts_on    | ends_on      | duration | real_duration | holiday_or_vacation_type_id |
|-----|--------------|--------------|----------|---------------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 2        | 6             | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 3        | 6             | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1        | 6             | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 2        | 2             | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 2        | 2             | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 2        | 2             | 8                           |

问题 2

可以在part_of_range 字段中列出休会期吗?这将是结果。用 SQL 能做到吗?

| id  | starts_on    | ends_on      | duration | part_of_range | holiday_or_vacation_type_id |
|-----|--------------|--------------|----------|---------------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 2        | 670,532,533   | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 3        | 670,532,533   | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1        | 670,532,533   | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 2        |               | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 2        |               | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 2        |               | 8                           |

【问题讨论】:

【参考方案1】:

这是一个空白和孤岛问题。在这种情况下,您可以使用lag() 来查看岛屿的起点,然后查看累积总和。

最后的操作是一些聚合(使用窗口函数):

SELECT p.*, 
      (Max(ends_on) OVER (PARTITION BY location_id, grp) - Min(starts_on) OVER (PARTITION BY location_id, grp) ) + 1 AS duration,
      Array_agg(p.id) OVER (PARTITION BY location_id) 
FROM (SELECT p.*, 
             Count(*) FILTER (WHERE prev_eo < starts_on - INTERVAL '1 day') OVER (PARTITION BY location_id ORDER BY starts_on) AS grp
      FROM (SELECT id, starts_on, ends_on, location_id, holiday_or_vacation_type_id, 
                   lag(ends_on) OVER (PARTITION BY location_id ORDER BY (starts_on)) AS prev_eo
            FROM periods 
           ) p
     ) p;

【讨论】:

以上是关于查找具有延期日期范围的行并累积其持续时间的主要内容,如果未能解决你的问题,请参考以下文章

使用 Oracle 查找任意日期范围内的行数

SQL BigQuery - 插入具有不同日期范围的行

日期时间范围之间的 Python Pandas 累积列

如何选择具有最新日期的行并根据该行计算另一个字段

如何在实体框架中查找具有指定日期范围列表的日期?

Redshift:在可变日期范围内构建累积和