如何在SQL Server中选择给定日期和房间的房间时间?

时间:2017-02-16 18:38:00

标签: sql sql-server sql-server-2008 tsql sql-server-2012

我有预订'具有以下字段的表

ReservationID   int
ReservationDateFrom datetime
ReservationDateTo   datetime
ReservationRoom int

我想创建一个sp,它可以选择可用的房间以及给定日期和房间的可用时间。这是我到目前为止所做的工作,但它并没有起作用。如果在给定日期没有房间时间表,并且只返回2个日期时间范围之间的可用房间时间表,则它不会返回任何内容。

CREATE PROCEDURE [dbo].[sp_GetAvailableSchedules](
@date date,
@room int
)
AS 
BEGIN
    select ReservationID, ReservationDateFrom, ReservationDateTo, ReservationRoom 
    from Reservation r
    where ReservationRoom = @room and  @date = CONVERT(date, ReservationDateFrom)
    union all
        select NULL, ReservationDateTo,
               lead(ReservationDateFrom) over (partition by ReservationRoom order by ReservationDateFrom),
               ReservationRoom
        from reservation r
        where ReservationRoom = @room and @date = CONVERT(date, ReservationDateFrom)
END

示例数据:

ReservationID     ReservationDateFrom        ReservationDateTo       ReservationRoom     
      1         2017-01-02 00:00:00.000    2017-01-02 02:00:00.000         14              
      2         2017-01-02 04:00:00.000    2017-01-02 05:00:00.000         14              
      3         2017-01-02 06:00:00.000    2017-01-02 08:00:00.000         14              
      4         2017-01-02 08:30:00.000    2017-01-02 09:30:00.000         14              
      5         2017-01-02 09:50:00.000    2017-01-02 11:00:00.000         14  
      6         2017-01-02 13:00:00.000    2017-01-02 15:00:00.000         14         

执行时的预期输出

EXEC sp_GetAvailableSchedules '2017-01-02', 14

TimeIn      TimeOut        Minutes
 02:00       04:00           120
 05:00       06:00            60
 08:00       08:30            30
 09:30       09:50            20
 11:00       13:00           120
 15:00       24:00           540

下面的sql server 2012的解决方案可以,但如果它可以在至少sql server 2008上运行会更好。

进度更新

我已经尝试将@ zerox981的答案整合到我的sp中,所以

create PROCEDURE [dbo].[sp_GetAvailableSchedules](
@date datetime,
@room int
)
AS 
BEGIN

 with data as
 (
    SELECT *,
        ISNULL(
            (select top 1 ReservationDateFrom from Reservation where ReservationRoom =a.ReservationRoom and ReservationDateFrom> a.ReservationDateTo order by ReservationDateFrom)
            , @date+1) as next 
    from Reservation a
    where ReservationRoom = @room
 )
 select
    cast(Reservationdateto as time) TimeIn,
    cast(next as time) [TimeOut], 
    datediff(mi,ReservationDateTo, next) [Minutes]
 from data

END

如果这是我插入的数据,我发现了很多问题

select * from Reservation

ReservationID ReservationDateFrom       ReservationDateTo      ReservationRoom
34             2017-02-17 13:00:00.000  2017-02-17 15:00:00.000   6003
35             2017-02-17 09:00:00.000  2017-02-17 12:00:00.000   6003
36             2017-02-18 12:00:00.000  2017-02-18 14:00:00.000   6003

案例1 - 当日期和房间内有许多预订时

declare @date datetime = '2017-02-17 00:00:00.000' , @room int = 6003
exec [sp_GetAvailableSchedules] @date, @room

预期结果

TimeIn TimeOut Minutes
00:00   09:00  540
12:00   13:00   60
15:00   24:00  540 

实际结果

 TimeIn               TimeOut           Minutes
 15:00:00.0000000   12:00:00.0000000    1260
 12:00:00.0000000   13:00:00.0000000    60
 14:00:00.0000000   00:00:00.0000000    -840

案例2 - 当日期和房间内有1次预订时

declare @date datetime = '2017-02-18 00:00:00.000' , @room int = 6003
exec [sp_GetAvailableSchedules] @date, @room

预期结果

TimeIn TimeOut Minutes
00:00   12:00  720
14:00   24:00  600

实际结果

 TimeIn               TimeOut           Minutes
15:00:00.0000000    12:00:00.0000000    1260
12:00:00.0000000    13:00:00.0000000    60
14:00:00.0000000    00:00:00.0000000    600

案例3 - 如果在指定日期和房间内没有预订(全天应该可用)

declare @date datetime = '2017-02-20 00:00:00.000' , @room int = 500
exec [sp_GetAvailableSchedules] @date, @room

预期结果(全天可用)

TimeIn TimeOut Minutes
00:00   24:00  1440

实际结果为空

3 个答案:

答案 0 :(得分:3)

我认为您在测试数据或预期输出中存在错误。边框也没有定义等。

这是你可以从

开始的
if object_id('tempdb..#r') is not null
    drop table #r
create table #r ( ReservationID int ,   ReservationDateFrom datetime   ,     ReservationDateTo  datetime,    ReservationRoom  int)

--insert into #r values(1,         '2017-01-02 00:00:00.000'    ,'2017-01-02 02:00:00.000' ,       14   )           
insert into #r values(2,         '2017-01-02 04:00:00.000'    ,'2017-01-02 05:00:00.000' ,        14  )            
insert into #r values(3,         '2017-01-02 06:00:00.000'    ,'2017-01-02 08:00:00.000' ,        14  )            
insert into #r values(4,         '2017-01-02 08:30:00.000'    ,'2017-01-02 09:30:00.000' ,        14  )            
insert into #r values(5,         '2017-01-02 09:50:00.000'    ,'2017-01-02 11:00:00.000' ,        14  )
insert into #r values(6,         '2017-01-02 13:00:00.000'    ,'2017-01-02 15:00:00.000' ,        14  )   

