【问题标题】:Maximum Count Range Intersection (in T-SQL)最大计数范围交集(在 T-SQL 中)
【发布时间】:2009-09-22 18:16:38
【问题描述】:

假设我有一张包含一堆日期的表格,例如:

declare @tbl table {
    idx int primary key,
    startdate datetime,
    enddate datetime
}

我想找到 startdate 和 enddate 相交的最大行集(在现实世界中,开始日期和结束日期代表事件的开始和结束时间,我需要找到同时发生的最大事件数)。

在另一种编程语言中,我可能会按 startdate 对所有条目进行排序,然后对每个条目进行一次迭代,构建一组临时交叉点(跟踪生成的最大集合)。但我不确定这是否是在 T-SQL 中表达这一点的最有效方式。帮忙!

哦,它是 SQL Server 2000。:(

【问题讨论】:

  • 交叉路口,你需要什么,你想出了什么
  • 我认为我上面的要求很明确。我想要同时发生的最大事件集。两行“a”和“b”在 a.startdate = b.startdate 时相交。到目前为止,我尝试过的每一个解决方案都有些老套。这就是为什么我要问亲爱的lazyweb。 :)
  • 根据问题,如果你用谷歌搜索,sqlhacks.com/index.php/Dates/Dates 会在那里
  • 我并不是要粗鲁,但这些链接都没有回答我的问题。是的,我用谷歌搜索了。
  • 显示一些 luv,提供一小组数据、表结构和您尝试过的潜在查询(正如我从下面的帖子中看到的那样(duh???)您之前尝试过),我们会尝试帮助...

标签: sql sql-server algorithm tsql sql-server-2000


【解决方案1】:

更新为删除联合所有

declare @tbl table (
idx int identity(1,1) primary key,    
startdate datetime,    
enddate datetime);

insert into @tbl (startdate, enddate) 
select '2009-01-01', '2009-01-05'
union all select '2009-01-02', '2009-01-04'
union all select '2009-01-01', '2009-01-03'
union all select '2009-01-03', '2009-01-06'
union all select '2009-01-04', '2009-01-07'
union all select '2009-01-05', '2009-01-08'

select idx, startdate
   , (select sum(in_or_out) 
from (
   select case when startdate<=all_events.startdate then 1 else 0 end
     + case when enddate <= all_events.startdate then -1 else 0 end as in_or_out
   from @tbl 
   where startdate <= all_events.startdate
     or enddate <= all_events.startdate) as previous
) as concurent
from @tbl all_events
order by startdate

这给出了开始会话的时间线,以及新会话开始时的并发会话数:

idx startdate   concurent
3   2009-01-01 00:00:00.000 2
1   2009-01-01 00:00:00.000 2
2   2009-01-02 00:00:00.000 3
4   2009-01-03 00:00:00.000 3
5   2009-01-04 00:00:00.000 3
6   2009-01-05 00:00:00.000 3

要获取原始请求(具有最大并发的并发会话集),您需要运行此查询两次,一次获取最大并发会话,一次获取具有最大并发时间的会话的开始日期,然后您必须得到这些会话。

更新

好的,这里只有一个查询来检索最大并发会话。我更改了测试数据以删除结束和开始的模糊重叠:

declare @tbl table (
idx int identity(1,1) primary key,    
startdate datetime,    
enddate datetime);

insert into @tbl (startdate, enddate) 
select '2009-01-01', '2009-01-04 23:59:59'
union all select '2009-01-02', '2009-01-03 23:59:59'
union all select '2009-01-01', '2009-01-02 23:59:59'
union all select '2009-01-03', '2009-01-03 23:59:59'
union all select '2009-01-04', '2009-01-04 23:59:59'
union all select '2009-01-05', '2009-01-05 23:59:59'


select max_concurent_starts.startdate as concurentdate
  , session.*
from (
  select *
  ,(
        select sum(in_or_out) 
        from (
            select case when startdate<=all_events.startdate then 1 else 0 end
                + case when enddate <= all_events.startdate then -1 else 0 end 
                as in_or_out
          from @tbl 
          where startdate <= all_events.startdate
              or enddate <= all_events.startdate) as previous
    ) as concurent
  from @tbl all_events) as max_concurent_starts
  join @tbl as session 
     on session.startdate <= max_concurent_starts.startdate 
     and session.enddate >= max_concurent_starts.startdate
  where concurent = (
  select top 1 concurent
  from (
      select (
          select sum(in_or_out) 
          from (
              select case when startdate<=all_events.startdate then 1 else 0 end
                  + case when enddate <= all_events.startdate then -1 else 0 end 
                  as in_or_out
            from @tbl 
            where startdate <= all_events.startdate
                or enddate <= all_events.startdate) as previous
      ) as concurent
    from @tbl all_events) as all_events_with_concurent
    order by concurent desc)
  order by concurentdate, startdate;

这给出了如下结果:

