说我们有这样一张桌子:
declare @periods table (
s date,
e date,
t tinyint
);
Run Code Online (Sandbox Code Playgroud)
日期间隔没有按开始日期排序的间隙
insert into @periods values
('2013-01-01' , '2013-01-02', 3),
('2013-01-02' , '2013-01-04', 1),
('2013-01-04' , '2013-01-05', 1),
('2013-01-05' , '2013-01-06', 2),
('2013-01-06' , '2013-01-07', 2),
('2013-01-07' , '2013-01-08', 2),
('2013-01-08' , '2013-01-09', 1);
Run Code Online (Sandbox Code Playgroud)
所有日期间隔都有不同的类型(t).
需要组合相同类型的日期间隔,它们不会被其他类型的间隔(按开始日期排序的所有间隔)打破.
所以结果表应如下所示:
s | e | t
------------|------------|-----
2013-01-01 | 2013-01-02 | 3
2013-01-02 | 2013-01-05 | 1
2013-01-05 | 2013-01-08 | 2
2013-01-08 | 2013-01-09 | 1
Run Code Online (Sandbox Code Playgroud)
任何想法如何没有光标吗?
我有一个有效的解决方案:
declare @periods table (
s datetime primary key clustered,
e datetime,
t tinyint,
period_number int
);
insert into @periods (s, e, t) values
('2013-01-01' , '2013-01-02', 3),
('2013-01-02' , '2013-01-04', 1),
('2013-01-04' , '2013-01-05', 1),
('2013-01-05' , '2013-01-06', 2),
('2013-01-06' , '2013-01-07', 2),
('2013-01-07' , '2013-01-08', 2),
('2013-01-08' , '2013-01-09', 1);
declare @t tinyint = null;
declare @PeriodNumber int = 0;
declare @anchor date;
update @periods
set period_number = @PeriodNumber,
@PeriodNumber = case
when @t <> t
then @PeriodNumber + 1
else
@PeriodNumber
end,
@t = t,
@anchor = s
option (maxdop 1);
select
s = min(s),
e = max(e),
t = min(t)
from
@periods
group by
period_number
order by
s;
Run Code Online (Sandbox Code Playgroud)
但我怀疑我是否可以依赖UPDATE语句的这种行为?
我使用SQL Server 2008 R2.
编辑:
感谢Daniel和这篇文章:http://www.sqlservercentral.com/articles/T-SQL/68467/
我发现上述解决方案中遗漏了三件重要的事情:
我已按照这些规则更改了上述解决方案.
由于您的范围是连续的,因此问题本质上变成了间隙和孤岛问题。如果您有一个标准来帮助您区分具有相同t值的不同序列,您可以使用该标准对所有行进行分组,然后MIN(s), MAX(e)对每个组进行取值。
获得这种标准的一种方法是使用两次ROW_NUMBER调用。考虑以下查询:
SELECT
*,
rnk1 = ROW_NUMBER() OVER ( ORDER BY s),
rnk2 = ROW_NUMBER() OVER (PARTITION BY t ORDER BY s)
FROM @periods
;
Run Code Online (Sandbox Code Playgroud)
对于您的示例,它将返回以下集合:
s e t rnk1 rnk2
---------- ---------- -- ---- ----
2013-01-01 2013-01-02 3 1 1
2013-01-02 2013-01-04 1 2 1
2013-01-04 2013-01-05 1 3 2
2013-01-05 2013-01-06 2 4 1
2013-01-06 2013-01-07 2 5 2
2013-01-07 2013-01-08 2 6 3
2013-01-08 2013-01-09 1 7 3
Run Code Online (Sandbox Code Playgroud)
关于rnk1和rnk2排名的有趣之处在于,如果您从另一个中减去一个,您将获得的值与 一起t,唯一标识具有相同 的每个不同的行序列t:
s e t rnk1 rnk2 rnk1 - rnk2
---------- ---------- -- ---- ---- -----------
2013-01-01 2013-01-02 3 1 1 0
2013-01-02 2013-01-04 1 2 1 1
2013-01-04 2013-01-05 1 3 2 1
2013-01-05 2013-01-06 2 4 1 3
2013-01-06 2013-01-07 2 5 2 3
2013-01-07 2013-01-08 2 6 3 3
2013-01-08 2013-01-09 1 7 3 4
Run Code Online (Sandbox Code Playgroud)
知道了这一点,您可以轻松地应用分组和聚合。这就是最终查询的样子:
WITH partitioned AS (
SELECT
*,
g = ROW_NUMBER() OVER ( ORDER BY s)
- ROW_NUMBER() OVER (PARTITION BY t ORDER BY s)
FROM @periods
)
SELECT
s = MIN(s),
e = MAX(e),
t
FROM partitioned
GROUP BY
t,
g
;
Run Code Online (Sandbox Code Playgroud)
如果您愿意,可以在 SQL Fiddle使用此解决方案。