Select rows in a self-relationship where all children meet a condition

本秂侑毒 提交于 2019-12-08 11:18:58

问题


I am working with a single table (called documents) with the following fields: id, parent_id, and status. The parent_id field refers to the id field in the same table. The status field is of type ENUM('submitted', 'accepted', 'rejected').

I would like to select all documents that have no children where status = 'accepted'.

My first attempt looked like this:

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'

The problem with this is that a document with both accepted and unaccepted children will still be selected. No document with any accepted children should be selected.

I have a feeling GROUP BY might be my friend, but I can't figure out how I would use it to get the intended result.


回答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



回答2:


I solved this with the 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

This selects all documents, regardless of whether they have children, and counts the number of accepted children they have. That number must be zero for the row to be selected.

Edit: For the curious, I managed to emulate the query in 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);



回答3:


If I understand correctly what you're looking for, I would use the following approach to keep things simple.

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



回答4:


The fastest way in MySQL is probably like this:

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
                 )

This uses a correlated subquery to identify any children documents that fail the condition.



来源:https://stackoverflow.com/questions/12082353/select-rows-in-a-self-relationship-where-all-children-meet-a-condition

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!