2014-07-24 2 views
5

Ich habe eine vereinfachte Tabelle namens Bookings, die zwei Spalten BookDate und BookSlot hat. Die BookDate Spalte hat nur Daten (keine Zeit) und die BookSlot Spalte enthält die Uhrzeit des Tages in Intervallen von 30 Minuten von 0 bis einschließlich 1410. (d. h. 600 = 10:00 Uhr)Wie kann ich einen Wert finden, der in einer Tabelle nicht existiert?

Wie kann ich den ersten verfügbaren Steckplatz in der Zukunft finden (nicht gebucht) ohne läuft durch eine Schleife? Hier

sind die Tabellendefinition und Testdaten:

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); 

Ich mag eine Möglichkeit, die erste freie Steckplatz zurückzugeben, die nicht in der Tabelle ist, und das ist in der Zukunft (basierend auf der Serverzeit).

Basierend auf obigen Testdaten:

  • Wenn die aktuelle Serverzeit 1. Juli, 12.10 ist, soll das Ergebnis 1. Juli sein, 90min (01.30).
  • Wenn die aktuelle Serverzeit 2. Juli, 01:05 war, sollte das Ergebnis 2. Juli, 150min (02:30) sein.

Wenn es in der Zukunft keine Buchungen gibt, würde die Funktion einfach die nächste halbe Stunde in der Zukunft zurückgeben.

-

SQL Fiddle dafür ist hier:

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

+0

INSERT INTO Bookings (BookDate, BookSlot) Werte ('2014-07-01', 630); bitte erläutern Sie Bookslot Wert von 630 und nachfolgende Werte .. –

+2

OP hat das schon erklärt, die Zahl stellt den 30-Minuten-Intervall-Slot dar, wenn '600' = 10: 00 dann" 630 "wäre 10:30 Uhr – Jamiec

+0

@mmhasannn, Bitte Siehe die obige Erklärung. BookSlot ist in wenigen Minuten pro Tag. 6:30 bedeutet 10:30 Uhr. Teilen Sie einfach durch 60, um die Zeit zu bekommen. – navigator

Antwort

-1

try this:

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 
+0

Ich schrieb, dass als Kommentar, das nicht funktioniert, wenn der nächste verfügbare Steckplatz zwischen jetzt und dem ersten verwendeten Steckplatz nach ihm ist. – scragar

+0

good point jetzt behoben – Jayvee

+0

@navigator hast du diesen ausprobiert? – Jayvee

1

Es ist ein bisschen kompliziert, aber versuchen Sie dies:

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 

Hier ist das SQL fiddle Beispiel.

Erklärung

Diese Lösung besteht aus 2 Teilen:

  1. jede Zeile zur nächsten und überprüft für eine Lücke Vergleich (in SQL 2012 leichter gemacht werden)
  2. Hinzufügen einer halben Stunde, um den nächsten Slot zu erstellen, schließt dies bei Bedarf den nächsten Tag ein.

bearbeiten

Added TOP 1 in der Abfrage, so dass nur der erste Schlitz wird wie gewünscht zurückgegeben.

aktualisieren

Hier ist die aktualisierte Version, darunter 2 neue Elemente (aktuelles Datum + Uhrzeit und den Umgang mit leeren Tisch zu bekommen):

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 

mit dem SQL fiddle Spielen Sie herum und lassen Sie mich wissen, was Sie denken.

+1

Diese Lösung fehlt 2 Elemente: 1. Es überprüft nicht das aktuelle Datum und die aktuelle Uhrzeit und kann daher einen Platz in der Vergangenheit zurückgeben. 2. Es kann nicht mit einer leeren Tabelle (keine Slots) umgehen. Sind diese Probleme kritisch? Wenn ja, lass es mich wissen und ich werde sie hinzufügen. – Gidil

+0

Danke für die Mühe @Gidil. Aber ja, beides ist kritisch. Die verfügbare Buchung muss nur in der Zukunft liegen und eine Tabelle könnte theoretisch leer sein. Die tatsächliche Tabelle in der Produktion hat auch die Länge der Buchung (in 30-Minuten-Intervallen), Facility ID, wo die Buchung passiert sowie Benutzer und andere Daten. Buchungen für alle Einrichtungen sind hier gespeichert. Aber die Logik kann zu den obigen Tabellen vereinfacht werden. – navigator

+0

Ich fange mit dem leeren Tisch an, es ist relativ einfach. Das aktuelle Datum müsste in die verwendeten Codes umgewandelt werden oder wäre es vom System verfügbar? – Gidil

0

In SQL Server 2012 und höher können Sie die Funktion lead() verwenden. Die Logik ist wegen aller Randbedingungen ein wenig verschachtelt. Ich denke, das fängt es:

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; 
+0

Sind Sie sicher, dass das funktioniert? Ich habe versucht, es auf SQL Fiddle (SQL 2012) zu verwenden, aber es gab einen Fehler zurück. Könnte auch ein Fehler in SQL Fiddle sein. – Gidil

+0

Gibt Fehler "Falsche Syntax in der Nähe des Schlüsselworts 'bestellen'" – navigator

1

Im Folgenden eine Methode, die Buchungen bis zu 256 Tagen in der Zukunft ermöglichen wird, und für einen leeren Reservierungstisch ermöglichen. Ich nehme an, dass Sie SQL Server 2005 verwenden, da Ihr BookDate dateTime anstelle von date ist.
In jedem Fall sollten Sie die Slots als vollständige datetime statt als separate Spalten speichern. Das erleichtert Abfragen und verbessert die Leistung.

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; 

Sehen Sie diese SQL Fiddle

+1

Hallo @Dan, danke für die Mühe. Von dem, was ich sammeln kann, generieren Sie eine virtuelle Tabelle von 256 Tagen mit 48 Einträgen in jeder Tabelle, verbinden sie mit Buchungen und suchen dann die leeren Plätze. Es scheint ein bisschen Overkill zu sein. Ich denke, eine Schleife könnte einfach schneller sein. In meiner tatsächlichen Tabelle gibt es Dutzende von Einrichtungen, wo Buchungen vorgenommen werden können - so würde die obige Funktion einmal für jede Einrichtung aufgerufen werden, um den nächsten verfügbaren Platz zu finden. Danke trotzdem. – navigator

0

Mit einer tally Tabelle eine Liste der ursprünglich verfügbaren Buchungs Slots aus 6 Wochen (einstellbar unten) zu erzeugen:

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 
Verwandte Themen