Let's say I have a table with a bunch of dates, e.g.:
比如说我有一张桌子,上面有很多日期,例如:
declare @tbl table {
idx int primary key,
startdate datetime,
enddate datetime
}
And I want to find the largest set of rows where startdate and enddate intersect (in the real world, the start date and end date represents start and end times for events, and I need to find the maximum # of events occurring simultaneously).
我想找到startdate和enddate相交的最大行集(在现实世界中,开始日期和结束日期表示事件的开始和结束时间,我需要找到同时发生的事件的最大#)。
In another programming language I might sort all entries by startdate, then iterate through each entry once, building a temporary set of intersections (keeping track of the largest set generated). But I'm not sure if this is the most efficient way to express this in T-SQL. help!
在另一种编程语言中,我可以按startdate对所有条目进行排序,然后遍历每个条目一次,构建一个临时的交集集(跟踪生成的最大集合)。但我不确定这是否是用T-SQL表示的最有效的方式。的帮助!
Oh, and it's SQL Server 2000. :(
哦,它是SQL Server 2000。:(
4 个解决方案
#1
3
Updated to remove the union all
更新以删除所有的union
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
This gives the timeline of start session, with the count of concurent sessions at the moment new session starts:
这就给出了开始会议的时间线,并在新会议开始时计算同期的会议数量:
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
To get the original request (set of concurent sessions with max concurency) you need to run this query twice, once to get the max concurent sessions and once to get the start dates of the sessions that have max concurent times, then you must get those sessions.
要获得原始请求(一组具有最大一致性的会话),您需要运行这个查询两次,一次是获得最大一致性会话,一次是获得具有最大一致性时间的会话的开始日期,然后您必须获得那些会话。
Updated
更新
OK, so here the one single query that retrieves the max concurent sessions. I changed the test data to remove ambibuos overlaps of end and start:
好,这里有一个查询检索最大并发会话。我更改了测试数据,删除了end and start的ambibuos重叠部分:
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;
This gives a result like:
这就产生了如下结果:
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
which reads as follows: on 2009-01-02 00:00:00
there were 3 concurent sessions (3, 1 and 2) with they respective starts and ends. There is a tie, on 2009-01-03 00:00:00
there were also 3 concurent sessions (1, 2 and 4) with their respective starts and ends.
在2009年01月02日00:00有3个会议(3、1和2)分别开始和结束。有一个平局,在2009年01月03日00:00也有3个阶段(1、2和4)分别开始和结束。
Performance milage may vary. The query can be written 1 million times simpler in SQL 2005 using CTEs.
性能运费可能会有所不同。在SQL 2005中,使用CTEs可以编写100万倍的查询。
#2
2
try this (it's close to what you want I think...
试试这个(我觉得很接近你想要的……)
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
oh, If your dates have date and Time in them, then replace all the dates herein with actual date portion only (strip off the time)
哦,如果你的约会对象有日期和时间,那么把所有的日期都换成实际的日期部分(除去时间)
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
0
Another approach:
另一种方法:
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
0
This one is pretty short, easy to understand and works fine:
这个很短,很容易理解,效果很好:
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
#1
3
Updated to remove the union all
更新以删除所有的union
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
This gives the timeline of start session, with the count of concurent sessions at the moment new session starts:
这就给出了开始会议的时间线,并在新会议开始时计算同期的会议数量:
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
To get the original request (set of concurent sessions with max concurency) you need to run this query twice, once to get the max concurent sessions and once to get the start dates of the sessions that have max concurent times, then you must get those sessions.
要获得原始请求(一组具有最大一致性的会话),您需要运行这个查询两次,一次是获得最大一致性会话,一次是获得具有最大一致性时间的会话的开始日期,然后您必须获得那些会话。
Updated
更新
OK, so here the one single query that retrieves the max concurent sessions. I changed the test data to remove ambibuos overlaps of end and start:
好,这里有一个查询检索最大并发会话。我更改了测试数据,删除了end and start的ambibuos重叠部分:
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;
This gives a result like:
这就产生了如下结果:
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
which reads as follows: on 2009-01-02 00:00:00
there were 3 concurent sessions (3, 1 and 2) with they respective starts and ends. There is a tie, on 2009-01-03 00:00:00
there were also 3 concurent sessions (1, 2 and 4) with their respective starts and ends.
在2009年01月02日00:00有3个会议(3、1和2)分别开始和结束。有一个平局,在2009年01月03日00:00也有3个阶段(1、2和4)分别开始和结束。
Performance milage may vary. The query can be written 1 million times simpler in SQL 2005 using CTEs.
性能运费可能会有所不同。在SQL 2005中,使用CTEs可以编写100万倍的查询。
#2
2
try this (it's close to what you want I think...
试试这个(我觉得很接近你想要的……)
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
oh, If your dates have date and Time in them, then replace all the dates herein with actual date portion only (strip off the time)
哦,如果你的约会对象有日期和时间,那么把所有的日期都换成实际的日期部分(除去时间)
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
0
Another approach:
另一种方法:
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
0
This one is pretty short, easy to understand and works fine:
这个很短,很容易理解,效果很好:
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