按类型组合连续的日期时间间隔

Vad*_*oda 5 t-sql sql-server

说我们有这样一张桌子:

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/

我发现上述解决方案中遗漏了三件重要的事情:

  1. 表上必须有聚集索引
  2. 必须有锚变量和聚集列的调用
  3. Update语句应该由一个处理器执行,即没有并行性

我已按照这些规则更改了上述解决方案.

And*_*y M 5

由于您的范围是连续的,因此问题本质上变成了问题。如果您有一个标准来帮助您区分具有相同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)

关于rnk1rnk2排名的有趣之处在于,如果您从另一个中减去一个,您将获得的值与 一起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使用此解决方案。