聚合日期范围

时间:2011-11-02 12:14:53

标签: sql sql-server-2008 tsql

我正在努力创建一个查询来汇总日期范围,同时按两个字段连续分组 - 基本上我试图将其转为:

|Key|Valid|DateFrom  |DateTo    |
|  1|    0|2001-01-01|2001-01-31|
|  1|    0|2001-02-01|2001-02-20|
|  1|    1|2001-02-21|2001-02-28|
|  1|    0|2001-03-01|2001-03-15|
|  2|    1|2001-01-01|2001-01-31|
|  2|    0|2001-02-01|2001-02-20|
|  2|    0|2001-02-21|2001-02-28|
|  2|    1|2001-03-01|2001-03-15|

进入这个:

|Key|Valid|DateFrom  |DateTo    |
|  1|    0|2001-01-01|2001-02-20|
|  1|    1|2001-02-21|2001-02-28|
|  1|    0|2001-03-01|2001-03-15|
|  2|    1|2001-01-01|2001-01-31|
|  2|    0|2001-02-01|2001-02-28|
|  2|    1|2001-03-01|2001-03-15|

当然,一个简单的min(DateFrom),max(DateTo)组按键,有效不起作用,因为它不遵守日期范围的时间顺序。应该注意的是,每个密钥和有效组中的日期范围没有间隙。

我已经广泛搜索了解决方案(无论是在这里还是在网络上的其他地方),并找到了大量使用OVER和CTE对其进行分组的解决方案(尝试了两者)但我认为问题在于我正在尝试两个不同组的因素。我也尝试将范围转换为单独的日期,但我再也无法按时间顺序将它们按两组进行推广。

任何帮助将不胜感激。感谢。

3 个答案:

答案 0 :(得分:0)

您可以先计算关键行(即有效或关键更改的位置),然后链接到该组的最大日期。

编辑 - 重写以处理由Dems标记的角落案例。本节还涉及序列中的空白

with keyItems as ( 
  -- First find all the "Key Frames" 
  select d.* 
from  
  data d 
left outer join data d2  
  on d.[Key]=d2.[key] and d.valid=d2.valid and d.dateFrom = DateAdd(d,1,d2.dateto) 
where d2.[key] is null 
), 
ordered as ( 
  -- This is to provide a sequence number for the main query against these key frames 
  select  
    ROW_NUMBER() over (partition by [key] order by datefrom) as row, 
    * 
  from keyItems 
),
rangeends([key],row,dateto) as (
select o.[key],o.row-1,MAX(d.DateTo)
from ordered o left outer join data d on d.[key]=o.[key] and d.DateTo < o.DateFrom
group by o.[key],o.row-1
union all
select o.[key],MAX(o.row),MAX(d.dateto)
from ordered o inner join data d on d.[key]=o.[key] 
group by o.[key]
)
select 
    o1.[Key], 
    o1.Valid, 
    o1.DateFrom, 
    coalesce(r.dateto,o1.dateTo) as DateTo 
    from ordered o1 
    left outer join rangeends r on r.[key]=o1.[Key] and r.row=o1.row

答案 1 :(得分:0)

我面前没有SQL客户端,但你可以这样做......

WITH
  sequenced_data
AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY Key        ORDER BY DateFrom) AS KeyRow,
    ROW_NUMBER() OVER (PARTITION BY Key, Valid ORDER BY DateFrom) AS KeyValidRow,
    *
  FROM
    yourData
)
SELECT
  Key,
  Valid,
  MIN(DateFrom) AS DateFrom,
  MAX(DatTo)    AS DateTo
FROM
  sequenced_data
GROUP BY
  Key,
  Valid,
  KeyRow - KeyValidRow
ORDER BY
  Key,
  MIN(DateFrom)


使用您的数据进行可视化......

|Key|Valid|DateFrom  |DateTo    |KeyRow|KeyValidRow|KeyRow - KeyValidRow
|  1|    0|2001-01-01|2001-01-31|     1|          1|       0
|  1|    0|2001-02-01|2001-02-20|     2|          2|       0
|  1|    1|2001-02-21|2001-02-28|     3|          1|       2
|  1|    0|2001-03-01|2001-03-15|     4|          3|       1
|  2|    1|2001-01-01|2001-01-31|     1|          1|       0
|  2|    0|2001-02-01|2001-02-20|     2|          1|       1
|  2|    0|2001-02-21|2001-02-28|     3|          2|       1
|  2|    1|2001-03-01|2001-03-15|     4|          2|       2

尽管KeyRow - KeyValidRow并不一定会告诉您太多,但它确实为每个组提供了明确的值,因此GROUP BY就足够了。

无论组中有多少条记录,它都能正常工作,但是假设数据中没有间隙或重叠。

答案 2 :(得分:0)

我无法想到使用光标。但这确实有效:

declare @example table (tKey int, Valid int, DateFrom date, DateTo date);

insert into @example values (1, 0, '2001-01-01', '2001-01-31');
insert into @example values (1, 0, '2001-02-01', '2001-02-20');
insert into @example values (1, 1, '2001-02-21', '2001-02-28');
insert into @example values (1, 0, '2001-03-01', '2001-03-15');
insert into @example values (2, 1, '2001-01-01', '2001-01-31');
insert into @example values (2, 0, '2001-02-01', '2001-02-20');
insert into @example values (2, 0, '2001-02-21', '2001-02-28');
insert into @example values (2, 1, '2001-03-01', '2001-03-15');

declare @output table (tKey int, Valid int, DateFrom date, DateTo date);

DECLARE ex_cursor CURSOR FOR
    select 
        tKey,Valid,DateFrom,DateTo
    from 
        @example
    order by tKey, DateFrom

DECLARE @tKey int
DECLARE @Valid int
DECLARE @DateFrom date
DECLARE @DateTo date

DECLARE @last_tKey int
DECLARE @last_Valid int
DECLARE @min_Date date
DECLARE @max_Date date

OPEN ex_cursor;

FETCH NEXT FROM ex_cursor
INTO @tKey, @Valid, @DateFrom, @DateTo;
SET @last_tKey = @tKey;
SET @last_Valid = @Valid;
SET @min_Date = @DateFrom;
SET @max_Date = @DateTo;

WHILE @@FETCH_STATUS = 0
BEGIN
    IF (@last_tKey <> @tKey OR @last_Valid <> @Valid)
        BEGIN
            -- output results
            INSERT INTO @output SELECT @last_tKey, @last_Valid, @min_Date, @max_Date
            -- reset values
            SET @last_tKey = @tKey;
            SET @last_Valid = @Valid;
            SET @min_Date = @DateFrom;
            SET @max_Date = @DateTo;
        END
    ELSE
        BEGIN
            IF (@DateTo > @max_Date) SET @max_Date = @DateTo
        END
    FETCH NEXT FROM ex_cursor
    INTO @tKey, @Valid, @DateFrom, @DateTo
END 
-- output one more time at end
INSERT INTO @output SELECT @last_tKey, @last_Valid, @min_Date, @max_Date
CLOSE ex_cursor;
DEALLOCATE ex_cursor;

SELECT * FROM @output ORDER BY tKey, DateFrom