WHERE条件放ON里控制连接逻辑,放WHERE里则过滤结果;LEFT JOIN需将右表过滤条件置于ON中,否则丢失左表记录。

WHERE 条件写在 JOIN 里还是 ON 里,结果可能完全不同
多表关联时加条件,最容易踩的坑就是把本该在 ON 子句里的过滤逻辑挪到 WHERE,尤其涉及 LEFT JOIN。比如想查「所有用户及其最近一笔订单金额(没有订单的用户也保留)」,却写了 WHERE order.status = 'paid'——这会直接把没订单或订单非 paid 的用户全踢掉,LEFT JOIN 形同虚设。
关键区别:ON 控制“怎么连”,WHERE 控制“连完后怎么筛”。对左表保留有要求的场景,过滤右表数据必须放在 ON 里。
- 需要保留左表全部记录?右表条件一律进
ON,别碰WHERE - 只想要匹配成功的记录?用
INNER JOIN+WHERE更直白,也更易读 -
ON中允许用AND连接多个条件,但不能用子查询或聚合函数
CASE WHEN 放在 ON 里做条件化 JOIN,不是所有数据库都支持
想根据左表字段值动态决定 JOIN 哪张右表(比如 user.type = 'vip' 时 join vip_orders,否则 join normal_orders),有人会写 ON CASE WHEN u.type = 'vip' THEN o1.id = u.order_id ELSE o2.id = u.order_id END——语法错误。SQL 标准不支持在 ON 里直接用 CASE WHEN 生成连接条件。
可行解法是用两个 LEFT JOIN + CASE WHEN 在 SELECT 中选值:
SELECT u.id,
CASE WHEN u.type = 'vip' THEN o1.amount
ELSE o2.amount END AS order_amount
FROM users u
LEFT JOIN vip_orders o1 ON u.order_id = o1.id AND u.type = 'vip'
LEFT JOIN normal_orders o2 ON u.order_id = o2.id AND u.type != 'vip'
- MySQL、PostgreSQL、SQL Server 都支持这种写法;SQLite 不支持
ON中的表达式过滤(需升级到 3.39+) - 每个
LEFT JOIN的ON必须包含类型判断,否则会出现交叉匹配 - 如果右表数据量大,两个 JOIN 会各自扫描一遍,注意加好索引(如
(type, order_id)联合索引)
用 CASE WHEN 替代多次 UNION,但要注意 NULL 和类型对齐
当需要合并不同条件下的统计结果(比如按地区分组,华东用销售额,华北用订单数),有人倾向写三个 UNION ALL。其实用一个 CASE WHEN 配合聚合更简洁,但容易忽略两件事:分支返回值类型要一致、漏处理 ELSE 导致字段为 NULL。
例如统计各地区核心指标:
SELECT region,
SUM(CASE WHEN region IN ('上海', '江苏', '浙江') THEN sales
WHEN region IN ('北京', '天津', '河北') THEN order_count
ELSE 0 END) AS metric
FROM orders
GROUP BY region
- 所有
THEN分支必须返回相同类型,数字就都用数字,字符串就都用字符串;混用会触发隐式转换甚至报错 - 没写
ELSE时,不满足任一条件的行该字段为NULL,参与SUM会被跳过——这未必是你想要的 - 若分支逻辑复杂,先抽成 CTE 或子查询,比堆砌长
CASE更易维护
JOIN 后再用 CASE WHEN 做条件聚合,比预过滤更灵活但也更慢
想查「每个用户的付费订单数 vs 总订单数」,可以先 LEFT JOIN 订单表再用 CASE WHEN order.status = 'paid' 统计,也可以先用子查询分别算两个数再 JOIN。前者写法简单,后者通常更快。
原因:JOIN 后再 CASE 意味着数据库要先拉出所有订单记录(包括 unpaid 的),再逐行判断;而子查询能利用索引快速定位 status = 'paid' 的行,减少中间数据量。
- 小表或低基数状态字段(如只有 2–3 种 status),JOIN + CASE 影响不大
- 大表且 status 离散度高(如含几十种状态),优先考虑预聚合或物化中间结果
- PostgreSQL 可用
FILTER子句替代CASE(如COUNT(*) FILTER (WHERE status = 'paid')),语义更清晰,优化器也更友好

















