在 SCD2 表中合并时间跨度的行

问题描述

我的下表来自 SCD2 表。从这个源表中,我只选择了几列,这导致几行看起来完全相似。我想删除不必要的行,即那些包含相同数据的行,并让 ValidFrom 列显示一个值,使 ValidTo 列显示“时间跨度组”中的最后一个值。

源数据:

| Item     | Color      | ValidFrom     | ValidTo    |
| -------- | ---------- | ------------- | ---------- |
| Ball     | Red        | 2020-01-01    | 2020-03-24 |
| Ball     | Blue       | 2020-03-25    | 2020-04-12 |
| Ball     | Blue       | 2020-04-13    | 2020-05-07 |
| Ball     | Blue       | 2020-05-08    | 2020-11-14 |
| Ball     | Red        | 2020-11-15    | 9999-12-31 |
| Doll     | Yellow     | 2020-01-01    | 2020-03-24 |
| Doll     | Green      | 2020-03-25    | 2020-04-12 |
| Doll     | Green      | 2020-04-13    | 2020-05-07 |
| Doll     | Green      | 2020-05-08    | 2020-11-14 |
| Doll     | Pink       | 2020-11-15    | 9999-12-31 | 

我想要完成的是:

| Item     | Color      | ValidFrom     | ValidTo    |
| -------- | ---------- | ------------- | ---------- |
| Ball     | Red        | 2020-01-01    | 2020-03-24 |
| Ball     | Blue       | 2020-03-25    | 2020-11-14 |
| Ball     | Red        | 2020-11-15    | 9999-12-31 |
| Doll     | Yellow     | 2020-01-01    | 2020-03-24 |
| Doll     | Green      | 2020-03-25    | 2020-11-14 |
| Doll     | Pink       | 2020-11-15    | 9999-12-31 | 

请注意,物品球最初的颜色是红色,然后是蓝色,然后又变回红色。根据我的经验,这让事情变得更加复杂。

感谢您的帮助。

解决方法

这是孤岛和缺口问题。

您可以使用解析函数如下:

Select item,color,min(validfrom) as validfrom,Max(validto) as validto
  From
(Select t.*,Sum(case when lged between validfrom and validto then 0 else 1 end) 
           over (partition by item,color order by validfrom) as sm
  From
(Select t.*,Lag(validto) over (partition by item,color order by validfrom) as lged
  From t) t) t
Group by item,sm
,

这确实是一个间隙和孤岛问题,其中孤岛是具有相同项目和颜色的相邻记录。

在这里,我建议使用行号之间的差异来定义组。这仅涉及一级嵌套,而使用 lag() 时为两级,因此它应该是最有效的选项:

select item,max(validto) as validto
from (
    select t.*,row_number() over(order by validfrom) as rn1,row_number() over(partition by item,color order by validfrom) as rn2
    from mytable t
) t
group by item,rn1 - rn2
,

您的数据非常规律。您似乎只想合并没有重叠或间隙的相邻平铺记录。然而,以下处理间隙和更一般的重叠:

select item,min(validfrom),max(validto)
from (select t.*,sum(case when prev_validto >= dateadd(day,-1,validfrom)
                      then 0 else 1
                 end) over (partition by item order by validfrom) as grp
      from (select t.*,lag(validto) over (partition by item,color order by validfrom) as prev_validto
            from t
            ) t
     ) t
group by item,grp;

您在原始数据中寻找行的孤岛,其中“孤岛”具有相同的项目、颜色和相邻的日期。这通过查看相同项目和颜色的前一行来确定岛屿的开始位置。如果没有这样的行或者行在当前行开始之前就结束了,那么当前行就是一个岛的开始。

然后,grp 是“岛屿开始”的累积总和,可用于聚合并获得最终结果。

您的特定数据非常有限——完美地平铺了一行,在下一天开始前一天结束。您可以使用 left join:

做一些非常相似的事情
select item,sum(case when tprev.color is null then 1 else 0
                 end) over (partition by t.item order by t.validfrom) as grp
      from t left join
           t tprev
           on tprev.item = t.item and
              tprev.color = t.color and
              tprev.validto = dateadd(day,t.validfrom)
     ) t
group by item,grp
order by item,min(validfrom);

Here 是一个 dbfiddle 说明了这两种方法

,

由于行之间没有间隙或重叠,所以这个查询就足够了

blockNumber