2010-11-12 11 views
5

Tengo un sistema de reserva en el que tengo que seleccionar cualquier habitación disponible de la base de datos. La configuración básica es:MySQL selecciona filas donde la fecha no está entre la fecha

table: room 
columns: id, maxGuests 

table: roombooking 
columns: id, startDate, endDate 

table: roombooking_room: 
columns: id, room_id, roombooking_id 

necesito para seleccionar las habitaciones que pueden caber los huéspedes solicitados en, o seleccionar dos (o más) habitaciones para adaptarse a los huéspedes en (según la definición de maxGuests, obviamente, con el más bajo/armario maxGuests primero)

pude bucle a través de mi rango de fecha y utilizar este SQL:.

SELECT `id` 
FROM `room` 
WHERE `id` NOT IN 
(
    SELECT `roombooking_room`.`room_id` 
    FROM `roombooking_room`, `roombooking` 
    WHERE `roombooking`.`confirmed` =1 
    AND DATE(%s) BETWEEN `roombooking`.`startDate` AND `roombooking`.`endDate` 
) 
AND `room`.`maxGuests`>=%d 

Dónde% $ 1 es la fecha y% bucle 2d es el número de invitados a ser reservado pero esto solo devuelve falso si hay más invitados que cualquier habitación puede tomar, y debe haber un rápido ker manera de hacer esto en lugar de bucle con php y ejecutar la consulta?

Esto es similar a una parte del sql que estaba pensando: Getting Dates between a range of dates pero con Mysql


solución, basada en la respuesta de ircmaxwell:

$query = sprintf(
     "SELECT `id`, `maxGuests` 
     FROM `room` 
     WHERE `id` NOT IN 
     (
      SELECT `roombooking_room`.`room_id` 
      FROM `roombooking_room` 
      JOIN `roombooking` ON `roombooking_room`.`roombooking_id` = `roombooking`.`id` 
      WHERE `roombooking`.`confirmed` =1 
      AND (`roomBooking`.`startDate` > DATE(%s) OR `roomBooking`.`endDate` < DATE(%s)) 
     ) 
     AND `maxGuests` <= %d ORDER BY `maxGuests` DESC", 
     $endDate->toString('yyyy-MM-dd'), $startDate->toString('yyyy-MM-dd'), $noGuests); 
     $result = $db->query($query); 
     $result = $result->fetchAll(); 

     $rooms = array(); 
     $guests = 0; 
     foreach($result as $res) { 
      if($guests >= $noGuests) break; 
      $guests += (int)$res['maxGuests']; 
      $rooms[] = $res['id']; 
     } 
+0

¿por qué tiene una tabla roombooking_room separada? no debe hacer una tabla de roombooking: id, room_id, startDate, endDate ser suficiente? – Axarydax

+0

Creo que el SQL necesario para hacer lo que quiera sería, en términos realistas, demasiado complejo para lo que está tratando de lograr. ¿Qué hay de malo con el bucle y el uso de PHP? También puede encontrar que si logra el resultado deseado con SQL puro, esta solución puede de hecho ser más lenta que un bucle con PHP. Sin embargo, estaría muy interesado en ver los resultados, ya que a veces me hago una pregunta similar (PHP vs. SQL). –

+0

@Axaryday, por favor vea el comentario en la respuesta a continuación. Es necesario porque un período de reserva puede tener más de una habitación asociada. Es decir, me quedo con 10 personas, una habitación puede llevar a 6 personas, por lo tanto, necesito dos habitaciones pero bajo la misma reserva – Ashley

Respuesta

4

Suponiendo que usted está interesado en colocar @Guests a partir @StartDate a @EndDate

SELECT DISTINCT r.id, 
FROM room r 
    LEFT JOIN roombooking_room rbr ON r.id = rbr.room_id 
    LEFT JOIN roombooking ON rbr.roombooking_id = rb.id 
