left join 因右表过滤条件误写在where子句而失效,正确做法是将右表筛选条件移至on子句;查“左表有、右表无”须用右表主键is null,而非其他字段;null传播需用coalesce显式兜底。

LEFT JOIN 因右表过滤失效,根本原因是把右表筛选条件写在 WHERE 里——它会在连接完成后才执行,直接过滤掉所有右表为 NULL 的行,等价于 INNER JOIN。
WHERE 里写了右表字段,LEFT JOIN 就失效了
典型错误:写 LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid'。执行顺序是先 JOIN 再 WHERE,此时没匹配到订单的用户,o.status 是 NULL,而 NULL = 'paid' 判定为 FALSE,整行被丢弃。
- 验证方法:对比
COUNT(*)和COUNT(o.id),若前者远大于后者,且你本意是查所有用户,说明 WHERE 位置错了 - 别用
WHERE o.status IS NOT NULL补救——这已违背 LEFT JOIN 语义,不如直接改 INNER JOIN - EXPLAIN 中若
Extra出现Using where且涉及右表字段,基本可判定逻辑变异
右表筛选条件必须塞进 ON 子句
正确写法是把业务过滤逻辑和连接条件一起放在 ON 里:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'。这样 o.status = 'paid' 只影响“是否建立连接”,不决定“是否保留左表行”。
- 多层 JOIN 时,每层右表条件都得进对应
ON,例如LEFT JOIN order_items oi ON o.id = oi.order_id AND oi.category = 'electronics' - MySQL 5.7 及更早版本不建议在
ON里用子查询;PostgreSQL/SQL Server 支持更好,但复杂表达式仍建议拆到 CTE -
ON中不能用COALESCE(o.status, 'draft') = 'paid'这类兜底函数——NULL 在 ON 阶段不支持这种处理
查“左表有、右表无”的情况,必须用右表主键 IS NULL
想找出从未下单的用户,正确写法是 WHERE o.id IS NULL(假设 o.id 是订单主键),而不是 o.status IS NULL 或 o.created_at IS NULL。
-
o.id IS NULL是唯一能 100% 确认“未匹配”的信号,因为主键不可能为 NULL - 用
NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)更稳,尤其在大表上,优化器更容易走索引 - 避免
IN (SELECT ...),右表子查询返回 NULL 会导致整个 IN 判定为 UNKNOWN,结果为空
多表关联时别让上层 NULL 污染下层连接
连续 LEFT JOIN 时,第二层依赖第一层结果。如果 o.product_id 是 NULL(比如用户没下单),那么 ON o.product_id = p.id 必然失败,p.* 全为 NULL——这不是 bug,是逻辑必然。
- 解决方案:连接字段尽量来自最左侧主表,例如用
u.preferred_product_id = p.id替代o.product_id = p.id - 别跳过中间表直接拉远端字段,比如不能用
LEFT JOIN products p ON u.preferred_product_id = p.id绕过 orders 表,语义断裂 - 所有表别名必须一致,不能前边用
u,后边写users.id,否则报错或结果不可控
真正容易被忽略的是:LEFT JOIN 后对右表字段做算术或字符串运算(如 o.amount * 1.1 或 '订单:' || o.order_no)会因 NULL 传播导致整列变空,必须用 COALESCE(o.amount, 0) 或 COALESCE(o.order_no, '无') 显式兜底——这一步常被跳过,直到报表导出时突然全 blank。










