2012-07-16 8 views
7

Sé que este puede parecer un poco confuso, ¡solo estoy tratando de contemplar la mejor manera de transmitirlo! Ya publiqué esto en un par de foros, pero parece que no estoy teniendo suerte. Con suerte, alguien puede ofrecer algunas sugerencias sobre cómo hacer esto.Lista de horarios disponibles a partir de 2 DateTimes

Ejemplo mesa (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 

Básicamente, quiero entrar 2 veces la fecha, tales como 16/07/2012 8:30 y 16/07/2012 13:30, y consultará mi tabla de ejemplo de arriba y devolverá las horas 'disponibles', IE, me gustaría que muestre lo siguiente.

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 

Mi pregunta es, ¿esto es completamente posible en SQL? Intenté pensar en cómo hacerlo en VB y también estoy luchando con eso. Mi idea/intento ha estado utilizando fn_daterange de Ron Savage (como se muestra abajo)

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 

luego iba a llamarlo por mediante el siguiente, pero mi problema es, me.DateEnded está fuera de dr.enddate y por lo tanto la ocurrencia sería '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 

alguien puede sugerir una mejor manera de hacer esto o con suerte ofrecer una solución a la manera en que yo estoy tratando de hacer en la actualidad?

¡Gracias de antemano!

+0

¿A qué hora es '16/07/2012 09: 90'? –

+0

Tim Schmelte, Easter Egg =) –

+0

¿Qué dbms está usando, SQL-Server (qué versión)? Básicamente, tiene un datetime de inicio y un datetime de finalización (por lo tanto, un TimeSpan. Ahora quiere encontrar las medias horas que son gratuitas (no reservadas). ¿Es correcto? –

Respuesta

0

No tengo idea de cómo resolver el problema utilizando conjuntos, pero el siguiente enfoque basado en cursor debería funcionar. Esta es la forma en que lo harías en 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 siguiente llamada ahora le obtener el resultado que está buscando en los datos de prueba.

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

También he supuesto que podría haber más de una habitación y está buscando tiempos disponibles en todas ellas.

Solo probé la función rápidamente, por lo tanto, estoy bastante seguro de que todavía hay algunos casos de borde que no se abordan correctamente. Pero deberías entender la idea.

0

Lo abordaría con la siguiente lógica. Cree un registro para el período comprendido entre la hora de inicio deseada y la primera hora de inicio que vea en los datos, si corresponde. Cree un registro para el período comprendido entre la hora de finalización deseada y la última hora de finalización que ve en los datos, si corresponde. Luego, crea los registros intermedios para los tiempos que tienes.

La siguiente consulta tiene esta idea. No estoy seguro de si funciona cuando la hora de inicio y finalización deseada es en el medio de un período que está reservado.

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

La última subconsulta es bastante complicada. Se está haciendo una auto unión para obtener el equivalente de la función lead().

2

Creo que tengo una solución de trabajo en SQL. Esto supone que los datos en tbl_Bookings son consistentes, es decir, no hay superposición de tiempos de inicio/finalización para una habitación determinada. Probablemente de una manera más simple, pero el truco fue ordenar las reservas y vincular las horas de finalización con los siguientes tiempos de inicio.Hay dos consultas adicionales unidas para obtener cualquier intervalo después de su Start especificado, pero antes de la primera reserva. Del mismo modo para End.

EDIT: Agregado WHERE NOT EXISTS guardias a las dos últimas consultas en caso @Start o @End cayó dentro de un intervalo reservado.

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 

Trabajando SqlFiddle

+1

+1 por presentarme en SQLFiddle – Qpirate

+0

Solo por "completitud" he duplicado esto en [data.se] [aquí] (http://data.stackexchange.com/stackoverflow/query/75613/so11511634). –

Cuestiones relacionadas