How to find matching time intervals for more than 2 users

后端 未结 5 1689
自闭症患者
自闭症患者 2021-02-10 06:42

Find best suitable time from given time interval of different users.

Rows: 5
fid  userid  FromDateTime           ToDateTime          flag
62   1   2012-07-18 01:         


        
5条回答
  •  梦如初夏
    2021-02-10 07:32

    You can use this solution to find the "best" time window in which ALL users in your criteria (let's say userids 1-5) can meet. The "best" time window is measured by the greatest amount of seconds.

    SELECT   MAX(b.FromDateTime) FromDateTime, 
             a.ToDateTime
    FROM     (
             SELECT   DISTINCT a.ToDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid     <> b.userid
                           AND a.userid     IN (1,2,3,4,5)
                           AND b.userid     IN (1,2,3,4,5)
                           AND a.ToDateTime >  b.FromDateTime 
                           AND a.ToDateTime <= b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) a
    JOIN     (
             SELECT   DISTINCT a.FromDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid       <> b.userid
                           AND a.userid       IN (1,2,3,4,5)
                           AND b.userid       IN (1,2,3,4,5)
                           AND a.FromDateTime >= b.FromDateTime 
                           AND a.FromDateTime <  b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) b ON b.FromDateTime < a.ToDateTime
    GROUP BY a.ToDateTime
    ORDER BY TIMESTAMPDIFF(SECOND, MAX(b.FromDateTime), a.ToDateTime) DESC
    LIMIT    1
    

    The 4 after COUNT(DISTINCT... is just the number of users in your criteria minus one (since user's are prevented from joining onto themselves). Adjust accordingly.

    What we should be returned with is the start and end time of the meeting in which all users can attend.


    Query Breakdown


    Given the following data:

    (62, 1, '2012-07-18 00:00:00', '2012-07-18 12:00:00', 1),
    
    (63, 2, '2012-07-18 00:00:00', '2012-07-18 02:00:00', 1),
    (64, 2, '2012-07-18 03:00:00', '2012-07-18 05:00:00', 1),
    (65, 2, '2012-07-18 05:30:00', '2012-07-18 06:00:00', 1),
    
    (66, 3, '2012-07-18 00:30:00', '2012-07-18 02:30:00', 1),
    (67, 3, '2012-07-18 03:10:00', '2012-07-18 07:30:00', 1),
    
    (68, 4, '2012-07-18 01:10:00', '2012-07-18 03:20:00', 1),
    (69, 4, '2012-07-18 03:50:00', '2012-07-18 06:00:00', 1),
    
    (70, 5, '2012-07-18 01:10:00', '2012-07-18 03:20:00', 1),
    (71, 5, '2012-07-18 04:30:00', '2012-07-18 07:10:00', 1),
    
    
    (72, 1, '2012-07-18 13:00:00', '2012-07-18 14:00:00', 1),
    (73, 2, '2012-07-18 13:30:00', '2012-07-18 14:30:00', 1),
    (74, 3, '2012-07-18 14:00:00', '2012-07-18 15:00:00', 1),
    (75, 4, '2012-07-18 14:30:00', '2012-07-18 15:30:00', 1),
    (76, 5, '2012-07-18 18:00:00', '2012-07-18 19:00:00', 1);
    

    The relative time interval positions should look like the following textual illustration (will have to sidescroll to see it all):

    uid 1   <--------------------------------------------------------------------------------------...-------->      <-------------------->
    uid 2   <----------------------->          <----------------------->    <---->                                          <-------------------->
    uid 3       <----------------------->       <------------------------------------------->                                       <-------------------->
    uid 4                 <----------------------->      <----------------------->                                                         <-------------------->
    uid 5                 <----------------------->              <----------------------->                                                                                              <-------------------->
                          [    1    ]           [2]              [  3  ]    [ 4  ]
                               ^
           We want the start and end times of this overlap
    

    The numbers in between the brackets [ ] represent the time window in which all users' free-times overlap. We want overlap #1 since it is the longest. Overlap #1 should be 2012-07-18 1:10:00 to 2012-07-18 2:00:00, so our expected result should be:

    FromDateTime       | ToDateTime
    ----------------------------------------
    2012-07-18 1:10:00 | 2012-07-18 2:00:00
    

    Step 1:

    The first thing we must do is figure out what the end-times are of all potential meeting windows. We do this by selecting those particular intervals in which their end-times are in between all other users' free-time intervals.

    The end-times returned represent the end-times of each overlap pointed out in the textual illustration above. If there are two of the same end-times returned, we only pick one since we don't need to know anything else about that end-time other than the fact that it is the latest time that that particular meeting can go until:

    SELECT   DISTINCT a.ToDateTime
    FROM     tbl a
    JOIN     tbl b ON a.userid     <> b.userid
                  AND a.userid     IN (1,2,3,4,5)
                  AND b.userid     IN (1,2,3,4,5)
                  AND a.ToDateTime >  b.FromDateTime 
                  AND a.ToDateTime <= b.ToDateTime
    GROUP BY a.userid,
             a.FromDateTime,
             a.ToDateTime
    HAVING   COUNT(DISTINCT b.userid) = 4
    

    Renders:

    TODATETIME
    -------------------
    2012-07-18 02:00:00
    2012-07-18 05:00:00
    2012-07-18 06:00:00
    2012-07-18 03:20:00
    

    SQLFiddle Demo


    Step 2:

    The next thing we will have to do is take the reverse of the last step and figure out all of the start-times of each potential meeting window, and join the result of this query with the result of the previous step on the condition that the start time is less than the previous step's end-time:

    SELECT   b.FromDateTime,
             a.ToDateTime
    FROM     (
             SELECT   DISTINCT a.ToDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid     <> b.userid
                           AND a.userid     IN (1,2,3,4,5)
                           AND b.userid     IN (1,2,3,4,5)
                           AND a.ToDateTime >  b.FromDateTime 
                           AND a.ToDateTime <= b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) a
    JOIN     (
             SELECT   DISTINCT a.FromDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid       <> b.userid
                           AND a.userid       IN (1,2,3,4,5)
                           AND b.userid       IN (1,2,3,4,5)
                           AND a.FromDateTime >= b.FromDateTime 
                           AND a.FromDateTime <  b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) b ON b.FromDateTime < a.ToDateTime
    ORDER BY a.ToDateTime, b.FromDateTime --Ordered for display purposes
    

    Renders:

    TODATETIME          | FROMDATETIME        
    ------------------------------------------
    2012-07-18 02:00:00 | 2012-07-18 01:10:00  <-- Most recent FromDateTime
    2012-07-18 03:20:00 | 2012-07-18 01:10:00 
    2012-07-18 03:20:00 | 2012-07-18 03:10:00  <-- Most recent FromDateTime
    2012-07-18 05:00:00 | 2012-07-18 01:10:00 
    2012-07-18 05:00:00 | 2012-07-18 03:10:00 
    2012-07-18 05:00:00 | 2012-07-18 04:30:00  <-- Most recent FromDateTime 
    2012-07-18 06:00:00 | 2012-07-18 01:10:00 
    2012-07-18 06:00:00 | 2012-07-18 03:10:00 
    2012-07-18 06:00:00 | 2012-07-18 04:30:00 
    2012-07-18 06:00:00 | 2012-07-18 05:30:00  <-- Most recent FromDateTime 
    

    The most recent FromDateTimes represent the start of each potential meeting window. We only want to pull the rows where FromDateTime is most recent per ToDateTime. We do this in the next step using GROUP BY in conjunction with the MAX() aggregate function.

    SQLFiddle Demo


    Step 3:

    Next, we use GROUP BY on ToDateTime and MAX() on FromDateTime to pull only the most recent FromDateTimes:

    SELECT   MAX(b.FromDateTime) FromDateTime, 
             a.ToDateTime
    FROM     (
             SELECT   DISTINCT a.ToDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid     <> b.userid
                           AND a.userid     IN (1,2,3,4,5)
                           AND b.userid     IN (1,2,3,4,5)
                           AND a.ToDateTime >  b.FromDateTime 
                           AND a.ToDateTime <= b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) a
    JOIN     (
             SELECT   DISTINCT a.FromDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid       <> b.userid
                           AND a.userid       IN (1,2,3,4,5)
                           AND b.userid       IN (1,2,3,4,5)
                           AND a.FromDateTime >= b.FromDateTime 
                           AND a.FromDateTime <  b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) b ON b.FromDateTime < a.ToDateTime
    GROUP BY a.ToDateTime
    

    Renders:

    FROMDATETIME        | TODATETIME
    -----------------------------------------
    2012-07-18 01:10:00 | 2012-07-18 02:00:00
    2012-07-18 03:10:00 | 2012-07-18 03:20:00
    2012-07-18 04:30:00 | 2012-07-18 05:00:00
    2012-07-18 05:30:00 | 2012-07-18 06:00:00
    

    These are basically our potential time-windows. Now it's just a simple matter of selecting the longest one.


    Step 4:

    We use the ORDER BY / LIMIT 1 max/min selection technique since we only need one row. We order based on the seconds-difference between the end-time and start-time of each meeting, then select the one with the greatest amount of seconds (via LIMIT 1), giving us our final desired result:

    SELECT   MAX(b.FromDateTime) FromDateTime, 
             a.ToDateTime
    FROM     (
             SELECT   DISTINCT a.ToDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid     <> b.userid
                           AND a.userid     IN (1,2,3,4,5)
                           AND b.userid     IN (1,2,3,4,5)
                           AND a.ToDateTime >  b.FromDateTime 
                           AND a.ToDateTime <= b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) a
    JOIN     (
             SELECT   DISTINCT a.FromDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid       <> b.userid
                           AND a.userid       IN (1,2,3,4,5)
                           AND b.userid       IN (1,2,3,4,5)
                           AND a.FromDateTime >= b.FromDateTime 
                           AND a.FromDateTime <  b.ToDateTime
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime
             HAVING   COUNT(DISTINCT b.userid) = 4
             ) b ON b.FromDateTime < a.ToDateTime
    GROUP BY a.ToDateTime
    ORDER BY TIMESTAMPDIFF(SECOND, MAX(b.FromDateTime), a.ToDateTime) DESC
    LIMIT    1
    

    SQLFiddle Demo of Final Result

    SQLFiddle Demo with Other Example Data


    Getting the meeting time between all users in the table (no criteria):

    If you don't want to specify which users you want to check meeting-times for (just do it for all users in the table), you can use:

    SELECT   MAX(b.FromDateTime) FromDateTime, 
             a.ToDateTime
    FROM     (
             SELECT     DISTINCT a.ToDateTime
             FROM       tbl a
             JOIN       tbl b ON a.userid     <> b.userid
                             AND a.ToDateTime >  b.FromDateTime 
                             AND a.ToDateTime <= b.ToDateTime
             CROSS JOIN (SELECT COUNT(DISTINCT userid) totalusers FROM tbl) c
             GROUP BY   a.userid,
                        a.FromDateTime,
                        a.ToDateTime,
                        c.totalusers
             HAVING     COUNT(DISTINCT b.userid) = c.totalusers-1
             ) a
    JOIN     (
             SELECT   DISTINCT a.FromDateTime
             FROM     tbl a
             JOIN     tbl b ON a.userid       <> b.userid
                           AND a.FromDateTime >= b.FromDateTime 
                           AND a.FromDateTime <  b.ToDateTime
             CROSS JOIN (SELECT COUNT(DISTINCT userid) totalusers FROM tbl) c
             GROUP BY a.userid,
                      a.FromDateTime,
                      a.ToDateTime,
                      c.totalusers
             HAVING   COUNT(DISTINCT b.userid) = c.totalusers-1
             ) b ON b.FromDateTime < a.ToDateTime
    GROUP BY a.ToDateTime
    ORDER BY TIMESTAMPDIFF(SECOND, MAX(b.FromDateTime), a.ToDateTime) DESC
    LIMIT    1
    

提交回复
热议问题