2010-11-12 136 views
5

我有一個預訂系統,我需要從數據庫中選擇任何可用的房間。基本設置是:MySQL選擇日期不在日期之間的行

table: room 
columns: id, maxGuests 

table: roombooking 
columns: id, startDate, endDate 

table: roombooking_room: 
columns: id, room_id, roombooking_id 

,我需要選擇的客房,可容納請求的客人,或選擇兩個(或更多)的房間,以適應賓客(由maxGuests定義,顯然用最低/壁櫥maxGuests第一)

我可以遍歷我的日期範圍,並使用此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 

其中,%$ 1是環狀日期%2d是在被預訂的客人數量,但這將只是如果有多於任何房間的客人可以返回假,並且必須有一個quic更好的方式做到這一點,而不是循環與PHP和運行查詢?

這類似於SQL的一部分,我想的是:Getting Dates between a range of dates但與MySQL


解決方案的基礎上,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

爲什麼你有一個單獨的roombooking_room表?不應該tablerooming:id,room_id,startDate,endDate是否夠了? – Axarydax 2010-11-12 14:44:21

+0

我認爲,爲了實現目標,對於想要實現的目標而言,執行所需任務所需的SQL會過於複雜。循環和使用PHP有什麼問題?您也可能會發現,如果您使用純SQL實現期望的結果,則該解決方案實際上可能比用PHP循環更慢。但是,我對看到結果非常感興趣,因爲我有時會發現自己提出了一個類似的問題(PHP vs SQL)。 – 2010-11-12 15:01:22

+0

@Axaryday請在下面看到關於答案的評論。這是必要的,因爲一個預訂週期可能有多個房間關聯。即,我住10人,一個房間可以帶6人,因此我需要兩個房間,但在相同的預訂 – Ashley 2010-11-12 15:50:58

回答

4

假設你有興趣的地方@Guests@StartDate@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 

應該給你的所有的客房,免費,可容納客人給定數量的在一定期間的列表。

注意
此查詢僅適用於單間,如果你想看看在多個房間,你需要同樣的標準適用於房間的組合。爲此,您需要遞歸查詢或一些輔助表。 此外,COALESCE是否有照顧空 - 如果一個房間沒有被預訂,它將不會有任何記錄與日期比較,所以它不會返回完全免費的房間。日期1和日期2之間的日期將返回NULL,如果date1或date2爲空並且coalesce將其變爲true(或者是完全免費房間的UNION;這可能會更快)。

與多個房間的事情變得非常有趣。 這是你的問題的大部分嗎?你正在使用哪個數據庫,即是否有權訪問遞歸查詢?

編輯

正如我前面所說多次,你的尋找一個解決方案(貪心算法,着眼於最大的免費客房第一)的方式並不是最佳的,如果你想獲得之間的最佳契合所需客人數量和客房數量。

所以,如果你有

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

取代你的foreach會經過所有可能的組合,發現浪費的空間數最少的解決方案。

+0

感謝您的支持。對多個房間來說這不是必須的 - 我總是可以對多個房間的情況進行硬編碼 - 但是這似乎是放棄了 – Ashley 2010-11-12 19:09:20

+0

@Ashley,多個房間的問題是您必須檢查所有可能的房間組合以找到最佳解決方案(2^N-1)。您通常可以擁有多少個房間,以及多少個房間的大小相同? – Unreason 2010-11-12 19:17:43

+0

對於本網站而言,只有14個房間的房間介於6和10之間。但是你說得對,這可能會改變其他客戶,並可能導致問題。 ircmaxwell打出了很好的一擊。也許我會跟着我的想法,讓maxGuests獲得房間並循環,直到沒有更多的客人分配爲止。 – Ashley 2010-11-12 19:20:17

3

好吧,首先,內部查詢你使用的是笛卡爾連接,而且會非常昂貴。您需要指定加入標準(例如,roombooking_room.booking_id = roombooking.id)。其次,假設你有一個日期範圍,我們可以說什麼呢?那麼,讓我們稱您的範圍rangeStartDaterangeEndDate的開始。

現在,我們可以說關於任何其他範圍的日期沒有任何形式的重疊範圍?那麼,endDate一定不能在rangeStartDaterangeEndDate之間。與startDate一樣。而rangeStartDate(和rangeEndDate,但我們並不需要檢查它)不能startDateendDate之間...

因此,假設%1$srangeStartDate%2$srangeEndDate,全面的where子句可能是:

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` 

但是,有一種更簡單的說法。一個範圍是另一個之外的唯一方法是在起始日期是END_DATE後,或END_DATE是START_ID

所以之前,假設%1$srangeStartDate%2$srangeEndDate,另一個全面的where子句可能是:

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

所以,這使你的整體查詢:

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 

有這樣做,以及其他方式,就這樣一直看着......

+0

謝謝,我認爲這是前進的方向,當maxGuests小於或等於請求的guest虛擬機數量時,這將工作得很好。我想我必須運行這個,如果它不成功,那麼使用maxGuests獲得房間,然後減去總客人的房間並再次運行。循環循環,但我認爲這是唯一的方法? – Ashley 2010-11-12 15:54:06

+0

@Ashley,實際上不是,你提出的建議並不全面 - 你可能會錯過一個很好的解決方案。考慮你有一段時間有3個免費房間,一個有10個空間,兩個有7個,你想容納14個人。使用貪婪算法,您將佔用10和7的空間,並錯過兩個7個空間的解決方案。 – Unreason 2010-11-12 19:14:58

+0

因此,循環似乎是一種好方法。組織房間與他們的衣櫃maxGuests(其中noGuests> = maxGuests ORDER BY maxGuests限制1)我認爲? – Ashley 2010-11-12 19:21:36

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 

我可能會錯過記住左連接如何工作,所以你可能需要使用稍有不同的條件,有可能是一個計數或總和。

最糟糕的是,如果有合適的索引,那麼應該掃描一半的預訂。

+0

如果您不需要聚合,您通常不使用GROPY BY,並且在上述情況下您不使用任何 - 因此,您可以在rooms.id上使用DISTINCT,並將HAVING移動到WHERE(您應該移動條件,即使你確實有聚合/需求組;根據聚合條件和打算應用於結果集*計算聚合後) – Unreason 2010-11-13 14:23:52

+0

@不理想:雖然這可能工作(或者甚至在第二個想法中是必要的)對於'IS NULL'版本來說,'sum'或'count'版本恰好相反。對於他們來說,過濾器必須適用於聚合結果,因此我使用「HAVING」而不是「WHERE」子句的原因。 – BCS 2010-11-15 15:12:37