列出2 DateTimes的可用时间

时间:2012-07-16 19:57:00

标签: .net sql vb.net sql-server-2008

我知道这个可能会让人感到有些困惑,我只是想着考虑最好的方法来解决它!我现在在几个论坛上发布了这个,但我似乎没有任何运气。希望有人可以就如何做到这一点提出一些建议。

示例表(tbl_Bookings)

ID  DateStarted       DateEnded         RoomID
1   16/07/2012 09:00  16/07/2012 10:00    1
2   16/07/2012 12:00  16/07/2012 13:00    1

基本上,我想输入2个日期时间,例如16/07/2012 08:30和16/07/2012 13:30,它将查询我上面的示例表并返回“可用”时间,IE,我希望它输出以下内容。

16/07/2012 08:30 - 16/07/2012 09:00
16/07/2012 10:00 - 16/07/2012 12:00
16/07/2012 13:00 - 16/07/2012 13:30

我的问题是,这完全可以在SQL中使用吗?我试着想一想如何在VB中做到这一点,我也在努力解决这个问题。我的想法/尝试一直在使用Ron Savage的fn_daterange(如下所示)

if exists (select * from dbo.sysobjects where name = 'fn_daterange') drop function fn_daterange;
go
create function fn_daterange
(
@MinDate as datetime,
@MaxDate as datetime,
@intval  as datetime
)
returns table
as
return
WITH times (startdate, enddate, intervl) AS
(
SELECT @MinDate as startdate, @MinDate + @intval - .0000001 as enddate, @intval as intervl
UNION ALL
SELECT startdate + intervl as startdate, enddate + intervl as enddate, intervl as intervl
FROM times
WHERE startdate + intervl <= @MaxDate
)
select startdate, enddate from times;
go

然后我会用下面的方法来调用它,但我的问题是,我.DateEnded不在dr.enddate中,因此出现的次数为'0':

SELECT dr.startdate, dr.enddate, count(me.DateStarted) as occurrence
FROM fn_daterange('16/07/2012 08:30', '16/07/2012 13:30', '00:30:00' ) dr
LEFT OUTER JOIN tbl_Bookings me
ON me.DateStarted BETWEEN dr.startdate AND dr.enddate
AND me.DateEnded BETWEEN dr.startdate AND dr.enddate)
GROUP BY dr.startdate, dr.enddate

任何人都可以建议一种更好的方法来实现这一目标,或者希望能够提供一种解决方案来解决我目前正在尝试的方式吗?

提前致谢!

3 个答案:

答案 0 :(得分:2)

我相信我在SQL中有一个可行的解决方案。这假设tbl_Bookings中的数据是一致的,即给定房间没有开始/结束时间重叠。可能是一种更简单的方法,但诀窍是订购预订并将结束时间配对以下开始时间。在指定的Start之后但在第一次预订之前,有两个额外的查询联合以获取任何间隔。同样适用于End

编辑:在WHERE NOT EXISTS@Start落入预定时间间隔内的最后两个查询中添加了@End个警卫。

DECLARE @Start DateTime = '05/07/2012 08:30'
DECLARE @End DateTime = '05/07/2012 13:30'

;WITH Bookings (RoomId, RowNum, Started, Ended) AS (
     SELECT RoomId,
     ROW_NUMBER() OVER (PARTITION BY RoomId ORDER BY DateStarted) AS RowNum,
     DateStarted, DateEnded
     FROM tbl_Bookings
)
SELECT RoomId, B.Ended AS S, C.Started AS E
FROM Bookings B
CROSS APPLY (
    SELECT B2.Started FROM Bookings B2
    WHERE B2.RowNum = B.RowNum + 1
    AND B2.Started <= @End
    AND B2.RoomId = B.RoomId
) C
WHERE B.Ended >= @Start

UNION

-- Show any available time from @Start until the next DateStarted, unless @Start 
-- falls within a booked interval.
SELECT RoomId, @Start, MIN(DateStarted)
FROM tbl_Bookings
WHERE DateStarted > @Start
    AND NOT EXISTS (
        SELECT 1 FROM Bookings WHERE Started < @Start AND Ended > @Start
    )
GROUP BY RoomId

UNION

