SQL 如何用子查询实现反查询?

10次阅读

反查询是查不在某集合里的记录,应优先用NOT EXISTS替代WHERE NOT IN以避免NULL导致结果为空;也可用LEFT JOIN+IS NULL实现,但需确保关联字段有索引且正确判空。

SQL 如何用子查询实现反查询?

什么是反查询:用 NOT EXISTS 替代 WHERE NOT IN

反查询本质是“查不在某集合里的记录”,但直接写 WHERE id NOT IN (select id FROM ...) 在子查询返回 NULL 时会整个结果为空——这是最常踩的坑。根本原因是 sql 中任何与 NULL 的等值比较(包括 NOT IN)都返回 UNKNOWN,被当作假值过滤掉。

正确做法是改用 NOT EXISTS,它不依赖值比较,只判断子查询是否返回行:

SELECT name FROM users u WHERE NOT EXISTS (   SELECT 1 FROM orders o WHERE o.user_id = u.id );
  • NOT EXISTSNULL 安全,子查询里哪怕有 NULL 字段也不影响外层逻辑
  • 必须写关联条件(如 o.user_id = u.id),否则变成“只要子查询无结果就全选”,失去反查意义
  • 子查询中 SELECT 1 是惯用写法,比 SELECT * 更轻量,数据库通常会忽略实际列内容

LEFT JOIN + IS NULL:更直观的反查询写法

当需要反查字段较多、或想顺便看匹配失败原因时,LEFT JOIN 比子查询更易读且调试友好:

SELECT u.name, u.email FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.user_id IS NULL;
  • 关键在 WHERE o.user_id IS NULL,不是 o.id IS NULL——因为 LEFT JOIN 后未匹配的右表行所有字段都是 NULL,但用外键字段判空最稳妥
  • 如果 orders.user_id 允许为 NULL,这个写法可能误判;此时应优先用 NOT EXISTS
  • 性能上,现代优化器对这两种写法通常生成相同执行计划,但 LEFT JOIN 在涉及多表或复杂条件时更容易加索引提示

子查询里用 NOT IN 的唯一安全场景

只有当你能 100% 确保子查询结果不含 NULL,才可放心用 NOT IN。常见于明确过滤后的主键查询:

SELECT name FROM users WHERE id NOT IN (   SELECT user_id FROM orders WHERE user_id IS NOT NULL );
  • 必须显式加 WHERE user_id IS NOT NULL,不能依赖业务假设
  • 如果子查询来自视图或复杂嵌套,很难保证无 NULL,这时宁可换 NOT EXISTS
  • mysql 8.0+ 和 postgresql 支持 NOT IN (VALUES (1),(2)) 这类字面量列表,无 NULL 风险,但实用性有限

性能陷阱:相关子查询别漏掉索引

NOT EXISTSNOT IN 都是相关子查询,数据库需对外表每行执行一次子查询。若没索引,性能会断崖式下跌:

  • 检查子查询中的关联字段(如 orders.user_id)是否有索引,没有就加:CREATE INDEX idx_orders_user_id ON orders(user_id);
  • PostgreSQL 中可用 EXPLAIN ANALYZE 看是否走了 Hash Anti JoinNested Loop Anti Join;MySQL 则关注 type 是否为 refeq_ref
  • oracle 用户注意:NOT EXISTS 在某些版本下可能比 LEFT JOIN 多一次全表扫描,务必实测

反查询看着简单,但 NULL 处理和索引缺失是最容易被跳过的两个点,一漏就查不到数据或查得极慢。

text=ZqhQzanResources