nanjero echizen nanjero echizen - 1 year ago 57
SQL Question

PHP/SQL - How can I check if a date user input is in between an existing date range in my database?

I am trying to create a search page that takes a check in and check out date to check if a room is available. if a room has an existing booking and its date range overlaps with the user input then it is not outputted. otherwise if it does not have a booking or its current bookings does not overlap with user input then it is outputted. A check in can exist on another booking's check out date

My current SQL outputs all rooms based on the number of beds only. for some reason it does not check for date overlaps.

$sql13 = "SELECT rid, beds, orientation, price FROM rooms
WHERE beds = $nOfBeds AND rooms.rid NOT IN
(SELECT bookings.rid FROM bookings
WHERE $cInDate BETWEEN checkin AND checkout OR
($cInDate <= checkin AND $cOutDate >= checkout))";

if ($rOrientation != "") {
$sql13 .= " AND orientation = '$rOrientation'";
$results13 = mysqli_query($conn, $sql13)
or die ('Problem with query' . mysqli_error($conn));

there are more rooms. I just took a smaller screenshot to make the question smaller

Answer Source

It's pretty simple to check for an overlap of time periods.

Let b1 and e1 represent start and end of period 1, with b1 before e1

Let b2 and e2 represent start and end of period 2, with b2 before e2

If there is no overlap in the periods, then we know that the following will return TRUE

 ( b1 > e2 OR b2 > e1 )

(basically, the beginning of one period will be after the end of the other period.)

Given that the periods in the question are defined by just dates (with no time components), assuming that "checkout time" of one booking period will be sufficiently before "checkin time" of a subsequent booking period, giving enough time between for the overworked and underpaid housekeeping staff to clean the room... then we wouldn't consider a checkin date equal to a checkout date of another period to be an overlap... so equals wouldn't be a considered an overlap

 ( b1 >= e2 OR b2 >= e1 )

Conversely, if there is an overlap in the periods, then the negation of that condition would return TRUE

 NOT ( b1 >= e2 OR b2 >= e1 )

To find "rooms" available for a specified period (i.e. no overlapping bookings), we could use an anti-join pattern:

  SELECT r.rid
       , r.beds
       , r.orientation
       , r.price
    FROM rooms r
    JOIN bookings b
      ON b.rid = r.rid
     AND NOT ( ? >= b.checkout OR ? <= b.checkin )
   WHERE b.rid IS NULL
     AND r.beds = ?

The question marks are placeholders for checkin, checkout and number of beds, in that order. (We prefer to use prepared statements with bind placeholders, for a couple of reasons... to avoid creating unnecessary SQL Injection vulnerabilities, and in some databases, potential for improved performance.) But feel free to substitute those question mark placeholders in the SQL text with appropriately validated and properly escaped literal expressions.

The way to understand the anti-join... the outer join returns all rows from rooms, along with any matching overlapping bookings. If there are no "matching" (overlapping) rows from bookings, the row from rooms will be returned, with all NULL values for the columns from bookings. So we can exclude all rooms that had at least one matching (overlapping) row from booking with a simple test in the WHERE clause. If there was a matching row, we know that the rid column from booking would have a non-NULL value. If we exclude all rows with non-NULL values, we are left with rooms that didn't have an overlapping booking.