declare @dt datetime ='2017/01/02', @room int = 14

; with data as
(
    select ReservationDateFrom, ReservationDateTo,ReservationRoom
    from #r
    where ReservationRoom = @room
        and ReservationDateFrom between @dt and @dt+1
    union 
    select @dt,@dt,@room
    union
    select @dt+1,@dt+1,@room
)
,mid as
(
    select *,
        (select top 1 ReservationDateFrom 
        from data 
        where ReservationRoom =a.ReservationRoom 
            and ReservationDateFrom> a.ReservationDateTo 
        order by ReservationDateFrom) [next]
    from data a         
 )
 select
    cast(Reservationdateto as time) TimeIn,
    cast(next as time) [TimeOut], 
    datediff(mi,ReservationDateTo, next) [Minutes]
 from mid
 where datediff(mi,ReservationDateTo, next)>0

您可以在此处进行测试:http://rextester.com/IZH14294

答案 1 :(得分:1)

假设没有重叠,并且分钟是最小的时间单位,当然还有Numbers表,对于SQL Server 2012,这应该有效:

declare @date datetime = '20170102', @room int = 14
;with x as (
    select num.n, isnull(lag(num.n) over(order by num.n),-1) n_prev
    from utility.numbers num
    left join Reservation t on num.n between datediff(minute, @date, reservationdatefrom) and datediff(minute, @date, reservationdateto) and t.reservationroom = @room
    where num.n < 1440 and num.n > 0
    and t.reservationid is null
)
select *, cast(dateadd(minute, n-1, @date) as time), cast(dateadd (minute, isnull(lead(n_prev+1) over(order by n), 1440), @date) as time),
isnull(lead(n_prev+1) over(order by n), 1440) - (n-1)
from x 
where n <> n_prev+1

(2008年的解决方案将包括自我加入。非常丑陋的IMO)。

有关Numbers表的更多信息,请点击此处:

https://dba.stackexchange.com/questions/11506/why-are-numbers-tables-invaluable

答案 2 :(得分:0)

我在表格中添加了一些示例数据并修复了日期。这不是一个复杂的解决方案,但希望它能为您提供有关如何获得所需结果的一些想法。

您可以使用循环或公用表表达式管理结果。 我使用了一个cte和一个声明的表。您可以在我的解决方案中删除声明的表,并将递归部分加入到与基本查询类似的select中,如果需要的话。

基本思路是将预约的结束时间和下一个的开始时间放在同一行。

我只做了选择部分,我会让它成为你的程序。

CREATE TABLE reservation (
    ReservationID int,
    ReservationDateFrom datetime,
    ReservationDateTo datetime,
    ReservationRoom int
)

INSERT INTO reservation
(
    ReservationID, ReservationDateFrom, ReservationDateTo, ReservationRoom
)
VALUES
(1, '2017-01-02 00:00:00.000', '2017-01-02 02:00:00.000', 14),              
(2, '2017-01-02 04:00:00.000', '2017-01-02 05:00:00.000', 14),              
(3, '2017-01-02 06:00:00.000', '2017-01-02 08:00:00.000', 14),              
(4, '2017-01-02 08:30:00.000', '2017-01-02 09:30:00.000', 14),              
(5, '2017-01-02 09:50:00.000', '2017-01-02 11:00:00.000', 14),  
(6, '2017-01-02 13:00:00.000', '2017-01-03 15:00:00.000', 14),
(6, '2017-01-03 16:00:00.000', '2017-01-03 17:00:00.000', 14),
(7, '2017-01-04 13:00:00.000', '2017-01-03 15:00:00.000', 14),
(8, '2017-01-02 13:00:00.000', '2017-01-03 15:00:00.000', 15)


DECLARE @date date = '2017-01-02';
DECLARE @room int = 14;
DECLARE @res_table TABLE
(
    RowNumber int,
    IsReserved bit,
    ReservationDateFrom datetime,
    ReservationDateTo datetime
);

INSERT INTO @res_table
SELECT
    ROW_NUMBER() OVER(PARTITION BY ReservationRoom ORDER BY ReservationDateFrom) as RowNumber,
    1 as IsReserved,
    ReservationDateFrom,
    ReservationDateTo
FROM reservation
WHERE @date BETWEEN CAST(ReservationDateFrom as date) AND CAST(ReservationDateTo as date)
        AND @room = ReservationRoom;

WITH cte AS
(
    SELECT
        RowNumber,
        IsReserved,
        ReservationDateFrom,
        ReservationDateTo
    FROM @res_table

    UNION ALL

    SELECT
        cte.RowNumber * -1 as RowNumber,
        cast(0 as bit) as IsReserved,
        cte.ReservationDateTo as ReservationDateFrom,
        rt.ReservationDateFrom as ReservationDateTo
    FROM cte
    INNER JOIN @res_table rt ON
        rt.RowNumber = cte.RowNumber + 1

)
SELECT
    cte.ReservationDateFrom as FreeFromDate,
    cte.ReservationDateTo as FreeToDate,
    DATEDIFF(mi, cte.ReservationDateFrom, cte.ReservationDateTo) as FreeMinutes
FROM cte
WHERE IsReserved = 0
ORDER BY FreeFromDate