2010-11-12 16 views
5

Ho un sistema di prenotazione in cui ho bisogno di selezionare qualsiasi stanza disponibile dal database. La configurazione di base è:MySQL seleziona le righe in cui la data non è compresa tra la data

table: room 
columns: id, maxGuests 

table: roombooking 
columns: id, startDate, endDate 

table: roombooking_room: 
columns: id, room_id, roombooking_id 

Ho bisogno di selezionare stanze che può andare bene gli ospiti richiesti, o selezionare due (o più) camere per soddisfare gli ospiti in (come definito da maxGuests, ovviamente utilizzando il più basso/armadio maxGuests prima)

potevo ciclo attraverso il mio intervallo di date e utilizzare questo 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 

Dove% $ 1 è la data e l'% in loop 2d è il numero di ospiti per essere prenotato in Ma questo solo sarà restituisce false se ci sono più ospiti di quanti ne possa prendere una e ci deve essere un quic modo per farlo, piuttosto che fare un ciclo con php ed eseguire la query?

Questo è simile a una parte del SQL pensavo: Getting Dates between a range of dates ma con Mysql


soluzione, in base alla risposta del 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

perché hai una tabella roombooking_room separata? non dovresti scrivere roombooking: id, room_id, startDate, endDate essere abbastanza? – Axarydax

+0

Penso che l'SQL necessario per fare ciò che vuoi sarebbe, in termini realistici, essere eccessivamente complesso per quello che stai cercando di ottenere. Cosa c'è di sbagliato con il ciclo e l'utilizzo di PHP? Potresti anche scoprire che se raggiungi il risultato desiderato con puro SQL, questa soluzione potrebbe essere più lenta del loop con PHP. Sarei molto interessato a vedere i risultati, tuttavia, a volte mi trovo a fare una domanda simile (PHP vs SQL). –

+0

@Asaryday si prega di vedere il commento sulla risposta qui sotto. È necessario perché un periodo di prenotazione può avere più di una stanza associata. Vale a dire, sto con 10 persone, una stanza può ospitare 6 persone, quindi ho bisogno di due stanze ma sotto la stessa prenotazione – Ashley

risposta

4

Supponendo che siete interessati a mettere @Guests da @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 

dovrebbe dare un elenco di tutte le camere che sono liberi e possono ospitare dato numero di ospiti per il periodo specificato.

NOTE
Questa query funziona solo per le camere singole, se si vuole guardare più sale che sarà necessario applicare gli stessi criteri a una combinazione di sale. Per questo è necessario avere query ricorsive o alcune tabelle di supporto. Inoltre, COALESCE è lì per occuparsi dei NULL: se una stanza non è stata prenotata, non avrebbe alcun record con date da confrontare, quindi non restituirebbe stanze completamente libere. La data tra data1 e data2 restituirà NULL se data1 o date2 sono nulle e la coalesce la trasformerà in vera (alternativa è fare un UNION di stanze completamente libere, che potrebbe essere più veloce).

Con più stanze le cose diventano davvero interessanti. Questo scenario è una parte importante del tuo problema? E quale database stai usando, hai accesso a richieste ricorsive?

EDIT

Come ho detto più volte in passato, il tuo modo di cercare una soluzione (algoritmo greedy che guarda più grandi camere libere prima) non è ottimale, se si vuole ottenere il miglior adattamento tra numero richiesto di ospiti e camere.

Quindi, se si sostituisce il tuo foreach con

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

passerà attraverso tutte le possibili combinazioni e trovare la soluzione che spreca il minor numero di spazi.

+0

Grazie per questo. Non è un must per più stanze - potrei sempre eseguire l'hardcode della situazione multipla della stanza - ma a quel punto sembra come dover rinunciare allo – Ashley

+0

@Ashley, il problema con più stanze è che devi esaminare tutte le possibili combinazioni di stanze per trovare la soluzione migliore (2^n-1). Quante stanze potresti avere in genere e quante sono le stesse dimensioni? – Unreason

