MySQL “NOT IN” query 3 tables

Avoid NOT IN like the plague if

SELECT ID_Courses FROM Evaluation where `NAME`='JOHN' and Year=1

could ever contain NULL. Instead, use NOT EXISTS or Left Joins

use explicit joins, not 1980’s style joins using the WHERE clause

To illustrate the misery of NOT IN:

SQL NOT IN () danger

create table mStatus
(   id int auto_increment primary key,
    status varchar(10) not null
);
insert mStatus (status) values ('single'),('married'),('divorced'),('widow');

create table people
(   id int auto_increment primary key,
    fullName varchar(100) not null,
    status varchar(10)  null
);

Chunk1:

truncate table people;
insert people (fullName,`status`) values ('John Henry','single');
select * from mstatus where `status` not in (select status from people);

** 3 rows, as expected **

Chunk2:

truncate table people;
insert people (fullName,`status`) values ('John Henry','single'),('Kim Billings',null);
select * from mstatus where status not in (select status from people);

no rows, huh?

Obviously this is ‘incorrect’. It arises from SQL’s use of three-valued logic,
driven by the existence of NULL, a non-value indicating missing (or UNKNOWN) information.
With NOT IN, Chunk2 it is translated like this:

status NOT IN ('married', 'divorced', 'widowed', NULL)

This is equivalent to:

NOT(status="single" OR status="married" OR status="widowed" OR status=NULL)

The expression “status=NULL” evaluates to UNKNOWN and, according to the rules of three-valued logic,
NOT UNKNOWN also evaluates to UNKNOWN. As a result, all rows are filtered out and the query returns an empty set.

Possible solutions include:

select s.status
from mstatus s
left join people p
on p.status=s.status
where p.status is null

or use not exists

Leave a Comment