【问题标题】:How to select the time a room is available given date and room in SQL Server?如何在 SQL Server 中给定日期和房间选择房间可用的时间?
【发布时间】:2017-02-16 18:37:59
【问题描述】:

我有包含以下字段的“预订”表

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

实际结果为空

【问题讨论】:

  • 您的样本数据是否正确,尤其是 5 和 6 ReservationID ?
  • 有什么问题吗? 5 是上午 9:50 到 11:00,6 是下午 1:00 -3:00
  • 第 6 行看起来应该是 2017-01-03 而不是 2017-01-02 的 FromDate。
  • 那是一个错字。 Tnx 指出这一点。

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


【解决方案1】:

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

你可以从这里开始

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

【讨论】:

  • 我刚刚发现这行不通。尝试删除第一条记录,它不会显示第一条记录之前的可用计划。 00:00:00 05:00:00 300. 当房间没有在给定日期预订时,它也不起作用。当它应该返回整个日期时,它什么也不返回。就像您尝试将房间从 14 换到 15 时一样
  • 我编辑了答案,现在对那些测试用例有用吗?如果您希望它返回 24:00 作为结束时间,您必须将时间转换为 varchar 并添加一个案例句。
【解决方案2】:

假设没有重叠,并且分钟作为最小时间单位,当然还有 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)。

有关数字表的更多信息,请查看此处:

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

【讨论】:

  • 如何将它集成到我的 sp?我不知道这些对象在我的 sp 中代表什么。我知道@t 是我的 sp 中的预订表。 utility.numbers 代表什么?它给了我错误
  • 是的,@t 代表 Reservation 表,我进行了编辑以反映这一点。我还包含了一个 SE 链接,其中包含有关 Numbers 表的更多信息(如何创建和使用它)。
  • 你能给我确切的脚本来创建和插入你所说的数字表吗?我很乐意测试它。老实说,我不明白这是如何工作的。
  • 您是否检查了我添加到答案中的链接?如果没有,请这样做。
【解决方案3】:

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

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

基本思想是在同一行获取一个预订的结束时间和下一个预订的开始时间。

我只做了选择部分,我会把它变成一个程序给你。

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

【讨论】:

  • 您错过了此解决方案的最后一条记录。 2017-01-02 15:00:00.000 2017-01-02 00:00:00.000
猜你喜欢
  • 2014-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-29
  • 2013-01-17
  • 2021-07-07
  • 2012-05-31
  • 2014-08-08
相关资源
最近更新 更多