As Uueerdo demonstrated, use an anti-join pattern.
SELECT a.*
FROM `table A` a
LEFT
JOIN `table B` b
ON b.fname = a.fname
AND b.lname = a.lname
WHERE b.fname IS NULL
Let's break that down a bit.
It's an outer join, to return all rows from A
, along with matching rows from B
, with a twist, the condition in the WHERE
clause.
Any rows where we found a match in table B
, we are guaranteed the both b.fname
and b.lname
will be non-null. (If either of the columns had been NULL, the equality condition in the join predicate wouldn't have been satisfied, and the row from B wouldn't have been matched.
Any rows from A
that didn't have a match found in B
will have NULL values for all of the columns in B
. If we exclude all rows that have a non-NULL value in any column from B
that is guaranteed to be non-NULL, what we are left with is rows from A
that didn't have a match in B
.
The anti-join is a very useful pattern to have in our SQL toolbelts.
An equivalent result can be obtained using a NOT EXISTS
pattern.
(See answer from Nick for a demonstration of that.)
Curiously, in MySQL 5.6, with appropriate index available, for the "anti-join" query pattern, the EXPLAIN output actually shows "not exists".
-- explain antijoin
id select_type table type key rows Extra
-- ------------------ ----- ---- ---- ------ ------------------------------------
1 SIMPLE a ALL 210339
1 SIMPLE b ref FK_a 1 Using where; Not exists; Using index
For an equivalent "not exists" query, the EXPLAIN output is slightly different, showing a dependent subquery.
-- explain "not exists" equivalent
id select_type table type key rows Extra
-- ------------------ ----- ---- ---- ------ ------------------------------------
1 PRIMARY a ALL 210339 Using where
2 DEPENDENT SUBQUERY b ref FK_a 1 Using index
Personally, I'd opt for the anti-join, because I tend to see the best performance with that. (It's possible that the NOT EXISTS
pattern will give better performance, less likely we will see better performance with a NOT IN (subquery)
.