【问题标题】:Aggregating a Date Range聚合日期范围
【发布时间】:2011-11-02 23:47:24
【问题描述】:

我正在努力创建一个查询来汇总日期范围,同时按两个字段连续分组 - 基本上我正在尝试改变这个:

|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) group by Key, Valid 不起作用,因为它不尊重日期范围的时间顺序。需要注意的是,每个键和有效组内的日期范围没有间隔。

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

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

【问题讨论】:

  • 您的源数据中是否存在任何空白或重叠?
  • 您可以使用任何提供的解决方案吗?
  • 是的,现在回答了 - 感谢大家的建议 - 非常感谢

标签: sql sql-server-2008 tsql


【解决方案1】:

我面前没有 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】:

除了使用光标之外,我想不出任何办法。但是,这确实有效:

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

【讨论】:

  • 正如答案开头所暗示的那样;虽然这确实有效,但游标通常会产生沉重的成本 - 如果有基于集合的方法,通常值得首先探索......
【解决方案3】:

您可以通过首先计算关键行(即有效或关键更改的位置)然后链接到该组的最大日期来做到这一点。

编辑 - 重写以处理 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

【讨论】:

  • 如果同一个(Key,Valid)组合有3个或更多的连续记录怎么办?
  • @Dems 这应该仍然有效,代码通过定位没有直接在前的记录的记录来找到切换点。然后遍历那些“关键帧”
  • 对不起,我的错。我只浏览了查询并假设它做了什么 - 错误地。有一个可能无法处理的极端情况......如果我添加记录| 1| 0|2001-03-16|2001-03-31|,最终结果是否仍会显示DateTo2001-03-15
  • 我现在已经进行了适当的更正,代码现在将处理序列中的极端情况和间隙。重叠是完全不同的情况!
猜你喜欢
  • 2018-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-16
  • 2014-01-29
  • 2020-12-13
  • 1970-01-01
  • 2023-03-19
相关资源
最近更新 更多