我正在使用SQL Server 2008(如果需要,可以访问SQL 2017)并且有一个这样的表:
DECLARE @tbl TABLE (recdate DATE, myflag BIT)
该表包含范围内所有日期的行,myflag位将关闭和打开,如下所示:
recdate | myflag
2017-01-01 | 1
2017-01-02 | 1
2017-01-03 | 1
...
2017-04-03 | 1
2017-04-04 | 0
2017-04-05 | 0
..
2017-05-15 | 0
2017-05-16 | 1
etc.
但我真正需要的是
period_from | period_to | myflag
2017-01-01 | 2017-04-03 | 1
2017-04-04 | 2017-05-15 | 0
2017-05-16 | 2017-05-21 | 1
所以每当myflag改变时,它会创建一个新行,前一行设置结束日期(如果有意义的话)
我确信有一种非常明显的方式可以做到这一点,但是我已经准备好把头撞到墙上了......我已经选择和子选择来回走动了插入和更新到临时表,甚至尝试光标(我知道!但它是一次性查询)
答案 0 :(得分:5)
这是一个缺口和岛屿问题。为此,您可以使用行号的差异:
select min(recdate) as period_from, max(recdate) as period_to, flag
from (select t.*,
row_number() over (order by recdate) as seqnum,
row_number() over (partition by flag order by recdate) as seqnum_f
from @tbl t
) t
group by (seqnum - seqnum_f), flag;
为什么这样做有点难以用语言解释。我发现如果你运行子查询,你就会明白为什么你所寻找的组的差异是不变的。
如果您的日期是连续的,没有间隙或重复或时间组件,您可以稍微简化:
select min(recdate) as period_from, max(recdate) as period_to, flag
from (select t.*,
dateadd(day,
- row_number() over (partition by flag order by recdate
recdate
) as grp
from @tbl t
) t
group by grp, flag;
这与第一个版本的逻辑基本相同。