Reputation: 2606
Here's SQL fiddle.
I have a table with a list of names. I want to get a list of all the names that do not have an entry in a related table with a particular status ('S') or a field in a 3rd table (related to the second) that meet a certain status ('A').
I've been able to accomplish this with the subquery shown in the SQL fiddle and below, but I'd like to do this without a subquery if possible.
SELECT * FROM name_table LEFT JOIN
(SELECT b.name_id FROM b LEFT JOIN c ON (b.c_id = c.id) WHERE (c.c_status = 'A') OR (b.b_status='S'))
results ON (name_table.id=results.name_id)
WHERE results.name_id IS NULL;
In my example, I'd like rows "Ted Andrews" and "Jack Johnson"
"John Doe" is not included because table C has a row with status 'A' "Bill Smith" is not included because table B has status 'S' "Jim Scott" is not included because table C has a row with status 'A' (even though there is another row without status 'A' in table C)
SELECT * FROM name_table
LEFT JOIN b ON (name_table.id = b.name_id)
LEFT JOIN c ON (b.c_id = c.id) AND (c_status = 'A');
WHERE (b.b_status IS NULL) OR ((b.b_status <> 'S') AND (c.id IS NULL));
was an attempt that incorrectly includes "Jim Scott"
Upvotes: 0
Views: 50
Reputation: 17915
SELECT name_table.id, min(first) as first, min(last) as last
FROM name_table
LEFT JOIN b ON name_table.id = b.name_id
LEFT JOIN c ON b.c_id = c.id
GROUP BY name_table.id
HAVING
count(case when b.b_status = 'S' then 1 end) = 0
and count(case when c.c_status = 'A' then 1 end) = 0;
Since you have the requirement that you must look at multiple rows in c
to determine whether you have a match you'll find yourself using some kind of aggregation. Personally I think the query isn't so horrible.
Upvotes: 1
Reputation: 2302
I'd do it like this (sqlfiddle):
SELECT nt.first, nt.last
FROM name_table nt
LEFT JOIN b ON nt.id = b.name_id
LEFT JOIN c ON nt.id = c.a_id
GROUP BY first, last
HAVING SUM(IF(b.b_status = 'S',1,0)) = 0
AND SUM(IF(c.c_status = 'A',1,0)) = 0;
The having bit just counts instances of the status symbols that exclude people from your desired result set and requires none.
Upvotes: 2
Reputation: 35573
not exists
is helpful in these situations
select
*
from name_table t
where not exists (select null from B
where t.id = b.name_id and b.b_status = 'S')
and not exists (select null from C
where t.id = c.a_id and c.c_status = 'A')
| id | first | last |
|----|-------|----------|
| 3 | Ted | Anderson |
| 5 | Jack | Johnson |
Upvotes: 0
Reputation: 1520
Here's a way that doesn't use subqueries, although I'd want to check the performance of this against a large data set:
SELECT a.*
FROM name_table AS a
LEFT JOIN b ON a.id = b.name_id
LEFT JOIN c ON b.c_id = c.id
GROUP BY a.id
HAVING MIN((b.b_status IS NULL OR b.b_status <> 'S') AND (c.c_status IS NULL OR c.c_status <> 'A'));
The HAVING
clause evaluates to 0 or 1 for each row to indicate if it meets your criteria, and then takes the minimum of this across all names to ensure that Jim Scott is excluded - only names where all joined rows match your criteria are included.
Upvotes: 0
Reputation: 699
Even though you said Jim Scott should not be included, it looked to me like he does not have a b_status of 'S' or a c_status of 'A'. Is this what you're looking for?
select * from name_table
join b on name_table.id = b.name_id and b.b_status <> 'S'
join c on b.c_id = c.id and c.c_status <> 'A';
Upvotes: 0