7

我知道这可能会让人有些困惑,我只是想考虑一下最好的表达方式!我现在已经在几个论坛上发布了这个,但我似乎没有任何运气。希望有人可以就如何做到这一点提供一些建议。

示例表 (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

然后我打算使用下面的方法来调用它,但我的问题是,me.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

任何人都可以提出更好的方法,或者希望为我目前尝试的方式提供解决方案吗?

提前致谢!

4

3 回答 3

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

于 2012-07-16T21:00:59.617 回答
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() 函数等效的功能。

于 2012-07-16T20:58:54.643 回答
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')

我还假设可能有不止一个房间,并且您正在寻找所有房间的可用时间。

我只是快速测试了该功能,因此我很确定仍有一些边界情况没有得到妥善解决。但你应该明白这一点。

于 2012-07-16T20:46:25.940 回答