MySQL - 如何进行自联接以返回重叠的日期范围?

时间:2016-10-07 11:32:46

标签: mysql self-join overlapping-matches

我有一张组织内成员持有的历史职位表(tbl_tenue)。 每个职位一次只能由一个人持有,但是一个人可以顺序或同时持有多个职位。 我想通过依次获取每个位置任期记录,然后将其与表格中的每个其他记录进行比较来检查该表格的完整性,以查找与同一位置的其他任期的(错误的)重叠。

我编写了一个工作查询(下面),它在传递test_tenure_id,position_id以及特定任期的开始和结束日期时返回重叠的详细信息(见下文) 对于每个重叠。此查询返回tenure_id,member_id,member_sn,date_started,date_ended和重叠原因。

任何人都可以帮助我使用sql来对同一个tbl_tenue表运行此查询,每次从tbl_tenue中的一行传递数据,这样我就可以测试每条记录与其他每一条重叠(当然除了它自己)并从记录及其所有重叠中返回数据?

(我意识到如果我能做到这一点,那么我应该能够避免通过使用连接将tenure_id传递给WHERE子句,并且还避免使用连接传递日期,但我可以&#39 ;看看目前该怎么做,所以任何帮助都会很好)

以下查询使用以下表格,针对此问题进行了简化

TABLE tbl_member 
  ( member_id INT AUTO_INCREMENT, -- pk
    member_sn` varchar(50) , --surname
    <other stuff>
  )

TABLE tbl_tenure
  (tenure_id INT AUTO_INCREMENT, -- pk
   member_id INT -- fk to tbl_member
   position_id -- fk to table of position titles
   date_started DATE
   date_ended DATE -- will be NULL if still in post
  )


  -- test data for query
  SET @the_test_tenure_start_date = '2016-05-13' ;
  SET @the_test_tenure_end_date = '2016-10-05';
  SET @the_test_position_id = 18; 
  SET @the_test_tenue_id = 122;

-- the query to return overlaps with data from a given tenure record  
SELECT
   tbl_tenure.tenure_id,
   tbl_tenure.member_id,
   tbl_member.member_sn,
   tbl_tenure.date_started,
   tbl_tenure.date_ended,
   CASE
      WHEN @the_test_tenure_end_date <= IFNULL(date_ended, CURDATE())  -- test end date <= existing end date
      AND @the_test_tenure_start_date >= date_started   -- test start date >= existing start date
      THEN 'Test dates fall completely inside an existing tenure'

      WHEN @the_test_tenure_end_date >= IFNULL(date_ended, CURDATE())   -- test end date >= existing end date
      AND @the_test_tenure_start_date <= date_started   -- test start date <= existing start date
      THEN 'An existing tenure falls completely inside test dates'

      WHEN @the_test_tenure_start_date >= date_started   -- test start date >= existing start date
      AND @the_test_tenure_start_date <= IFNULL(date_ended, CURDATE())   -- test start date <= existing end date
      THEN 'Test start date overlaps with an existing tenure'

      WHEN @the_test_tenure_end_date >= date_started   -- test end date >= existing start date
      AND @the_test_tenure_end_date <= IFNULL(date_ended, CURDATE())   -- test end date <= existing end date
      THEN 'Test end date overlaps with an existing tenure'
   END AS reason

FROM
   tbl_tenure
   INNER JOIN tbl_member
      ON tbl_tenure.member_id = tbl_member.member_id

WHERE ( -- there is an overlap (see qry 2.2 http://salman-w.blogspot.co.uk/2012/06/sql-query-overlapping-date-ranges.html
          @the_test_tenure_end_date >= date_started)    
          AND 
      IFNULL(date_ended, CURDATE()) >= @the_test_tenure_start_date
      )   
   AND tbl_tenure.position_id = @the_test_position_id   -- position to be tested
   AND  tbl_tenure.tenure_id <> @the_test_tenue_id    -- don't look at the test tenure record
ORDER BY tbl_tenure.date_started ASC;

为了澄清这个问题,我正在寻找的输出是这样的,注意tenure_id 132,其中一个成员被记录为与自己一起过度

tenure_id   | member_id     | position_id   | start_date | end_date   | overlapping_member_id | overlapping_tenure_id | overlapping_start_date |overlapping_end_date | overlap_reason
   123      |      2        |      6        | 2016-02-01 | 2016 02-01 |       7               |       456             |     2016-01-05         |   2016-01-10         |'Test start date overlaps with an existing tenure'
   125      |      2        |      8        | 2016-02-01 | 2016 03-01 |       8               |       459             |     2016-01-0          |   2016-02-01         |'Test end date overlaps with an existing tenure'
   129      |      4        |      7        | 2016-03-10 | 2016 04-01 |       6               |       501             |     2016-03-2          |   2016-03-25         |'An existing tenure falls completely inside test dates'
   132      |      4        |      7        | 2016-01-01 | 2016 04-01 |       4               |       505             |     2016-03-01         |   2016-04-01         |'Test end date overlaps with an existing tenure'
   135      |      9        |      3        | 2016-05-01 | 2016 07-01 |       9               |       520             |     2016-04-0          |   2016-08-01         |'Test dates fall completely inside an existing tenure'

1 个答案:

答案 0 :(得分:0)

花了一天的时间在跑步机上跑步并且在跑步机上跑步时我相信我有答案。我是为了别人的利益而在这里张贴的。我将使用tbl_member的连接移动到子查询中,并且为了完整性,包括另一个子查询以从第三个表tbl_position获得位置的实际标题,如下所示。 (无法看到用连接替换子查询的方法,但这并不重要。)

TABLE tbl_positions
   (
   position_id INT AUTO_INCREMENT, -- pk
   position VARCHAR(100), -- title of position
   <other stuff>
   )

我提出的代码在下方似乎正常工作,并显示所有重叠的详细信息,包括谁与谁,何时以及为何重叠。

讽刺的是,例如,如果弗雷德被证明与吉姆的现有记录重叠,因为弗雷德的任期完全包含了吉姆的任期,那么吉姆&# 39,作为总统的职位也被证明与弗雷德现有的终身职位记录重叠,理由是弗雷德完全被吉姆所包围。即我得到重叠的两面。

如果有一种快速的方式可以获得一种单一的方式&#39;然后通过各种方式重叠发布一个更好的答案。

我的回答

SELECT
   base_tenure.position_id     AS base_tenure_id,
   base_tenure.member_id     AS base_member_id,
   (SELECT member_sn FROM tbl_member WHERE tbl_member.member_id = base_tenure.member_id)     AS base_sn,
   (SELECT tbl_positions.position FROM tbl_positions WHERE tbl_positions.position_id = base_tenure.position_id ) AS POSITION,
   base_tenure.date_started  AS base_date_started,
   base_tenure.date_ended    AS base_date_ended,

   overlap_tenure.position_id  AS overlap_tenure_id,
   overlap_tenure.member_id AS overlap_member_id,
   (SELECT member_sn FROM tbl_member WHERE tbl_member.member_id = overlap_tenure.member_id)     AS overlap_sn,
   overlap_tenure.date_started AS overlap_date_started,
   overlap_tenure.date_ended   AS overlap_date_ended,  

   CASE
      WHEN base_tenure.date_ended <= IFNULL(overlap_tenure.date_ended, CURDATE())-- test end date <= existing end date
      AND base_tenure.date_started >= overlap_tenure.date_started  -- test start date >= existing start date
      THEN 'tbl_member dates fall completely inside an existing tenue'

      WHEN base_tenure.date_ended >= IFNULL(overlap_tenure.date_ended, CURDATE()) -- test end date >= existing end date
      AND base_tenure.date_started <=  overlap_tenure.date_started -- test start date <= existing start date
      THEN 'An existing tenue falls completely inside tbl_member dates'

      WHEN base_tenure.date_started >= overlap_tenure.date_started  -- test start date >= existing start date
      AND base_tenure.date_started <= IFNULL( overlap_tenure.date_ended , CURDATE()) -- test start date <= existing end date
      THEN 'tbl_member start date overlaps with an existing tenue'

      WHEN base_tenure.date_ended >= overlap_tenure.date_started  -- test end date >= existing start date
      AND base_tenure.date_ended <= IFNULL( overlap_tenure.date_ended , CURDATE())-- test end date <= existing end date
      THEN 'tbl_member end date overlaps with an existing tenue'
   END AS reason

FROM -- a self join on tbl_tenure
   tbl_tenure AS base_tenure,
   tbl_tenure AS overlap_tenure

WHERE (-- there is an overlap (see qry 2.2 http://salman-w.blogspot.co.uk/2012/06/sql-query-overlapping-date-ranges.html
           base_tenure.date_ended  >= overlap_tenure.date_started -- test end date >= existing start date
          AND 
          IFNULL(overlap_tenure.date_ended, CURDATE()) >= base_tenure.date_started 
          )

   AND 
      base_tenure.club_function_id = overlap_tenure.club_function_id -- positions are the same for both members

   AND  
      base_tenure.position_id <> overlap_tenure.position_id --  don't compare the base record with itself as they are identical and will always overlap 

 ORDER BY
       (SELECT member_sn FROM tbl_member WHERE tbl_member.member_id = base_tenure.member_id) ,
       base_tenure.date_started ;