为特定用户 MySQL 在同一 table 中查找日期范围重叠

Find date range overlaps within the same table, for specific user MySQL

我绝不是MySQL专家,所以我正在寻求有关此事的任何帮助。

我需要做一个简单的测试(原则上),我有这个(简化)table:

tableid | userid  | car      | From        | To
--------------------------------------------------------
1       | 1       |  Fiesta  |  2015-01-01 | 2015-01-31
2       | 1       |  MX5     |  2015-02-01 | 2015-02-28
3       | 1       |  Navara  |  2015-03-01 | 2015-03-31
4       | 1       |  GTR     |  2015-03-28 | 2015-04-30
5       | 2       |  Focus   |  2015-01-01 | 2015-01-31
6       | 2       |  i5      |  2015-02-01 | 2015-02-28
7       | 2       |  Aygo    |  2015-03-01 | 2015-03-31
8       | 2       |  206     |  2015-03-29 | 2015-04-30
9       | 1       |  Skyline |  2015-04-29 | 2015-05-31
10      | 2       |  Skyline |  2015-04-29 | 2015-05-31

我需要在这里找到两件事:

  1. 如果任何用户在他的汽车分配中有超过一天的日期重叠(分配的结束可以与新分配的开始在同一天)。
  2. 是否有任何两个用户试图在同一日期分配同一辆车,或者他们在同一辆车上的日期范围重叠。

所以我正在寻找的查询(或多个查询)应该 return 这些行:

tableid | userid  | car      | From        | To
--------------------------------------------------------
3       | 1       |  Navara  |  2015-03-01 | 2015-03-31
4       | 1       |  GTR     |  2015-03-28 | 2015-04-30
7       | 2       |  Aygo    |  2015-03-01 | 2015-03-31
8       | 2       |  206     |  2015-03-29 | 2015-04-30
9       | 1       |  Skyline |  2015-04-29 | 2015-05-31
10      | 2       |  Skyline |  2015-04-29 | 2015-05-31 

我觉得我在用头撞墙,我很高兴能够在单独的查询中进行这些比较。我需要将它们显示在一个 table 中,但我总是可以加入结果。

我已经完成了研究和几个小时的测试,但我离我想要的结果还很远。

SQLFiddle with the above test data

我试过这些 posts 顺便说一句(它们不是我所需要的,但足够接近,或者我认为):

Comparing two date ranges within the same table

How to compare values of text columns from the same table

这是我能找到的最接近的解决方案,但是当我在单个 table 上尝试它(将 table 加入自身)时,我得到了疯狂的结果:Checking a table for time overlap?

编辑

作为临时解决方案,我采用了一种不同的方法,类似于我在研究过程中发现的 posts(上文)。我现在将检查新车租赁/分配日期是否与 table 内的任何日期范围重叠。如果是这样,我将保存与日期重叠的行的 ID。这样至少我将能够标记重叠并允许用户查看标记的行并手动解决任何重叠。

感谢所有为此提供帮助的人,除非有人有更好的方法来实现这一点,否则我会将 philipxy 答案标记为已选答案(在接下来的 24 小时内)。我毫不怀疑,按照他的回答,我最终将能够达到我需要的结果。目前我需要采用任何有效的解决方案,因为我需要在接下来的几天内完成我的项目,因此改变了方法。

编辑 #2

这两个答案都很棒,对于发现此 post 与我有相同问题的任何人,请阅读它们并查看小提琴! :) 他们付出了很多惊人的脑力劳动!暂时我不得不采用我在#1 编辑中提到的解决方案,但我将调整我的查询以配合@Ryan Vincent 方法 + @philipxy edits/comments 关于忽略最初的一天重叠。

对于每个输入和输出 table 找到它的 含义 。即由列名参数化的语句模板,又名 predicate,一行构成真或假语句,又名 proposition。 table 包含使其谓词成为真命题的行。即,使真命题的行进入 table,而使假命题的行留在外面。例如,对于您的输入 table:

rental [tableid] was user [userid] renting car [car] from [from] to [to]

