2012-07-16 9 views
7

So che questo può sembrare un po 'confuso, sto solo cercando di contemplare il modo migliore di farlo passare! Ho postato questo su un paio di forum ora, ma non mi sembra che abbia avuto fortuna. Spero che qualcuno possa offrire alcuni suggerimenti su come farlo.Lista orari disponibili da 2 DateTimes

tabella di esempio (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 

Fondamentalmente, voglio entrare 2 volte data, come ad esempio 16/07/2012 08:30 alle 13:30 16/07/2012, e interrogherà la mia tabella di esempio sopra e restituirà 'disponibili' volte, IE, mi piacerebbe che producesse quanto segue.

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 

La mia domanda è, è tutto possibile in SQL? Ho provato a pensare a come farlo in VB e anche io sto lottando con quello. La mia idea/tentativo è stato utilizzato fn_daterange di Ron Savage (come illustrato di seguito)

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 

stavo andando poi a chiamare utilizzando il sotto, ma il mio problema è, me.DateEnded è al di fuori di dr.enddate e quindi la occurance sarebbe '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 

qualcuno può forse suggerire un modo migliore di fare questo o si spera di offrire una soluzione per il modo in cui sto cercando di farlo al momento?

Grazie in anticipo!

+0

Che ora è '16/07/2012 09: 90'? –

+0

Tim Schmelte, Easter Egg =) –

+0

Quali dbms state usando, SQL-Server (quale versione)? Fondamentalmente hai un datetime di inizio e un datetime di fine (quindi un TimeSpan.) Ora vuoi trovare le mezze ore libere (non prenotate) .Sempre corretto? –

risposta

0

Non ho idea di come risolvere il problema utilizzando gli insiemi ma il seguente approccio basato sul cursore dovrebbe funzionare. Questo è come si farebbe in VB o 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 

La seguente chiamata ora ad ottenere il risultato che stai cercando sui vostri dati di test.

SELECT * FROM dbo.GetAvailableTimes('20120716 8:30', '20120716 13:30') 

Ho anche ipotizzato che ci potrebbe essere più di una stanza e siete alla ricerca di orari disponibili in tutti loro.

Ho solo testato rapidamente la funzione, quindi sono abbastanza sicuro che ci siano ancora alcuni casi di confine che non sono stati adeguatamente indirizzati. Ma dovresti avere l'idea.

0

Mi avvicinerei a questo con la seguente logica. Crea un record per il periodo compreso tra l'ora di inizio desiderata e la prima ora di inizio che vedi nei dati, se presenti. Crea un record per il periodo compreso tra l'ora di fine desiderata e l'ultima volta che vedi nei dati, se presenti. Quindi, crea i record intermedi per le ore che hai.

La seguente query ha questa idea. Non sono sicuro che funzioni quando l'ora di inizio e di fine desiderata si trova nel bel mezzo di un periodo prenotato.

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

L'ultima sottoquery è piuttosto complicata. Sta facendo un auto join per ottenere l'equivalente della funzione lead().

2

Credo di avere una soluzione funzionante in SQL. Ciò presuppone che i dati in tbl_Bookings siano coerenti, ovvero che non vi siano sovrapposizioni di ore di inizio/fine per una data stanza. Probabilmente un modo più semplice, ma il trucco era quello di ordinare le prenotazioni e abbinare gli orari finali con i seguenti orari di inizio.Ci sono due query aggiuntive sincronizzate per ottenere qualsiasi intervallo dopo lo specifico Start ma prima della prima prenotazione. Allo stesso modo per End.

EDIT: Aggiunto WHERE NOT EXISTS guardie alle ultime due query in caso @Start o @End caduto all'interno di un intervallo di prenotazione.

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 

Lavorare SqlFiddle

+1

+1 per avermi introdotto in SQLFiddle – Qpirate

+0

Solo per "completezza" l'ho duplicato in [data.se] [qui] (http://data.stackexchange.com/stackoverflow/query/75613/so11511634). –