子查询校验需优先用exists而非in以避免null导致的三值逻辑问题,聚合校验须分清where与having时机,相关子查询应改写为join并加索引优化。

子查询写在 WHERE 里校验单条记录是否合法
用 WHERE ... IN (SELECT ...) 或 WHERE EXISTS (SELECT ...) 是最常见、也最容易出错的校验方式。关键不是“能不能写”,而是“查什么”和“空结果怎么处理”。
-
IN遇到子查询返回NULL时整条条件变UNKNOWN,结果被过滤掉——这不是 bug,是 SQL 三值逻辑的必然表现 - 想校验“某订单的客户 ID 必须存在于客户表”,用
EXISTS更安全,它不关心 NULL,只看有没有匹配行 - 子查询里别写
SELECT *,只SELECT 1或主键,避免无谓字段传输和优化器误判
示例:检查订单表中所有 customer_id 是否真实存在
SELECT order_id, customer_id FROM orders WHERE NOT EXISTS ( SELECT 1 FROM customers WHERE customers.id = orders.customer_id );
用子查询做跨表字段一致性比对(比如金额对账)
当需要确认 A 表某字段总和是否等于 B 表对应字段总和,直接 SUM() 对比会漏掉空值或聚合粒度不一致的问题。
- 先用子查询分别算出两个总和,再在外层用
=或ABS(a - b) 判断——注意浮点数不能直接用 <code>= - 如果 A 表是明细、B 表是汇总,子查询必须带
GROUP BY对齐维度,否则一对多会导致笛卡尔膨胀 - 某些数据库(如 MySQL 5.7)对子查询里用
ORDER BY+LIMIT会报错,校验逻辑里尽量避免这种组合
示例:核对每日销售明细总金额 vs 日结汇总表
SELECT (SELECT SUM(amount) FROM sales WHERE DATE(created_at) = '2024-06-01') AS detail_sum, (SELECT total_amount FROM daily_summary WHERE date = '2024-06-01') AS summary_sum;
相关子查询性能崩了怎么办
当子查询里引用了外层表字段(比如 WHERE t1.id = t2.ref_id),数据库可能对每行外层数据都执行一次子查询——这就是相关子查询,也是校验类 SQL 慢的元凶。
- 先看执行计划,确认是否真用了 Nested Loop;如果是,优先改写成
JOIN或LEFT JOIN ... WHERE right.id IS NULL - 给子查询涉及的关联字段加索引,但注意:MySQL 对
IN (subquery)的子查询字段索引支持有限,EXISTS更友好 - PostgreSQL 可以用
LATERAL显式控制执行顺序,但多数业务 SQL 不值得为这点可控性增加复杂度
WHERE 子句里嵌套子查询 vs HAVING 子句里嵌套子查询
校验聚合后数据时,容易把本该放 HAVING 的逻辑硬塞进 WHERE,结果要么报错,要么逻辑错位。
-
WHERE在分组前过滤,不能用聚合函数;HAVING在分组后过滤,可以且必须用SUM、COUNT等 - 想查“哪些客户的订单总金额超 1 万”,子查询若用于计算客户级总金额,就必须出现在
HAVING中,而不是WHERE - 有些方言(如 SQLite)允许
WHERE里写聚合,但行为不可靠,别依赖
示例:找出订单总金额异常的客户(对比历史均值)
SELECT customer_id
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > (
SELECT AVG(cust_total) FROM (
SELECT customer_id, SUM(amount) AS cust_total
FROM orders GROUP BY customer_id
) t
);
实际写校验 SQL 时,最常被忽略的是子查询的空结果语义和聚合时机——这两点不厘清,表面能跑通,一上生产就漏数据或误报。