然后根据输入 table 谓词来表达输出 table 谓词。 不要 使用像您的 1 和 2 这样的描述:

  1. If any user has date overlaps in his car assignments of more than one day (end of the assignment can be on the same day as the new assignment start).

而是在 table:

中找到任意行声明的谓词
rental [tableid] was user [user] renting car [car] from [from] to [to]
    in self-conflict with some other rental

为了让 DBMS 计算出使这一点成立的行,我们必须根据给定的谓词加上文字和条件来表达这一点:

-- query result holds the rows where
FOR SOME t2.tableid, t2.userid, ...:
    rental [t1.tableid] was user [t1.userid] renting car [t1.car] from [t1.from] to [t1.to]
AND rental [t2.tableid] was user [t2.userid] renting car [t2.car] from [t2.from] to [t2.to]
AND [t1.userid] = [t2.userid] -- userids id the same users
AND [t1.to] > [t2.from] AND ...  -- tos/froms id intervals with overlap more than one day
...

(在 SQL SELECT 语句中 JOINed tables 的叉积具有 [=18= 形式的列名称].column。将 . 视为列名中允许的另一个字符。最后是 SELECT 子句删除 alias.s.)

我们将查询谓词转换为 SQL 查询,该查询计算使其为真的行:

  • table 的谓词被 table 别名替换。
  • 要多次使用相同的 predicate/table 创建别名。
  • 在谓词中将列 old 更改为 new 添加 ANDold=new.
  • AND 的谓词被替换为 JOIN
  • OR 的谓词被替换为 UNION
  • AND NOT 的谓词被替换为 EXCEPTMINUS 或适当的 LEFT JOIN.
  • ANDcondition 被替换为 WHEREON condition.
  • 对于谓词为真 FOR SOMEcolumns to drop 或当 THERE EXISTScolumns to drop, SELECT DISTINCTcolumns to keep.
  • 等(参见 this。)

Hence(完成省略号):

SELECT DISTINCT t1.*
FROM t t1 JOIN t t2
ON t1.userid = t1.userid -- userids id the same users
WHERE t1.to > t2.from AND t2.to > t1.from -- tos/froms id intervals with overlap more than one day
AND t1.tableid <> t2.tableid -- tableids id different rentals
  1. Did any two users tried to get the same car assigned on the same date, or the date ranges overlap for them on the same car.

在 table:

中查找任意行声明的谓词
rental [tableid] was user [user] renting car [car] from [from] to [to]
    in conflict with some other user's rental

根据我们给定的谓词加上文字和条件:

-- query result holds the rows where
FOR SOME t2.*
    rental [t1.tableid] was user [t1.userid] renting car [t1.car] from [t1.from] to [t1.to]
AND rental [t2.tableid] was user [t2.userid] renting car [t2.car] from [t2.from] to [t2.to]
AND [t1.userid] <> [t2.userid] -- userids id different users
AND [t1.car] = [t2.car] -- .cars id the same car
AND [t1.to] >= [t2.from] AND [t2.to] >= [t1.from] -- tos/froms id intervals with any overlap
AND [t1.tableid] <> [t2.tableid] -- tableids id different rentals

谓词 1 和 2 的查询 UNION returns predicate 1OR[=84 的行=]predicate 2.

尝试学习表达谓词——tables 中的行状态——如果只是作为直观(子)查询的目标。

PS 最好总是让数据检查边缘和非边缘情况的条件是否为真和为假。例如,从 31 日开始使用 GTR 尝试查询 1,只有一天的重叠,这应该是一个自我冲突。

PPS 涉及重复行的查询,与NULL一样,查询含义相当复杂。很难说元组何时进入或离开 table 以及有多少次。对于根据我的通信具有简单直观含义的查询,它们不能重复。不幸的是,这里 SQL 与关系模型不同。在实践中,人们在允许非不同的行时依赖习语,并且他们依赖于由于约束而不同的行。例如,根据 UNIQUE、PK 和 FK 加入 UNIQUE 列。例如:最后的 DISTINCT 步骤只是在与不需要它的版本不同的时间进行工作;时间可能是也可能不是影响为给定 predicate/result.

选择的措辞的重要实施问题

这是第一部分:每个用户重叠的汽车...

