优先选 not exists。它能正确处理 null 值,而 not in 遇子查询含 null 时返回空集;正确写法是 where not exists (select 1 from orders o where o.user_id = u.id and o.product_name = 'iphone')。

子查询用 NOT EXISTS 还是 NOT IN?
优先选 NOT EXISTS。它能正确处理 NULL 值,而 NOT IN 遇到子查询结果含 NULL 时直接返回空集——这是最常踩的坑。
比如用户表 users 和订单明细表 orders 中,若 orders.product_id 允许为 NULL,用 NOT IN (SELECT product_id FROM orders WHERE ...) 会意外过滤掉所有用户。
实操建议:
-
NOT EXISTS性能通常更好,尤其当外层数据量大、内层可走索引时 - 子查询里必须关联外层表(如
WHERE o.user_id = u.id),否则变成非相关子查询,逻辑错误 - 避免在
NOT IN子句中 SELECT 可能为NULL的字段
查“未购买过 iPhone”的用户,怎么写子查询?
假设 users 表有 id、name,orders 表有 user_id、product_name,目标是找出从没下过 'iPhone' 订单的用户。
正确写法:
SELECT u.id, u.name
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
AND o.product_name = 'iPhone'
);
关键点:
-
SELECT 1是惯用写法,比SELECT *更轻量,语义也更清晰 -
AND o.product_name = 'iPhone'必须放在子查询的WHERE里,不能提到外层——否则逻辑变成“存在订单且商品名不是 iPhone”,完全跑偏 - 如果
orders表很大,确保(user_id, product_name)有联合索引,否则性能急剧下降
LEFT JOIN 能不能替代子查询?
能,而且有时更直观。但要注意 ON 条件和 WHERE 条件的位置差异。
等价写法:
SELECT u.id, u.name FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.product_name = 'iPhone' WHERE o.user_id IS NULL;
常见错误:
- 把
o.product_name = 'iPhone'放到WHERE子句:会导致 LEFT JOIN 变成 INNER JOIN 效果,漏掉本该匹配上的用户 - 用
o.id IS NULL判断缺失——不安全,因为orders.id可能允许NULL;稳妥做法是判断关联键o.user_id IS NULL - 如果需要排除多个商品(如“未买过 iPhone 且未买过 iPad”),
NOT EXISTS更易扩展,LEFT JOIN会迅速变复杂
子查询返回多列会影响性能或结果吗?
不影响结果,但影响可读性和优化器判断。SQL 标准允许子查询中 SELECT 多列,但 EXISTS / NOT EXISTS 只关心是否存在行,不读取列值。
所以:
- 一律用
SELECT 1或SELECT NULL,别写SELECT *或SELECT user_id, product_name - 某些数据库(如 MySQL 5.7)对子查询列数敏感,多列可能触发临时表或禁用部分优化
- 如果子查询本身很重(比如带聚合或多表 JOIN),考虑先物化中间结果(如用 CTE 或临时表),但需权衡维护成本
真正容易被忽略的是子查询里的条件是否覆盖了业务语义全集——比如“未购买 iPhone”是指从未下单,还是指最近 30 天未下单?时间范围、状态字段(如 order_status = 'completed')这些细节一旦漏掉,结果就不可信。