WHERE COALESCE(@StartDate NOT BETWEEN rb.startDate AND rb.endDate, TRUE) 
     AND COALESCE(@EndDate NOT BETWEEN rb.startDate AND rb.endDate, TRUE) 
     AND @Guests < r.maxGuests 

debe darle una lista de todas las habitaciones que están libres y pueden acomodar determinado número de personas para el periodo indicado.

NOTAS
Esta consulta funciona sólo para habitaciones individuales, si desea buscar en varias habitaciones que se necesitan para aplicar los mismos criterios a una combinación de habitaciones. Para esto necesitaría consultas recursivas o algunas tablas auxiliares. Además, COALESCE está allí para encargarse de los NULL: si no se reserva una habitación, no tendría ningún registro con fechas para comparar, por lo que no devolvería habitaciones completamente gratis. La fecha entre date1 y date2 devolverá NULL si date1 o date2 es nulo y coalesce lo convertirá en true (la alternativa es hacer un UNION de habitaciones completamente gratis, que podría ser más rápido).

Con habitaciones múltiples, las cosas se vuelven realmente interesantes. ¿Es ese escenario gran parte de su problema? ¿Y qué base de datos está utilizando, es decir, tiene acceso a consultas recursivas?

EDITAR

Como he dicho varias veces antes, su manera de buscar una solución (algoritmo voraz que se ve en las mayores habitaciones libres primeros) no es la óptima si usted desea conseguir el mejor ajuste entre número requerido de huéspedes y habitaciones.

Por lo tanto, si la sustituye con foreach

$bestCapacity = 0; 
$bestSolution = array(); 

for ($i = 1; $i <= pow(2,sizeof($result))-1; $i++) { 
    $solutionIdx = $i; 
    $solutionGuests = 0; 
    $solution = array(); 
    $j = 0; 
    while ($solutionIdx > 0) : 
     if ($solutionIdx % 2 == 1) { 
      $solution[] = $result[$j]['id']; 
      $solutionGuests += $result[$j]['maxGuests']; 
     } 
     $solutionIdx = intval($solutionIdx/2); 
     $j++; 
    endwhile;  
    if (($solutionGuests <= $bestCapacity || $bestCapacity == 0) && $solutionGuests >= $noGuests) { 
     $bestCapacity = $solutionGuests; 
     $bestSolution = $solution; 
    } 
} 

print_r($bestSolution); 
print_r($bestCapacity); 

pasará a través de todas las combinaciones posibles y encontrar la solución que desperdicia el menor número de espacios.

+0

Gracias por esto. No es obligatorio para varias habitaciones, siempre podría codificar la situación de múltiples habitaciones, pero parece que renunciar a – Ashley

+0

@ Ashley, el problema con varias habitaciones es que tienes que examinar todas las combinaciones posibles de habitaciones para encontrar la mejor solución (2^n-1). ¿Cuántas habitaciones podría tener normalmente y cuántas tienen el mismo tamaño? – Unreason

+0

Para este sitio, solo 14 habitaciones tienen entre 6 y 10. Pero tiene razón, esto podría cambiar para otros clientes y podría causar problemas. ircmaxwell hace un buen punto de golpe. Tal vez voy a ir con mi idea de obtener la sala con maxGuests y loop hasta que no haya más invitados para asignar. – Ashley

3

Ok, antes que nada, la consulta interna lo estás usando es una unión cartesiana, y será MUY caro. Debe especificar criterios de unión (roombooking_room.booking_id = roombooking.id por ejemplo).

En segundo lugar, suponiendo que tiene un rango de fechas, ¿qué podemos decir al respecto? Bueno, llamemos al inicio de su rango rangeStartDate y rangeEndDate.

Ahora, ¿qué podemos decir sobre cualquier otro rango de fechas que no tenga ninguna forma de superposición con este rango? Bueno, el endDate no debe estar entre el rangeStartDate y el rangeEndDate. Lo mismo con el startDate. Y el rangeStartDate (y rangeEndDate, pero no necesitamos comprobarlo) no puede ser de entre startDate y endDate ...