-- Show any available time from the last DateEnded to @End, unless @End 
-- falls within a booked interval.
SELECT RoomId, MAX(DateEnded), @End
FROM tbl_Bookings
WHERE DateEnded < @End
    AND NOT EXISTS (
        SELECT 1 FROM Bookings WHERE Started < @End AND Ended > @End
    )
GROUP BY RoomId

工作SqlFiddle

答案 1 :(得分:0)

我不知道如何使用集合解决问题,但以下基于游标的方法应该有效。这是你在VB或C#中的方式:

CREATE FUNCTION GetAvailableTimes
(
    @MinDate datetime,
    @MaxDate datetime
)
RETURNS @result TABLE
(
    DateStarted datetime,
    DateEnded datetime,
    RoomID int
)
AS
BEGIN
    DECLARE @DateStarted datetime
    DECLARE @DateEnded datetime
    DECLARE @CurrentDate datetime
    DECLARE @RoomID int
    DECLARE @CurrentRoom int

    DECLARE c CURSOR FOR
        SELECT DateStarted, DateEnded, RoomID 
        FROM tbl_Bookings 
        WHERE DateStarted BETWEEN @MinDate AND @MaxDate
            OR DateEnded BETWEEN @MinDate AND @MaxDate
        ORDER BY RoomID, DateStarted

    SET @CurrentRoom = 0

    OPEN c
    FETCH NEXT FROM c
    INTO @DateStarted, @DateEnded, @RoomID

    WHILE @@FETCH_STATUS = 0
    BEGIN

        IF @CurrentRoom <> @RoomID BEGIN
            IF @CurrentRoom <> 0 AND @CurrentDate < @MaxDate BEGIN
                INSERT INTO @result VALUES (@CurrentDate, @MaxDate, @CurrentRoom)
            END

            SET @CurrentDate = @MinDate
            SET @CurrentRoom = @RoomID
        END

        IF @CurrentDate < @DateStarted BEGIN
            INSERT INTO @result VALUES (@CurrentDate, @DateStarted, @CurrentRoom)
        END

        SET @CurrentDate = @DateEnded

        FETCH NEXT FROM c
        INTO @DateStarted, @DateEnded, @RoomID
    END
    CLOSE c
    DEALLOCATE c

    IF @CurrentRoom <> 0 AND @CurrentDate < @MaxDate BEGIN
        INSERT INTO @result VALUES (@CurrentDate, @MaxDate, @CurrentRoom)
    END

    RETURN
END

以下调用现在将为您提供测试数据所需的结果。

SELECT * FROM dbo.GetAvailableTimes('20120716 8:30', '20120716 13:30')

我还假设可能有多个房间,而且您正在寻找所有房间的可用时间。

我只是很快就测试了这个功能,所以我很确定还有一些边境案例没有得到妥善处理。但你应该明白这一点。

答案 2 :(得分:0)

我会用以下逻辑来解决这个问题。创建所需开始时间与您在数据中看到的第一个开始时间之间的时间段的记录(如果有)。创建所需结束时间与您在数据中看到的最后结束时间之间的时间段的记录(如果有)。然后,根据您的时间创建中间记录。

以下查询有这个想法。当期望的开始和结束时间在预订的时间段中间时,我不确定它是否有效。

with const as (select @starttime as StartTime, @endtime as EndTime)
select *
from ((Select c.StartTime, MIN(DateStarted), RoomId
       from tbl_Bookings b cross join const c
       where b.DateStarted >= c.StartTime
       group by RoomID
       having c.StartTime <> MIN(DateStarted)
      ) union all
      (Select max(DateEnded), c.EndTime, RoomId
       from tbl_Bookings b cross join const c
       where b.DateEnded <= c.EndTime
       group by RoomID
       having c.EndTime <> max(DateEnded)
      ) union all
      (select *
       from (select b.DateEnded as DateStarted, min(b.DateStarted) as DateEnded
             from tbl_Bookings b join
                  tbl_Bookings bnext
                  on b.RoomId = bnext.RoomId and
                     bnext.DateStarted > b.DateStarted cross join
                  const c
             where b.DateStarted < c.endtime and
                   b.DateEnded > c.StartTime and
                   bnext.DateStart < c.EndTime and
                   bnext.DateEnded > c.StartTime
             group by b.DateEnded
            ) b cross join const c
       where DateStarted <> DateEnded
      )
     )

最后一个子查询相当复杂。它正在进行自联接以获得与lead()函数等效的函数。