簡體   English   中英

如何找到表中不存在的值?

[英]How can I find a value that doesn't exist in a table?

我有一個名為Bookings的簡化表,它有兩列BookDateBookSlot BookDate列僅包含日期(沒有時間), BookSlot列將包含從0到1410(包括0和1410) 的30分鍾間隔的一天中的時間 (即600 =上午10:00)

如何在未經過循環的情況下找到未來可用的第一個插槽(未預訂)?

這是表定義和測試數據:

Create Table Bookings(
    BookDate DateTime Not Null,
    BookSlot Int Not Null
)
Go

Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',0);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',30);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',60);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-01',630);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-02',60);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-02',90);
Insert Into Bookings(BookDate,BookSlot) Values('2014-07-02',120);

我想要一種方法來返回表中沒有的第一個可用插槽,以及將來(基於服務器時間)。

根據以上測試數據:

  • 如果當前服務器時間是7月1日00:10 am ,結果應該是7月1日,90分鍾 (01:30 am)。
  • 如果當前服務器時間是7月2日,01:05 am ,結果應該是7月2日,150分鍾 (02:30 am)。

如果將來沒有預訂,該功能將簡單地返回將來最接近的半小時。

-

這個SQL小提琴就在這里:

http://sqlfiddle.com/#!6/0e93d/1

這有點復雜,但試試這個:

WITH DATA 
     AS (SELECT *, 
                Row_number() 
                  OVER ( 
                    ORDER BY BOOKDATE, BOOKSLOT) RN 
         FROM   BOOKINGS) 
SELECT CASE 
         WHEN T.BOOKSLOT = 1410 THEN Dateadd(DAY, 1, BOOKDATE) 
         ELSE BOOKDATE 
       END Book_Date, 
       CASE 
         WHEN T.BOOKSLOT = 1410 THEN 0 
         ELSE BOOKSLOT + 30 
       END Book_Slot 
FROM   (SELECT TOP 1 T1.* 
        FROM   DATA T1 
               LEFT JOIN DATA t2 
                      ON t1.RN = T2.RN - 1 
        WHERE  t2.BOOKSLOT - t1.BOOKSLOT > 30 
                OR ( t1.BOOKDATE != T2.BOOKDATE 
                     AND ( t2.BOOKSLOT != 0 
                            OR t1.BOOKSLOT != 630 ) ) 
                OR t2.BOOKSLOT IS NULL)T 

這是SQL小提琴示例。

說明

該解決方案包含兩部分:

  1. 將每一行與下一行進行比較並檢查間隙(在SQL 2012中可以更輕松地完成)
  2. 添加半小時來創建下一個插槽,這包括在需要時移動到第二天。

編輯

在查詢中添加了TOP 1 ,以便只按請求返回第一個槽。

更新

這是更新版本,包括2個新元素(獲取當前日期+時間和處理空表):

DECLARE @Date DATETIME = '2014-07-01', 
        @Slot INT = 630 
DECLARE @time AS TIME = Cast(Getdate() AS TIME) 

SELECT @Slot = Datepart(HOUR, @time) * 60 + Round(Datepart(MINUTE, @time) / 30, 
                                            0) * 30 
                      + 30 

SET @Date = Cast(Getdate() AS DATE)


;WITH DATA 
     AS (SELECT *, 
                Row_number() 
                  OVER ( 
                    ORDER BY BOOKDATE, BOOKSLOT) RN 
         FROM   BOOKINGS 
         WHERE  BOOKDATE > @Date 
                 OR ( BOOKDATE = @Date 
                      AND BOOKSLOT >= @Slot )) 
SELECT TOP 1 BOOK_DATE, 
             BOOK_SLOT 
FROM   (SELECT CASE 
                 WHEN RN = 1 
                      AND NOT (@slot = BOOKSLOT 
                      AND @Date = BOOKDATE) THEN @Date 
                 WHEN T.BOOKSLOT = 1410 THEN Dateadd(DAY, 1, BOOKDATE) 
                 ELSE BOOKDATE 
               END Book_Date, 
               CASE 
                 WHEN RN = 1 
                      AND NOT (@slot = BOOKSLOT 
                      AND @Date = BOOKDATE) THEN @Slot 
                 WHEN T.BOOKSLOT = 1410 THEN 0 
                 ELSE BOOKSLOT + 30 
               END Book_Slot, 
               1   AS ID 
        FROM   (SELECT TOP 1 T1.* 
                FROM   DATA T1 
                       LEFT JOIN DATA t2 
                              ON t1.RN = T2.RN - 1 
                WHERE  t2.BOOKSLOT - t1.BOOKSLOT > 30 
                        OR ( t1.BOOKDATE != T2.BOOKDATE 
                             AND ( t2.BOOKSLOT != 0 
                                    OR t1.BOOKSLOT != 1410 ) ) 
                        OR t2.BOOKSLOT IS NULL)T 
        UNION 
        SELECT @date AS bookDate, 
               @slot AS BookSlot, 
               2     ID)X 