concurentdate   idx startdate   enddate
2009-01-02 00:00:00.000 3   2009-01-01 00:00:00.000 2009-01-02 23:59:59.000
2009-01-02 00:00:00.000 1   2009-01-01 00:00:00.000 2009-01-04 23:59:59.000
2009-01-02 00:00:00.000 2   2009-01-02 00:00:00.000 2009-01-03 23:59:59.000
2009-01-03 00:00:00.000 1   2009-01-01 00:00:00.000 2009-01-04 23:59:59.000
2009-01-03 00:00:00.000 2   2009-01-02 00:00:00.000 2009-01-03 23:59:59.000
2009-01-03 00:00:00.000 4   2009-01-03 00:00:00.000 2009-01-03 23:59:59.000

内容如下:在2009-01-02 00:00:00 上有 3 个并发会话(3、1 和 2),它们各自的开始和结束。有一个平局,在2009-01-03 00:00:00 上还有 3 个并发会话(1、2 和 4),它们各自的开始和结束。

性能里程可能会有所不同。使用 CTE 在 SQL 2005 中可以将查询编写简单 100 万倍。

【讨论】:

  • sweet:将范围扁平化为一个简单列表的好主意
  • 如果你想知道为什么我的比 charles 这么胖是因为处理关系。
【解决方案2】:

试试这个(我认为它接近你想要的......

Select Distinct EventId 
From EventTable Et
Join  (Select Top 1 RunDate, Count(*) DateCount
       From 
          (Select Distinct StartDate RunDate
           From EventTable
               Union  
           Select Distinct EndDate RunDate
           From EventTable) A
         Join EventTable E
            On A.RunDate Between E.StartDate And E.EndDate
       Group By RunDate
       Order By Count(*) Desc) Z
   On Z.RunDate Between Et.StartDate and Et.EndDate

哦,如果您的日期中有日期和时间,则仅将此处的所有日期替换为实际日期部分(去掉时间)

Select Distinct EventId 
From EventTable Et
Join  (Select Top 1 RunDate, Count(*) DateCount
       From 
          (Select Distinct DateAdd(day, 0, DateDiff(day, 0, StartDate)) RunDate
           From EventTable
               Union  
           Select Distinct DateAdd(day, 0, DateDiff(day, -1, EndDate)) RunDate
           From EventTable) A
         Join EventTable E
            On A.RunDate Between DateAdd(day, 0, DateDiff(day, 0, E.StartDate))
                             and DateAdd(day, 0, DateDiff(day, -1, E.EndDate))
       Group By RunDate
       Order By Count(*) Desc) Z
   On Z.RunDate Between DateAdd(day, 0, DateDiff(day, 0, Et.StartDate))
                    and DateAdd(day, 0, DateDiff(day, -1, Et.EndDate))

【讨论】:

    【解决方案3】:

    另一种方法:

    DECLARE @idx INT,
            @startdate DATETIME,
        @enddate DATETIME,  
            @prev_enddate DATETIME,
            @counter INT,
        @counter_max INT
    
    DECLARE db_cursor CURSOR FOR  
    SELECT idx, startdate,enddate 
    FROM @tbl
    ORDER BY startdate,enddate
    
    OPEN db_cursor   
    
    FETCH NEXT FROM db_cursor INTO @idx, @startdate, @enddate
    SET @prev_enddate = @enddate
    SET @counter = 0
    SET @counter_max = 0
    
    WHILE @@FETCH_STATUS = 0   
    BEGIN   
    IF @startdate < @prev_enddate
    BEGIN
        SET @counter = @counter + 1 
        IF @counter > @counter_max
        BEGIN
            SET @counter_max = @counter
        END
    END
    ELSE
    BEGIN
        SET @counter = 1
    END
    
    SET @prev_enddate = @enddate
    FETCH NEXT FROM db_cursor INTO @idx, @startdate, @enddate           
    END   
    
    CLOSE db_cursor   
    DEALLOCATE db_cursor
    
    SELECT @counter_max
    

    【讨论】:

      【解决方案4】:

      这个很短,易于理解并且工作正常:

      CREATE PROCEDURE FindEvents
      AS
      BEGIN
          DECLARE dates_cursor CURSOR FOR 
              SELECT
                  startdate AS thedate, 1 AS change
              FROM
                  dates
              UNION
              SELECT
                  enddate AS thedate, - 1 AS change
              FROM
                  dates
              ORDER BY 
                  thedate ASC;
      
              DECLARE @max INT;
              DECLARE @thedate DATETIME;
              DECLARE @change INT;
              DECLARE @current INT;
      
              SET @max = 0;
              SET @current = 0;
      
          OPEN dates_cursor
      
          FETCH NEXT FROM dates_cursor INTO @thedate, @change
      
          WHILE @@FETCH_STATUS = 0
          BEGIN
              SET @current = @current + @change;
              IF (@current > @max)
              BEGIN
                  SET @max = @current;
              END
              FETCH NEXT FROM dates_cursor INTO @thedate, @change
          END
      
          CLOSE dates_cursor
          DEALLOCATE dates_cursor
      
          SELECT @max;
      END
      

      【讨论】:

      • 我认为使用游标循环比使用查询要慢
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-11
      • 2011-03-29
      • 1970-01-01
      • 1970-01-01
      • 2013-10-10
      相关资源
      最近更新 更多