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

基本的に、2012 年 7 月 16 日 08:30 と 2012 年 7 月 16 日 13:30 のような 2 つの日時を入力すると、上記の例のテーブルがクエリされ、「利用可能な」時間が返されます。次のように出力します。

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指定した後、最初の予約の前に間隔を取得するために、結合された 2 つの追加のクエリがあります。についても同様ですEnd

編集:WHERE NOT EXISTS場合に備えて、最後の 2 つのクエリにガードを追加した@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

セットを使用して問題を解決する方法はわかりませんが、次のカーソルベースのアプローチが機能するはずです。これは、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 に答える
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 に答える