Search code examples
mysqldatedistinctoverlapping

MySQL query to select distinct rows based on date range overlapping


Let's say we have a table (table1) in which we store 4 values (user_id, name, start_date, end_date)

 table1
 ------------------------------------------------
 id   user_id   name     start_date    end_date
 ------------------------------------------------
 1      1       john     2016-04-02    2016-04-03
 2      2       steve    2016-04-06    2016-04-06
 3      3       sarah    2016-04-03    2016-04-03
 4      1       john     2016-04-12    2016-04-15

I then enter a start_date of 2016-04-03 and end_date of 2016-04-03 to see if any of the users are available to be scheduled for a job. The query that checks for and ignores overlapping dates returns the following:

table1
------------------------------------------------
id   user_id   name     start_date    end_date
------------------------------------------------
2      2       steve    2016-04-06    2016-04-06
4      1       john     2016-04-12    2016-04-15

The issue I am having is that John is being displayed on the list even though he is already booked for a job for the dates I am searching for. The query returns TRUE for the other entry because the dates don't conflict, but i would like to hide John from the list completely since he will be unavailable.

Is there a way to filter the list and prevent the user info from displaying if the dates entered conflict with another entry for the same user?

An example of the query:

SELECT DISTINCT id, user_id, name, start_date, end_date 
FROM table1 
WHERE ('{$startDate}' NOT BETWEEN start_date AND end_date 
AND '{$endDate}' NOT BETWEEN start_date AND end_date  
AND start_date NOT BETWEEN '{$startDate}' AND '{$endDate}' 
AND end_date NOT BETWEEN '{$startDate}' AND '{$endDate}');

Solution

  • The "solution" in the question doesn't look right at all.

    INSERT INTO table1 VALUES  (5,2,'steve', '2016-04-01','2016-04-04')
    

    Now there's a row with Steve having an overlap.

    And the query proposed as a SOLUTION in the question will return 'steve'.


    Here's a demonstration of building a query to return the users that are "available" during the requested period, because there is no row in table1 for that user that "overlaps" with the requested period.

    First problem is getting the users that are not available due to the existence of a row that overlaps the requested period. Assuming that start_date <= end_date for all rows in the table...

    A row overlaps the requested period, if the end_date of the row is on or after the start of the requested period, and the start_date of the row is on or before the ed of the requested period.

    -- users that are "unavailable" due to row with overlap
    SELECT t.user_id
      FROM table1 t
     WHERE t.end_date   >= '2016-04-03'  -- start of requested period
       AND t.start_date <= '2016-04-03'  -- end of requested_period
     GROUP
        BY t.user_id   
    

    (If our assumption that start_date <= end_date doesn't hold, we can add that check as a condition in the query)

    To get a list of all users, we could query a table that has a distinct list of users. We don't see a table like that in the question, so we can get a list of all users that appear in table1 instead

    SELECT l.user_id
      FROM table1 l
     GROUP BY l.user_id
    

    To get the list of all users excluding the users that are unavailable, there are couple of ways we can write that. The simplest is an anti-join pattern:

    SELECT a.user_id
      FROM ( -- list of all users 
             SELECT l.user_id
               FROM table1 l
              GROUP BY l.user_id
           ) a
      LEFT
      JOIN ( -- users that are unavailable due to overlap
             SELECT t.user_id
               FROM table1 t
              WHERE t.end_date   >= '2016-04-03'  -- start of requested period
                AND t.start_date <= '2016-04-03'  -- end of requested_period
              GROUP
                 BY t.user_id
           ) u
        ON u.user_id = a.user_id
     WHERE u.user_id IS NULL