SQLFiddle - correlated Query and Join Query

第二部分-一辆车同时有多个用户:SQLFiddle - correlated Query and Join Query。下面查询...

我使用相关查询:

您可能需要 userid 和 'car' 的索引。但是 - 请检查 'explain plan' 以了解它 mysql 如何访问数据。试试吧:)

每个用户重叠的汽车

查询:

SELECT `allCars`.`userid`  AS `allCars_userid`, 
       `allCars`.`car`     AS `allCars_car`, 
       `allCars`.`From`    AS `allCars_From`, 
       `allCars`.`To`      AS `allCars_To`,
       `allCars`.`tableid` AS `allCars_id`
 FROM  
       `cars` AS `allCars`
 WHERE 
     EXISTS  
         (SELECT 1       
          FROM `cars` AS `overlapCar`            
          WHERE 
               `allCars`.`userid` = `overlapCar`.`userid` 
           AND `allCars`.`tableid` <> `overlapCar`.`tableid`          
           AND NOT (   `allCars`.`From`  >= `overlapCar`.`To`      /* starts after outer ends  */  
                    OR `allCars`.`To`    <= `overlapCar`.`From`))  /* ends before outer starts */
 ORDER BY
        `allCars`.`userid`, 
        `allCars`.`From`, 
        `allCars`.`car`;      

结果:

allCars_userid  allCars_car  allCars_From  allCars_To  allCars_id  
--------------  -----------  ------------  ----------  ------------
             1  Navara       2015-03-01    2015-03-31             3
             1  GTR          2015-03-28    2015-04-30             4
             1  Skyline      2015-04-29    2015-05-31             9
             2  Aygo         2015-03-01    2015-03-31             7
             2  206          2015-03-29    2015-04-30             8
             2  Skyline      2015-04-29    2015-05-31            10

为什么有效?或者我是怎么想的:

我使用相关查询,所以我不需要处理重复项,这对我来说可能是最容易理解的。还有其他表达查询的方式。每个都有优点和缺点。我想要一些我可以很容易理解的东西。

要求:对于每个用户,确保他们不会同时拥有两辆或更多汽车。

因此,对于每个用户记录 (AllCars),检查完整的 table (overlapCar),看看是否可以找到一个 不同的 记录重叠的时间当前记录。如果我们找到一个,那么 select 我们正在检查的当前记录(在 allCars 中)。

因此 overlap 检查是:

  • allCarsuseridoverLapuserid必须相同

  • allCars汽车记录和overlap汽车记录必须不同

  • allCars时间范围和overLap时间范围必须重叠。

    检查时间范围:

    不要检查重叠时间,而是使用阳性测试。最简单的方法是检查它是否重叠,然后对其应用 NOT

一辆车同时有多个用户...

查询:

SELECT  `allCars`.`car`     AS `allCars_car`,
        `allCars`.`userid`  AS `allCars_userid`,  
        `allCars`.`From`    AS `allCars_From`, 
        `allCars`.`To`      AS `allCars_To`, 
        `allCars`.`tableid` AS `allCars_id`
        
 FROM  
       `cars` AS `allCars`
 WHERE 
     EXISTS  
        (SELECT 1       
         FROM `cars` AS `overlapUser`            
         WHERE 
              `allCars`.`car` = `overlapUser`.`car` 
          AND `allCars`.`tableid` <> `overlapUser`.`tableid`          
          AND NOT (    `allCars`.`From`  >= `overlapUser`.`To`       /* starts after outer ends  */  
                   OR  `allCars`.`To`    <= `overlapUser`.`From`))  /* ends before outer starts */
 ORDER BY
        `allCars`.`car`,      
        `allCars`.`userid`, 
        `allCars`.`From`;

 

结果:

allCars_car  allCars_userid  allCars_From  allCars_To    allCars_id  
-----------  --------------  ------------  ----------  ------------
Skyline                   1  2015-04-29    2015-05-31             9
Skyline                   2  2015-04-29    2015-05-31            10

编辑:

鉴于@philipxy 的评论,关于需要 'greater than or equal to' 检查的时间范围,我已经更新了此处的代码。我没有更改 SQLFiddles.