问题
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