【问题标题】:SQL to determine multiple date ranges (SQL Server 2000)SQL 确定多个日期范围 (SQL Server 2000)
【发布时间】:2011-08-19 13:47:20
【问题描述】:

我有一个表,其中包含一个事件的ID 和一个Date。每行代表一个日期。我正在尝试确定连续的日期范围并合并输出以显示ID,StartDate,EndDate

ID      Date
200236  2011-01-02 00:00:00.000
200236  2011-01-03 00:00:00.000
200236  2011-01-05 00:00:00.000
200236  2011-01-06 00:00:00.000
200236  2011-01-07 00:00:00.000
200236  2011-01-08 00:00:00.000
200236  2011-01-09 00:00:00.000
200236  2011-01-10 00:00:00.000
200236  2011-01-11 00:00:00.000
200236  2011-01-12 00:00:00.000
200236  2011-01-13 00:00:00.000
200236  2011-01-15 00:00:00.000
200236  2011-01-16 00:00:00.000
200236  2011-01-17 00:00:00.000

输出如下:

ID       StartDate    EndDate
200236   2011-01-02   2011-01-03
200236   2011-01-05   2011-01-13
200236   2011-01-15   2011-01-17

关于如何在 SQL Server 2000 中处理这个问题有什么想法吗?

【问题讨论】:

    标签: sql tsql sql-server-2000 gaps-and-islands


    【解决方案1】:
    SELECT ...
    FROM   ...
    WHERE  date_column BETWEEN '2011-01-02' AND '2011-01-15'
    

    也许? Reference

    或者您可以执行子查询并使用 MAX 链接下一条记录,其中日期为

    SELECT id, date, (SELECT MAX(date) FROM mytable WHERE date <= mytable.date) AS nextDate
    FROM   mytable
    

    或使用:

    SELECT TOP 1 date
    FROM         mytable
    WHERE        date <= mytable.date AND id <> mytable.id
    ORDER BY     date
    

    作为子查询,它会在当前记录之后抓取下一个日期。

    【讨论】:

    • 列出的数据只是表格的一个样本。有数千个条目(行),我提供了一个示例。有多个 ID,每个 ID 可以有从 1 到无穷大的任何日期。
    • @GeorgeGonzola:所以使用子选择并引用当前行的日期来查找下一个日期(然后将该值作为第三列)。
    【解决方案2】:

    我刚刚在 SQL Server 2008 中做过类似的事情。我认为以下翻译适用于 SQL Server 2000:

    -- Create table variable
    DECLARE @StartTable TABLE
    (
      rowid INT IDENTITY(1,1) NOT NULL,
      userid int,
      startDate date
    )
    
    Insert Into @StartTable(userid, startDate)
    --This finds the start dates by finding unmatched values
    SELECT t1.ID, t1.[Date]
    FROM Example As t1
    LEFT OUTER JOIN Example As t2 ON t1.ID=t2.ID 
       And DateAdd(day, 1, t2.[Date]) = t1.[Date]
    WHERE t2.[Date] Is NULL
    ORDER BY t1.ID, t1.[Date]
    
    -- Create table variable
    DECLARE @EndTable TABLE
    (
      rowid INT IDENTITY(1,1) NOT NULL,
      userid int,
      endDate date
    )
    
    Insert Into @EndTable(userid, endDate)
    --This finds the end dates by getting unmatched values 
    SELECT t1.ID, t1.[Date]
    FROM Example As t1
    LEFT OUTER JOIN Example As t2 ON t1.ID=t2.ID
       And DateAdd(day, -1, t2.[Date]) = t1.[Date]
    WHERE t2.[Date] IS NULL
    ORDER BY t1.ID, t1.[Date]
    
    Select eT.userid, startDate, endDate 
    From @EndTable eT
    INNER JOIN @StartTable sT On eT.userid = sT.userid 
    AND eT.rowid = sT.rowid;
    

    如您所见,我创建了两个表变量,一个用于开始,一个用于结束,方法是在 [Date] 列中的日期之前或之后的日期自行加入表格。这意味着我只为开始表选择没有日期之前的记录(所以这些将在一个时期的开始)和那些没有日期之后的记录(所以这些将在一个结束期间)的结束表。

    当这些被插入到表变量中时,由于 Identity 列,它们按顺序编号。然后我将两个表变量连接在一起。因为它们是有序的,所以开始日期和结束日期应该始终正确匹配。

    此解决方案适用于我,因为我每天每个 ID 最多有一条记录,我只对天感兴趣,而不是小时等。即使它是几个步骤,我喜欢它,因为它在概念上很简单并且消除了匹配没有游标或循环的记录。我希望它也对你有用。

    【讨论】:

    • 很好,我喜欢临时表的高效使用。顺便说一句,我不确定这个解决方案与 SQL Server 2008 中的类似解决方案有多少共同之处,但您可能想看看this answer(以防万一)。
    • 谢谢!我的解决方案与那个类似;事实上,使用 CTE 和分区来完成这项任务要简单得多,但不幸的是,这些东西在 SQL Server 2000 中不可用。
    【解决方案3】:

    This SO Question 可能会对您有所帮助。我直接链接到 Rob Farley 的回答,因为我觉得这是一个类似的问题。

    【讨论】:

      【解决方案4】:

      您可以采取的一种方法是添加一个字段来指示序列中的下一个日期。 (要么将其添加到当前表中,要么使用临时表,将基础数据存储到临时表中,然后更新序列中的下一个日期)。

      您的起始数据结构如下所示:

      ID, PerfDate, NextDate
      200236, 2011-01-02, 2011-01-03
      200236, 2011-01-03, 2011-01-04
      etc.
      

      然后您可以使用一系列相关的子查询将数据汇总到所需的输出中:

      SELECT ID, StartDate, EndDate
      FROM (
      SELECT DISTINCT ID, PerfDate AS StartDate, 
          (SELECT MIN([PerfDate]) FROM [SourceTable] S3
          WHERE S3.ID = S1.ID
          AND S3.NextDate > S1.PerfDate
          AND ISNULL(
              (SELECT MIN(PerfDate) 
              FROM [SourceTable] AS S4
              WHERE S4.ID = S1.ID 
              AND S4.NextDate > S3.NextDate), S3.NextDate + 1) > S3.NextDate) AS EndDate
      FROM [SourceTable] S1
      WHERE 
          ISNULL(
              (SELECT MAX(NextDate) 
              FROM [SourceTable] S2 
              WHERE S2.ID = S1.ID 
              AND S2.PerfDate < S1.PerfDate), PerfDate -1) < S1.PerfDate)q
      ORDER BY q.ID, q.StartDate
      

      【讨论】:

        【解决方案5】:

        这是我过去的做法。这是一个两步过程:

        1. 构建候选连续周期集
        2. 如果有任何重叠时段,请删除除最长的此类时段以外的所有时段。

        这里有一个脚本,它显示了它是如何完成的。您也许可以在一个 [bug,丑陋] 查询中完成它,但尝试这样做会让我头疼。我正在使用临时表,因为它使调试变得更加容易。

        drop table #source
        create table #source
        (
          id    int      not null ,
          dtCol datetime not null ,
        
          -----------------------------------------------------------------------
          -- ASSUMPTION 1: Each date must be unique for a given ID value.
          -----------------------------------------------------------------------
          unique clustered ( id , dtCol ) ,
        
          -----------------------------------------------------------------------
          -- ASSUMPTION 2: The datetime column only represents a day.
          -- The value of the time component is always 00:00:00.000
          -----------------------------------------------------------------------
          check ( dtCol = convert(datetime,convert(varchar,dtCol,112),112) ) ,
        
        )
        go
        
        insert #source values(1,'jan 1, 2011')
        insert #source values(1,'jan 4, 2011')
        insert #source values(1,'jan 5, 2011')
        insert #source values(2,'jan 1, 2011')
        insert #source values(2,'jan 2, 2011')
        insert #source values(2,'jan 3, 2011')
        insert #source values(2,'jan 5, 2011')
        insert #source values(3,'jan 1, 2011')
        insert #source values(4,'jan 1, 2011')
        insert #source values(4,'jan 2, 2011')
        insert #source values(4,'jan 3, 2011')
        insert #source values(4,'jan 4, 2011')
        go
        
        insert #source values( 200236 , '2011-01-02')
        insert #source values( 200236 , '2011-01-03')
        insert #source values( 200236 , '2011-01-05')
        insert #source values( 200236 , '2011-01-06')
        insert #source values( 200236 , '2011-01-07')
        insert #source values( 200236 , '2011-01-08')
        insert #source values( 200236 , '2011-01-09')
        insert #source values( 200236 , '2011-01-10')
        insert #source values( 200236 , '2011-01-11')
        insert #source values( 200236 , '2011-01-12')
        insert #source values( 200236 , '2011-01-13')
        insert #source values( 200236 , '2011-01-15')
        insert #source values( 200236 , '2011-01-16')
        insert #source values( 200236 , '2011-01-17')
        go
        
        drop table #candidate_range
        go
        create table #candidate_range
        (
          rowId   int      not null identity(1,1) ,
          id      int      not null ,
          dtFrom  datetime not null ,
          dtThru  datetime not null ,
          length  as 1+datediff(day,dtFrom,dtThru) ,
        
          primary key nonclustered ( rowID ) ,
          unique clustered (id,dtFrom,dtThru) ,
        
        )
        go
        
        --
        -- seed the candidate range table with the set of all possible contiguous ranges for each id
        --
        insert #candidate_range ( id , dtFrom , dtThru )
        select id      = tFrom.id    ,
               valFrom = tFrom.dtCol ,
               valThru = tThru.dtCol
        from #source tFrom
        join #source tThru on tThru.id     = tFrom.id
                          and tThru.dtCol >= tFrom.dtCol
        where 1+datediff(day,tFrom.dtCol,tThru.dtCol) = ( select count(*)
                                                          from #source t
                                                          where t.id = tFrom.id
                                                            and t.dtCol between tFrom.dtCol and tThru.dtCol
                                                        )
        order by 1,2,3
        go
        
        --
        -- compare the table to itself. If we find overlapping periods,
        -- we'll keep the longest such period and delete the shorter overlapping periods.
        --
        delete t2
        from #candidate_range t1
        join #candidate_range t2 on t2.id      = t1.id
                                and t2.rowId  != t1.rowID
                                and t2.length <  t1.length
                                and t2.dtFrom <= t1.dtThru
                                and t2.dtThru >= t1.dtFrom
        go
        

        这就是它的全部内容。

        【讨论】:

          猜你喜欢
          • 2012-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-16
          • 1970-01-01
          相关资源
          最近更新 更多