【问题标题】:MySQL - How to select 'DISTINCT' overlapping periods (dates or number ranges)MySQL - 如何选择“DISTINCT”重叠时段(日期或数字范围)
【发布时间】:2016-10-10 10:03:00
【问题描述】:

简而言之,如果查询告诉我 A 与 B 重叠,那么我不需要它也告诉我 B 也与 A 重叠,因为它们彼此重叠。

所以我试图在 sql 中使用自连接来选择“DISTINCT”重叠。

为了说明,这是我写的一个简单的 SQL 小提琴,用于显示包容性重叠选择 (http://sqlfiddle.com/#!9/7af84f/1)

详细...

假设我有一个名称 (char)、d1 (int)、d2 (int) 的表,其架构如下。这里 d1 和 d2 表示某个区间的开始和结束,该区间可能与同一张表中的另一个区间重叠。

CREATE TABLE test (
  letter char ,
  d1 int ,
  d2 int  
) ;

给定这张表,我用一些值填充它

INSERT INTO test (letter,d1,d2)
VALUES
   ('A',  2, 10),    -- overlaps C and D
   ('B', 12, 20),    -- overlaps E
   ('C',  5, 10),    -- overlaps A and D
   ('D',  1,  8),    -- overlaps A and C 
   ('E', 13, 15),    -- overlaps B
   ('F', 25, 30);    -- doesn't overlap anything

并运行以下使用自联接的查询,以正确查找其中一行中的 d1 和 d2 与其他行中的 d1 和 d2 包含重叠的行。

-- selects all records that overlap in the range d1 - d2 inclusive
-- (excluding the implicit overlap between a record and itself)
-- The results are sorted by letter followed by d1

SELECT
  basetable.letter as test_letter,
  basetable.d1,
  basetable.d2,
  overlaptable.letter as overlap_letter,
  overlaptable.d1 as overlap_d1,
  overlaptable.d2 as overlap_d2

FROM
  test as basetable, 
  test as overlaptable
WHERE
  -- there is an inclusive overlap
  basetable.d1 <= overlaptable.d2 and basetable.d2 >= overlaptable.d1
AND
  -- the row being checked is not itsself
    basetable.letter <> overlaptable.letter
    AND
    basetable.d1 <> overlaptable.d1
    AND 
    basetable.d2 <> overlaptable.d2
ORDER BY 
  basetable.letter,
  basetable.d1

这正确地给了我以下内容,显示了所有 6 个版本的重叠,例如左列表示 A 与 C 重叠,另一行显示 C 与 A 重叠(注意 sqlfiddle 似乎不理解字段别名,所以我的列标题是不同)

test_letter     d1     d2   overlap_letter  overlap_d1  overlap_d2
  A              2     10         D              1         8
  B             12     20         E             13        15
  C              5     10         D              1         8
  D              1      8         A              2        10
  D              1      8         C              5        10
  E             13     15         B             12        20

我的问题是这样的:

如何更改 sql 以获得四行 'DISTINCT' 或 'one way' 重叠?

即这个结果...

test_letter  d1     d2  overlap_letter  overlap_d1  overlap_d2 
    A         2     10        D            1           8
    A         2     10        C            5          10
    B        12     20        E           13          15
    C         5     10        D            1           8

例如:
根据以下推理,仅在左侧列中显示 A、B 和 C 的记录的结果

  • A(2,10) 与 D(1,8) 和 C(5,10) 重叠并{显示这两行}
  • B(12,20) 与 E(13,15) 重叠 {SHOW THIS ROW}
  • C(5,10) 与 D(1,8) 重叠 {SHOW THIS ROW 但不显示 A(1,10) 重叠,因为第 2 行已经显示 A 和 C 重叠}
  • D(1,8) {DON'T SHOW any new as we already know about A(1,10) and C(5,10)}
  • E(13,15) {DON'T SHOW any new as we already know about B(12,20) }
  • F(25,30) {不显示任何内容,因为没有重叠}

【问题讨论】:

  • 您有一个名为 test 的表,然后您填写了一个名为 testnames 的表。我已经很困惑了。
  • 但您似乎只对一个字母小于另一个字母的情况感兴趣(而不是“不等于”)
  • 将basetable.letter &lt;&gt; overlaptable.letter 更改为basetable.letter &lt; overlaptable.letter。它还将使您的查询速度提高 50%。 (现在我可以看到,这正是@Strawberry 用文字写的)。
  • 您需要一个标准,如何在(B 重叠 E)和(E 重叠 B)之间进行选择。除了basetable.letter {&lt; | &gt; }overlaptable.letter 其他可能的选项是basetable.d1 &gt; overlaptable.d1' , basetable.d2
  • 是的,只需将 更改为

标签: mysql sql distinct overlapping


【解决方案1】:

您可以更改为不等式。而且,您还应该使用JOIN:

SELECT basetable.letter as test_letter, basetable.d1, basetable.d2,
       overlaptable.letter as overlap_letter, overlaptable.d1 as overlap_d1, overlaptable.d2 as overlap_d2
FROM test basetable JOIN
     test overlaptable
     ON basetable.d1 <= overlaptable.d2 AND
        basetable.d2 >= overlaptable.d1
WHERE basetable.letter < overlaptable.letter  -- This is the change
ORDER BY basetable.letter, basetable.d1;

【讨论】:

  • 这错过了 A 与 C 重叠的情况,因为它使用了与我原始帖子的 cmets 中建议的相同的不等式
  • @user3209752 。 . .我删除了两个where 条件。该查询对我来说很适合寻找重叠。
  • 做到了。它产生了我正在寻找的表格,甚至是相同的字母顺序。总的来说,我认为我会将此作为正确答案,因为尽管 Serg 的答案确实可以找出明显的重叠,但结果表与我的帖子中要求的不完全匹配。此外,通过使用连接和单个 where 子句,此答案对其他人来说更容易理解。
【解决方案2】:

这可以像已经建议的 PK 订购一样简单。或者,您可能希望引入某种字典顺序。

CREATE TABLE test (
  letter char ,
  d1 int ,
  d2 int  
) ;

INSERT INTO test (letter,d1,d2)
VALUES
   ('A',  2, 10),    -- overlaps C and D
   ('B', 12, 20),    -- overlaps E
   ('C',  5, 10),    -- overlaps A and D
   ('D',  1,  8),    -- overlaps A and C 
   ('E', 13, 15),    -- overlaps B
   ('F', 25, 30),    -- doesn't overlap anything
   ('G', 50, 60),    -- a set of equal intervals
   ('H', 50, 60),
   ('I', 50, 60)


SELECT
  basetable.letter as test_letter,
  basetable.d1,
  basetable.d2,
  overlaptable.letter as overlap_letter,
  overlaptable.d1 as overlap_d1,
  overlaptable.d2 as overlap_d2

FROM
  test as basetable, 
  test as overlaptable
WHERE
  -- there is an inclusive overlap
  basetable.d1 <= overlaptable.d2 and basetable.d2 >= overlaptable.d1
AND
  -- require lexicographic order: basetable starts later / finishes earlier / its letter is less then overlaptable
  basetable.d1 > overlaptable.d1 OR (basetable.d1 = overlaptable.d1 
                                     AND (basetable.d2 < overlaptable.d2 OR (basetable.d2 = overlaptable.d2 
                                                                             AND basetable.letter < overlaptable.letter)))
ORDER BY 
  overlaptable.d1, 
  basetable.d2,
  basetable.letter

【讨论】:

  • 谢谢,这似乎给了我正确的 4 行,包括 A 重叠的 C。我猜你正在将“字母”作为主键 - 这是我在我给出的小例子中的意图但为简洁起见,我没有明确说明。在实际应用程序中,我正在检索重叠的人们的名字、姓氏和日期,但每个人确实有一个数字 ID,因为他们的 PK 相当于我的表“测试”,所以我认为如果我用 member_ID 替换字母,你的解决方案将有效在 WHERE 子句中。
  • 是的,我猜“字母”是主键,因为它在您的查询中用于排除同一行。如果 member_ID 确实是 PK,您可以放心地将 member_ID 替换为字母。
  • 感谢您花时间开发 'where' 子句 Serg,我给了它一个赞成票。但是经过考虑,我已将 Gordon 的答案标记为正确,因为它准确地生成了我正在寻找的表,并且通过使用连接能够使用更简单的逻辑。
猜你喜欢
  • 2018-03-03
  • 1970-01-01
  • 2020-04-24
  • 1970-01-01
  • 2022-01-25
  • 2012-04-12
  • 1970-01-01
  • 2013-04-02
  • 2013-10-21
相关资源
最近更新 更多