+0

Per questo sito, solo 14 camere con tra 6 e 10. Ma hai ragione, questo potrebbe cambiare per altri clienti e potrebbe causare problemi. ircmaxwell fa un bel punto. Forse andrò con la mia idea di ottenere la stanza con maxGuests e loop fino a quando non ci saranno più ospiti da assegnare. – Ashley

3

Ok, prima di tutto, la query interna stai usando è un join cartesiano e sarà MOLTO costoso. È necessario specificare i criteri di join (roombooking_room.booking_id = roombooking.id per esempio).

In secondo luogo, supponendo di avere un intervallo di date, cosa possiamo dire a riguardo? Bene, chiamiamo l'inizio del range rangeStartDate e rangeEndDate.

Ora, che cosa possiamo dire su qualsiasi altro intervallo di date che non ha alcuna forma di sovrapposizione con questa gamma? Bene, lo endDate non deve essere compreso tra rangeStartDate e rangeEndDate. Lo stesso con lo startDate. E il rangeStartDate (e rangeEndDate, ma non abbiamo bisogno di check it) non può essere compreso tra startDate e endDate ...

Quindi, ipotizzando %1$s è rangeStartDate e %2$s è rangeEndDate, una vasta dove la clausola potrebbe essere:

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` 

Ma, c'è un modo più semplice per dirlo. L'unico modo per un campo di essere al di fuori di un altro è per il data_iniziale sia dopo l'end_date, o l'end_date essere prima della START_ID

Quindi, supponendo %1$s è rangeStartDate e %2$s è rangeEndDate, un altro completo dove la clausola potrebbe essere:

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

quindi, che porta la query complessiva:

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 

ci sono altri modi per farlo, così, in modo da tenere in cerca ...

+0

Grazie, penso che questa sia la via da seguire e questo funzionerà perfettamente per quando maxGuests è inferiore o uguale al numero richiesto di ospiti.Penso che dovrò eseguire questo, se non ha successo quindi ottenere la stanza con il maxGuests e meno quella dagli ospiti totali e eseguire nuovamente questo. Loop con loop ma penso che sia l'unico modo? – Ashley

+0

@Ashley, in realtà no e ciò che proponi non è esaustivo - potresti perdere una buona soluzione. Considera che hai 3 stanze libere per un periodo, una con 10 spazi e due con 7 e vuoi ospitare 14 persone. Con un algoritmo avido prenderai posto a 10 e 7 e perderai la soluzione di due stanze con 7 spazi. – Unreason

+0

Quindi il looping sembra un buon modo. Organizzare le stanze con il loro armadio maxGuests (dove noGuests> = maxGuests ORDER BY maxGuests limit 1) Riconosco? – 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 

Potrei perdere il ricordo di come funziona l'unione sinistra, quindi potrebbe essere necessario utilizzare una condizione leggermente diversa sull'avere, forse un conteggio o una somma.

Nel peggiore dei casi, con gli indici adatti, che dovrebbero scansionare metà delle prenotazioni.

+0

di solito non usi GROPY BY se non hai bisogno di aggregati e nel caso precedente non ne stai usando nessuno - quindi puoi dare il via a GROUP BY usa DISTINCT su rooms.id e sposta HAVING a WHERE (dovresti spostare il condizione per la parte where anche se si dispone di aggregati/gruppo di necessità per, avendo è per le condizioni sugli aggregati e destinato ad essere applicato al set di risultati * dopo * gli aggregati sono calcolati) – Unreason

+0

@Unreason: mentre potrebbe funzionare (o anche, a pensarci due volte, essere necessario) per la versione 'IS NULL' l'esatto contrario è vero per la versione' sum' o 'count'. Per loro, il filtro deve applicarsi al risultato aggregato, quindi il motivo per cui ho usato un 'HAVING' piuttosto che una clausola' WHERE'. – BCS

Problemi correlati