按ID对行进行分组,并查找具有日期间隔的最大/最小(date_from,date_to)

时间:2019-04-29 15:46:20

标签: sql tsql gaps-and-islands

我需要按ID对数据进行分组并找到最大/最小(date_from,date_to)。但是,如果有日期间隔,应该在新的一行。

我有以下数据:

SYS_ID  ITEM_ID DATE_FROM   DATE_TO
1       1       01.01.2019  20.01.2019
1       1       15.01.2019  10.02.2019
1       1       15.02.2019  20.02.2019
1       1       18.02.2019  10.03.2019
1       1       10.03.2019  22.03.2019
1       2       01.01.2019  10.01.2019
1       2       15.01.2019  25.01.2019

结果应为:

SYS_ID  ITEM_ID DATE_FROM   DATE_TO
1       1       01.01.2019  10.02.2019
1       1       15.02.2019  22.03.2019
1       2       01.01.2019  10.01.2019
1       2       15.01.2019  25.01.2019

有没有一种方法可以不使用光标?

1 个答案:

答案 0 :(得分:3)

使用差距和孤岛方法

实时测试:http://sqlfiddle.com/#!18/0174b/3

with gap_detector as
(
     select
        sys_id, item_id,
        date_from, date_to,
        case when 
            lag(date_to) 
            over(partition by sys_id, item_id order by date_from) >= date_from
        then
            0
        else
            1
        end as gap
     from tbl
 )
 , grouper as
 (
     select
         sys_id, item_id,
         date_from, date_to,
         sum(gap) over(partition by sys_id, item_id order by date_from) as grp
     from gap_detector   
)  
select
    sys_id, item_id,
    min(date_from) as date_from,
    max(date_to) as date_to
from grouper
group by sys_id, item_id, grp

输出:

| sys_id | item_id |  date_from |    date_to |
|--------|---------|------------|------------|
|      1 |       1 | 2019-01-01 | 2019-02-10 |
|      1 |       1 | 2019-02-15 | 2019-03-22 |
|      1 |       2 | 2019-01-01 | 2019-01-10 |
|      1 |       2 | 2019-01-15 | 2019-01-25 |

工作原理

首先,我们需要检测前一行(使用lag)的date_to是否与当前date_from重叠。

请注意,我们有独立的date_from集,即sys_id + item_id组合的前一行(例如1,1)与另一个sys_id + { {1}}组合(例如1,2)。因此,item_id的前一个date_to不是1,2,而是March 22, 2019。我们可以通过对NULL + sys_id进行分区来正确识别每个组合的前一行。

上面说的是我们如何确定上一行的date_to是否与当前date_from重叠:

  • 如果当前date_from与先前的date_to重叠,请不要将当前date_from与上一行隔离开来,我们可以通过将当前行的值设置为0来实现。
  • 否则,如果当前date_from与上一date_to不重叠,则通过将当前行标记为空白,将当前行与上一行隔离(换句话说,item_id)。值为1。稍后我们为什么需要1和0。

实时测试:http://sqlfiddle.com/#!18/0174b/7

partition by sys_id, item_id

输出:

gap

下一步是通过在间隙标记(1和0)上进行累加总计,将属于彼此的岛分组。通过在with gap_detector as ( select sys_id, item_id, date_from, date_to, case when lag(date_to) over(partition by sys_id, item_id order by date_from) >= date_from then 0 else 1 end as gap from tbl ) select * from gap_detector order by sys_id, item_id, date_from + | sys_id | item_id | date_from | date_to | gap | |--------|---------|------------|------------|-----| | 1 | 1 | 2019-01-01 | 2019-01-20 | 1 | | 1 | 1 | 2019-01-15 | 2019-02-10 | 0 | | 1 | 1 | 2019-02-15 | 2019-02-20 | 1 | | 1 | 1 | 2019-02-18 | 2019-03-10 | 0 | | 1 | 1 | 2019-03-10 | 2019-03-22 | 0 | | 1 | 2 | 2019-01-01 | 2019-01-10 | 1 | | 1 | 2 | 2019-01-15 | 2019-01-25 | 1 | 组合窗口上进行sum(gap)来完成总计。

sys_id + item_id组合的每个窗口都可以通过对其进行sys_iditem_id

来独立地操作。

实时测试:http://sqlfiddle.com/#!18/0174b/12

partition

输出:

partition by sys_id, item_id

最后,既然我们已经能够识别出哪些岛属于彼此(由with gap_detector as ( select sys_id, item_id, date_from, date_to, case when lag(date_to) over(partition by sys_id, item_id order by date_from) >= date_from then 0 else 1 end as gap from tbl ) , grouper as ( select sys_id, item_id, date_from, date_to, gap, sum(gap) over(partition by sys_id, item_id order by date_from) as grp from gap_detector ) select sys_id, item_id, date_from, date_to, gap, grp from grouper 表示),那么只需对这些| sys_id | item_id | date_from | date_to | gap | grp | |--------|---------|------------|------------|-----|-----| | 1 | 1 | 2019-01-01 | 2019-01-20 | 1 | 1 | | 1 | 1 | 2019-01-15 | 2019-02-10 | 0 | 1 | | 1 | 1 | 2019-02-15 | 2019-02-20 | 1 | 2 | | 1 | 1 | 2019-02-18 | 2019-03-10 | 0 | 2 | | 1 | 1 | 2019-03-10 | 2019-03-22 | 0 | 2 | | 1 | 2 | 2019-01-01 | 2019-01-10 | 1 | 1 | | 1 | 2 | 2019-01-15 | 2019-01-25 | 1 | 2 | 标记执行grp即可识别在各组(group by)岛屿上开始date_from和date_to时。

实时测试:http://sqlfiddle.com/#!18/0174b/13

grp

输出:

grp