Así, suponiendo %1$s es rangeStartDate y %2$s es rangeEndDate, una amplia cláusula where podría ser:

WHERE `roomBooking`.`startDate` NOT BETWEEN %1$s AND %2s 
    AND `roomBooking`.`endDate` NOT BETWEEN %1$s AND %2$$s 
    AND %1s NOT BETWEEN `roomBooking`.`startDate` AND `roomBooking`.`endDate` 

Pero, hay una manera más simple de decir eso. La única manera de que una serie sea fuera de otro es para el fecha_inicial sea después de la fecha_final, o la fecha_final sea antes de la START_ID

Así, suponiendo %1$s es rangeStartDate y %2$s es rangeEndDate, otro amplio que la cláusula podría ser:

WHERE `roomBooking`.`startDate` > %2$s 
    OR `roomBooking`.`endDate` < %1$s 

lo tanto, que trae la consulta general de:

SELECT `id` 
FROM `room` 
WHERE `id` NOT IN 
(
    SELECT `roombooking_room`.`room_id` 
    FROM `roombooking_room` 
    JOIN `roombooking` ON `roombooking_room`.`roombooking_id` = `roombooking`.`id` 
    WHERE `roombooking`.`confirmed` =1 
    AND (`roomBooking`.`startDate` > %2$s 
     OR `roomBooking`.`endDate` < %1$s) 
) 
AND `room`.`maxGuests`>=%d 

Hay otras maneras de hacer esto, así que sigue buscando ...

+0

Gracias, creo que este es el camino a seguir y esto funcionará perfectamente bien para cuando maxGuests sea menor o igual al número solicitado de invitados.Creo que tendré que ejecutar esto, si no es exitoso, obtener la sala con el máximo de Misiones y menos del total de invitados y ejecutar esto de nuevo. Bucles con bucles, pero creo que es la única manera? – Ashley

+0

@ Ashley, en realidad no y lo que usted propone no es exhaustivo; es posible que se pierda una buena solución. Tenga en cuenta que tiene 3 habitaciones libres por un período, una con 10 espacios y dos con 7 y desea acomodar a 14 personas. Con un algoritmo codicioso ocupará una sala de 10 y 7 y perderá la solución de dos salas con 7 espacios. – Unreason

+0

Así que recorrer parece una buena manera. Organizar salas con su closet maxGuests (donde noGuests> = maxGuests PEDIR POR maxGuests límite 1) ¿Creo? – Ashley

0
SELECT rooms.id 
FROM rooms LEFT JOIN bookings 
ON booking.room_id = rooms.id 
WHERE <booking overlaps date range of interest> AND <wherever else> 
GROUP BY rooms.id 
HAVING booking.id IS NULL 

que podría ser la señorita recordando cómo se unen las obras izquierda por lo que puede que tenga que utilizar una condición ligeramente diferente de la que tiene, tal vez un recuento o una suma.

En el peor de los casos, con índices adecuados, debería escanear la mitad de las reservas.

+0

normalmente no utiliza GROPY BY si no necesita agregados y en el caso anterior no está utilizando ninguno, por lo que puede expulsar GROUP BY utilizar DISTINCT en rooms.id y mover HAVING a WHERE (debe mover el condición para la parte where incluso si tiene grupos/need group; having es para las condiciones en los agregados y está destinado a ser aplicado al conjunto de resultados * después * se calculan los agregados) – Unreason

+0

@Unreason: mientras que eso podría funcionar (o incluso, pensándolo bien, será necesario) para la versión 'IS NULL', exactamente lo contrario es cierto para la versión' sum' o 'count'. Para ellos, el filtro tiene que aplicarse al resultado agregado, por lo que utilicé una cláusula 'HAVING' en lugar de' WHERE'. – BCS

Cuestiones relacionadas