问题
I have an ordinary one-to-many relation:
customer.id = order.customerid
I want to find customers who have no associated orders.
I tried:
-- one record
select * from customers where id = 123
-- no records
select * from orders where customerid = 123
-- NO RECORDS
select *
from customers
where id not in (select customerid from orders)
-- many records, as expected.
select *
from customers
where not exist (select customerid from orders
where customers.customerid = customer.id)
Am I mistaken, or should it work?
回答1:
NOT IN
does not behave as expected when the in-list contains NULL
values.
In fact, if any values are NULL
, then no rows are returned at all. Remember: In SQL, NULL
means "indeterminate" value, not "missing value". So, if the list contains any NULL
value then it might be equal to a comparison value.
So, customerid
must be NULL
in the orders
table.
For this reason, I strongly recommend that you always use NOT EXISTS
with a subquery rather than NOT IN
.
回答2:
I normally do this via a left join that looks for nulls created when the join fails:
SELECT c.*
FROM
customers c
LEFT JOIN orders o
ON c.id = o.customerid
WHERE
o.customerid IS NULL
Left join treats the customer table as "solid" and connects orders to it where there is an order with a given customer id and puts nulls where there isn't any matching order, hence the orders side of the relationship has "holes" in the data. By then saying we only want to see the holes (via the where clause), we get a list of "customers with no orders"
As per the comments I've always worked to the rule "do not use IN for lists longer than you'd be prepared to write by hand" but increasingly optimisers are rewriting IN, EXISTS and LEFT JOIN WHERE NULL queries to function identically as they're all recognised patterns of "data in A that has no matching data in B"
来源:https://stackoverflow.com/questions/57085105/t-sql-not-in-select-not-working-as-expected