【问题标题】:Select rows in a self-relationship where all children meet a condition选择所有孩子都满足条件的自我关系中的行
【发布时间】:2012-08-22 22:37:54
【问题描述】:

我正在使用具有以下字段的单个表(称为 documents):id、parent_id 和 status。 parent_id 字段是指同一个表中的id 字段。 status 字段的类型为 ENUM('submitted', 'accepted', 'rejected')。

我想选择所有documents 有没有 孩子的status = 'accepted'。

我的第一次尝试是这样的:

SELECT DISTINCT `documents`.*
FROM (`documents`)
LEFT OUTER JOIN `documents` children_documents
  ON `documents`.`id` = `children_documents`.`parent_id`
WHERE `children_documents`.`id` IS NULL
  OR `children_documents`.`status` != 'accepted'

这样做的问题是仍然会选择包含已接受和未接受子项的文档。不应选择包含任何已接受子级的文档。

我感觉GROUP BY 可能是我的朋友,但我不知道如何使用它来获得预期的结果。

【问题讨论】:

    标签: mysql sql join tree self-join


    【解决方案1】:
    SELECT DISTINCT `documents`.*
    FROM (`documents`)
    LEFT OUTER JOIN `documents` children_documents
      ON `documents`.`id` = `children_documents`.`parent_id`
      AND `children_documents`.`status` = 'accepted'
    WHERE `children_documents`.`parent_id` IS NULL
    

    【讨论】:

    • 如果我理解正确,最后一行也可以检查'children_documents'.'id'。您的解决方案更优雅,因此我将其标记为已接受。不幸的是,如果我的 ORM 库不支持多个 ON 条件,我可能最终不会使用它。
    • 是的,在左外连接中,当正在连接的表中没有匹配项时,该表中的所有列都将为 NULL,因此您可以检查其中的任何一个。作为个人风格,我喜欢使用加入条件中提到的那个。否则,您必须注意可能包含显式 NULL 的列。
    【解决方案2】:

    我用MySQL CASE statement 解决了这个问题。

    SELECT DISTINCT `documents`.*
    FROM (`documents`)
    LEFT OUTER JOIN `documents` children_documents
      ON `documents`.`id` = `children_documents`.`parent_id`
    GROUP BY `documents`.`id`
    HAVING SUM(CASE `children_documents`.`status` WHEN 'accepted' THEN 1 ELSE 0 END) = 0
    

    这会选择所有文档,无论它们是否有孩子,并计算他们拥有的已接受孩子的数量。对于要选择的行,该数字必须为零。

    编辑:出于好奇,我设法在 DataMapper ORM (CodeIgniter) 中模拟查询:

    $d->distinct()->where('status', 'accepted')->group_by('id')
      ->having_func('!SUM', array('[CASE]', '@children/status', '[WHEN]', 'accepted', '[THEN]', 1, '[ELSE]', 0, '[END]'), NULL);
    

    【讨论】:

    • @bluefeet:StackOverflow 直到两天后才会让你自我接受。
    【解决方案3】:

    如果我正确理解您在寻找什么,我会使用以下方法来保持简单。

    SELECT *
    FROM `documents`
    WHERE `id` NOT IN ( 
        SELECT `parent_id`
        FROM `documents` children_documents
        WHERE `status` = 'accepted' );
    

    【讨论】:

      【解决方案4】:

      MySQL中最快的方式大概是这样的:

      select d.*
      from documents d
      where not exists (select 1
                        from documents c
                        where c.parent_id = d.id and
                              coalesce(c.status, '') = 'Accepted'
                        limit 1
                       )
      

      这使用相关子查询来识别任何不符合条件的子文档。

      【讨论】:

      • 子查询真的比连接快吗?
      • @HenryMerriam 。 . .这种变化应该是。从表中实际返回数据没有任何开销。另外,当有多个匹配时,它应该将结果短路并在第一个匹配处停止。
      • 仔细观察,我相信状态检查应该是...AND c.status == 'accepted'。 (我正在尝试选择没有接受子级的文档。)这是一种有趣的方法。
      • @HenryMerriam 。 . .你是对的。那些讨厌的双重否定。它想在第一次发现一个不可接受的孩子时停止,这将是一个被接受的孩子。
      • 另外,由于 c.status 是用 NOT NULL 定义的(我知道我没有给你表定义)我可以摆脱 coalesce() 调用吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-07
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 2017-07-15
      • 2019-04-24
      • 1970-01-01
      相关资源
      最近更新 更多