LEFT JOIN 核心是左表行数不变,右表无匹配时字段为 NULL;WHERE 中误写右表条件会使其退化为 INNER JOIN;右表多匹配导致左表行重复;NULL 值需用 COALESCE 等处理避免计算错误。
LEFT JOIN 保证左表的每一行都出现在结果中,哪怕右表没有匹配项——此时右表字段全为 NULL。这不是“筛选”或“补全”,而是“保留左表主干 + 尝试拼接右表”。关键点在于:只要没在 WHERE 里对右表字段做非空限制,就不会意外过滤掉左表行。
常见错误是把本该放在 ON 子句里的右表条件挪到 WHERE 中。一旦 WHERE right_table.status = 'active' 这类判断生效,那些右表没匹配(即 status 是 NULL)的左表行就被筛掉了。
正确做法:
ON 里写关联逻辑和右表的宽松过滤(如 ON a.id = b.a_id AND b.deleted = 0)WHERE 里只写左表自身的约束(如 WHERE a.created_at > '2024-01-01')如果右表对左表某一行有多个匹配(比如一个订单对应三条物流记录),LEFT JOIN 会生成多行。这不是 bug,是预期行为。但若你只想取“最新一条”或“任意一条”,就得加子查询或窗口函数。
例如取每个用户的最新登录记录:
SELECT u.*, l.last_loginFROM users uLEFT JOIN ( SELECT user_id, MAX(created_at) AS last_login FROM logins GROUP BY user_id) l ON u.id = l.user_id
LEFT JOIN 后右表字段可能为 NULL,直接参与 SUM()、COUNT() 或字符串拼接时容易出错。比如 COUNT(b.id) 统计的是右表非空匹配数,而 COUNT(*) 才是左表总行数。
常用应对方式:
COALESCE(b.amount, 0) 替代裸字段参与数值运算b.name IS NOT NULL 显式判断是否存在匹配GROUP BY 中混用左表和右表字段,除非确认右表字段不会为 NULL
NULL,最后看聚合或过滤有没有悄悄吃掉本该保留的行——这些地方最容易漏。