ORDER  BY X.ID 

玩弄SQL小提琴 ,讓我知道你的想法。

以下是一種方法,允許將來預訂最多256天,並允許空的預訂表。 我假設您使用的是SQL Server 2005,因為您的BookDate是dateTime而不是date。
在任何情況下,您可以考慮將插槽存儲為完整的日期時間而不是單獨的列。 這將有助於查詢和提高性能。

DECLARE @now DATETIME = '2014-07-01 00:10:00'; 

WITH T4 
     AS (SELECT N 
         FROM   (VALUES(0), 
                       (0), 
                       (0), 
                       (0), 
                       (0), 
                       (0), 
                       (0), 
                       (0)) AS t(N)), 
     T256 
     AS (SELECT Row_number() 
                  OVER( 
                    ORDER BY (SELECT 0)) - 1 AS n 
         FROM   T4 AS a 
                CROSS JOIN T4 AS b 
                CROSS JOIN T4 AS c), 
     START_DATE 
     AS (SELECT Dateadd(DAY, Datediff(DAY, '', @now), '') AS start_date), 
     START_TIME 
     AS (SELECT Dateadd(MINUTE, Datediff(MINUTE, '', @now) / 30 * 30, '') AS 
                start_time), 
     DAILY_INTERVALS 
     AS (SELECT N * 30 AS interval 
         FROM   T256 
         WHERE  N < 48) 
SELECT TOP (1) Dateadd(DAY, future_days.N, START_DATE) AS BookDate, 
               DAILY_INTERVALS.INTERVAL                AS BookSlot 
FROM   START_DATE 
       CROSS APPLY START_TIME 
       CROSS APPLY DAILY_INTERVALS 
       CROSS APPLY T256 AS future_days 
WHERE  Dateadd(MINUTE, DAILY_INTERVALS.INTERVAL, 
              Dateadd(DAY, future_days.N, START_DATE)) > START_TIME 
       AND NOT EXISTS(SELECT * 
                      FROM   DBO.BOOKINGS 
                      WHERE  BOOKDATE = START_DATE 
                             AND BOOKSLOT = DAILY_INTERVALS.INTERVAL) 
ORDER  BY BOOKDATE, 
          BOOKSLOT; 

看到這個SQL小提琴

在SQL Server 2012及更高版本中,您可以使用lead()函數。 由於所有的邊界條件,邏輯有點復雜。 我認為這抓住了它:

select top 1
       (case when BookSlot = 1410 then BookDate else BookDate + 1 end) as BookDate,
       (case when BookSlot = 1410 then 0 else BookSlot + 30 end) as BookSlot
from (select b.*,
             lead(BookDate) over (order by BookDate) as next_dt,
             lead(BookSlot) over (partition by BookDate order by BookSlot) as next_bs
      from bookings b
     ) b
where (next_bs is null and BookSlot < 1410 or
       next_bs - BookSlot > 30 or
       BookSlot = 1410 and (next_dt <> BookDate + 1 or next_dt = BookDate and next_bs <> 0)
      )
order by BookDate, BookSlot;

使用計數表生成6周內原始可用預訂位置列表(可調節如下):

declare @Date as date = getdate();
declare @slot as int  = 30 * (datediff(n,@Date,getdate()) /30);

with 
slots as (
    select (ROW_NUMBER() over (order by s)-1) * 30 as BookSlot
    from(
        values (1),(1),(1),(1),(1),(1),(1),(1) -- 4 hour block
    )slots(s)
    cross join (
        values (1),(1),(1),(1),(1),(1) -- 6 blocks of 4 hours each day
    )QuadHours(t)
)
,days as (
    select (ROW_NUMBER() over (order by s)-1) + getdate() as BookDate
    from (
        values (1),(1),(1),(1),(1),(1),(1)  -- 7 days in a week
    )dayList(s)
    cross join (
        -- set this to number of weeks out to allow bookings to be made
        values (1),(1),(1),(1),(1),(1)      -- allow 6 weeks of bookings at a time
    )weeks(t)
)
,tally as (
    select
         cast(days.BookDate as date) as BookDate
        ,slots.BookSlot              as BookSLot
    from slots
    cross join days
)

select top 1 
     tally.BookDate
    ,tally.BookSlot
from tally
left join #Bookings  book
   on tally.BookDate    = book.BookDate
  and tally.BookSlot    = book.BookSlot
where book.BookSlot is null
  and ( tally.BookDate > @Date or tally.BookSlot > @slot )
order by tally.BookDate,tally.BookSlot;

go

嘗試這個:

SELECT a.bookdate, ((a.bookslot/60.)+.5) * 60
FROM bookings a LEFT JOIN bookings b 
ON a.bookdate=b.bookdate AND (a.bookslot/60.)+.50=b.bookslot/60.
WHERE b.bookslot IS null

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM