彻底搞懂 SQL 中的 JOIN 与 ON:原理、区别与避坑指南

📅 2026/7/15 6:30:08
彻底搞懂 SQL 中的 JOIN 与 ON:原理、区别与避坑指南
只要用到JOIN多表联查就必须用ON单表查询永远不用ON。LEFT JOIN 想保留哪张表条件就跟在哪个 ON 后面。很多同学可能会把ON和WHERE混淆甚至在LEFT JOIN中因为写错位置导致数据错误。本文将从原理到实战帮你彻底吃透ON的所有用法。1.ON的本质表与表之间的“连接条件”在 SQL 中ON唯一的职责就是定义两张表如何匹配每一行数据。当你写下JOIN时数据库会拿着左表的每一行去右表里找满足ON条件的行。如果找到就拼成一行找不到则取决于连接类型决定是否保留左表数据。表1 JOIN 表2 ON 表1.字段 表2.字段ON后面的条件可以看作连接阶段的过滤规则。它是JOIN语法不可分割的一部分没有ON的JOIN除了CROSS JOIN和NATURAL JOIN会直接报错。2.ON支持的所有连接类型ON可以和以下所有连接搭配使用连接类型语法说明内连接INNER JOIN ... ON只返回两表都满足条件的行左外连接LEFT JOIN ... ON保留左表所有行右表无匹配则填 NULL右外连接RIGHT JOIN ... ON保留右表所有行左表无匹配则填 NULL全外连接FULL OUTER JOIN ... ON保留两表所有行无匹配填 NULL-- 示例用户表 user订单表 ordersSELECT *FROM userLEFT JOIN orders ON user.id orders.uid;2.1 实例演示NULL 到底填在哪里假设有两张简单的表user 表idname1张三2李四3王五orders 表iduidproduct1011手机1021电脑1034耳机注意orders 表中 uid4 的用户在 user 表中不存在user 表中 id2、3 的用户没有订单。INNER JOIN内连接SELECT * FROM user INNER JOIN orders ON user.id orders.uid;结果只返回两表都能匹配上的行。user.idnameorders.iduidproduct1张三1011手机1张三1021电脑❌ 没有 NULL因为不匹配的行直接丢弃。LEFT JOIN左外连接SELECT * FROM user LEFT JOIN orders ON user.id orders.uid;结果左表 (user) 全部保留右表无匹配的字段填 NULL。user.idnameorders.iduidproduct1张三1011手机1张三1021电脑2李四NULLNULLNULL3王五NULLNULLNULL 李四和王五没有订单所以右边 orders 的列全是 NULL。RIGHT JOIN右外连接SELECT * FROM user RIGHT JOIN orders ON user.id orders.uid;结果右表 (orders) 全部保留左表无匹配的字段填 NULL。user.idnameorders.iduidproduct1张三1011手机1张三1021电脑NULLNULL1034耳机 订单 103 的 uid4 在 user 表中不存在所以左边 user 的列全是 NULL。FULL OUTER JOIN全外连接SELECT * FROM user FULL OUTER JOIN orders ON user.id orders.uid;结果两表所有行都保留任意一边没匹配上对应字段填 NULL。user.idnameorders.iduidproduct1张三1011手机1张三1021电脑2李四NULLNULLNULL3王五NULLNULLNULLNULLNULL1034耳机 左边没匹配的李四、王五右表填 NULL右边没匹配的订单103左表填 NULL。一句话总结哪张表的数据没匹配上另一张表的列位置就会出现 NULL。3.ON和WHERE的核心区别重点3.1 执行逻辑上的根本差异对于INNER JOINON和WHERE的效果看起来可能一样但内部执行顺序完全不同ON在连接过程中过滤数据不满足条件的行根本不会被拼进中间结果集。WHERE在连接完成后对最终结果集进行筛选。3.2LEFT JOIN中的经典巨坑下面两条 SQL结果可能天差地别-- 写法1条件放在 ON 中SELECT *FROM userLEFT JOIN orders ON user.id orders.uid AND orders.money 100;-- 写法2条件放在 WHERE 中SELECT *FROM userLEFT JOIN orders ON user.id orders.uidWHERE orders.money 100;结果差异写法1左表user的所有行都会被保留。如果用户的订单金额没有大于 100 的右表字段会显示 NULL但用户本身不会丢失。写法2先完成左连接生成包含所有用户及订单的中间表没订单的右表为 NULL然后WHERE再过滤orders.money 100。注意NULL 100的结果是UNKNOWN会被WHERE过滤掉。因此没有订单或订单金额不满足条件的用户会整个消失实际上退化成内连接的效果。避坑指南如果想“保留左表所有数据同时只连接右表满足某种条件的行”一定要把该条件写在ON里。如果想“连接后只保留最终结果中满足条件的行”条件写在WHERE里。4.ON条件的多种写法4.1 多个条件用AND连接SELECT *FROM aLEFT JOIN b ON a.id b.a_id AND b.status active AND b.year 2024;4.2 非等值连接ON不限于可以用、、BETWEEN等。-- 匹配分数区间SELECT *FROM student sLEFT JOIN score_grade g ON s.score BETWEEN g.min_score AND g.max_score;4.3 复合条件ON a.dept_id b.dept_id AND a.salary b.avg_salary5. 多表连接的ON写法超过两张表时每增加一张表就要加一对JOIN ... ONSELECT *FROM userLEFT JOIN orders ON user.id orders.uidLEFT JOIN product ON orders.pid product.id;也可以换行缩进增强可读性。6. 不需要写ON的特殊连接连接类型说明CROSS JOIN笛卡尔积无条件拼接不需要ONNATURAL JOIN自动寻找两表同名字段进行等值连接不需要ONJOIN ... USING(字段)当两表关联字段同名时可用USING代替ON示例-- CROSS JOINSELECT * FROM a CROSS JOIN b;-- NATURAL JOIN不推荐字段变动时易出错SELECT * FROM a NATURAL JOIN b;-- USING要求关联列名相同SELECT * FROM a LEFT JOIN b USING(id);7. 常见误区与调试技巧❌ 误区一在单表查询中使用ON-- 错误会直接报语法错误SELECT * FROM user ON id 1;记忆方法没写JOIN就别写ON。❌ 误区二混淆ON和WHERE在LEFT JOIN中的作用遇到数据行数比预想少时先检查过滤条件是不是误放在了WHERE中。✅ 调试技巧先用不带任何过滤的LEFT JOIN查看基础连接结果。再逐步把非关联条件加到ON或WHERE中观察数据变化。使用COUNT(*)和COUNT(右表.字段)判断连接后是否有 NULL。8. 实战案例需求查询所有用户及其有效的、金额大于 100 的订单没有此类订单的用户也要显示。SELECT user.name, orders.id AS order_id, orders.moneyFROM userLEFT JOIN orders ON user.id orders.uidAND orders.status validAND orders.money 100;三个条件都写在ON里保证了user表数据不丢失。如果再把money 100挪到WHERE中就会丢失没有大额订单的用户。9. 最终口诀刻进 DNA连表用 ON筛选用 WHERE单表不用 ON。LEFT JOIN 想保留哪张表条件就跟在哪个 ON 后面。补充多表联查不用JOIN为什么用WHERE你可能会在老旧代码中看到这样的 SQLSELECT *FROM user, ordersWHERE user.id orders.uid;这里没有出现JOIN也没有ON但两张表通过WHERE条件连在了一起。这是怎么回事1. 这种写法叫“隐式连接”逗号连接在FROM后面用逗号列出多张表等价于先做一个笛卡尔积CROSS JOIN把每张表的每一行都无脑拼在一起然后再用WHERE筛选出真正匹配的行。上面的 SQL 其实相当于-- 隐式写法逗号SELECT * FROM user, orders WHERE user.id orders.uid;-- 等价于显式 INNER JOINSELECT * FROM user INNER JOIN orders ON user.id orders.uid;2. 为什么连接条件写在WHERE里因为隐式写法根本没有JOIN关键字自然也就没有地方写ON。数据库先生成所有组合笛卡尔积然后靠WHERE把不需要的行过滤掉连接条件被迫写在WHERE中。3. 这种写法有什么问题容易混淆连接条件和业务过滤条件。当条件一多你分不清哪个是表关联哪个是筛选。在 LEFT JOIN 场景下会直接失效。隐式连接只能表达内连接如果用()等特定数据库语法模拟外连接不仅难读还容易出错。可读性和维护性差不符合现代 SQL 标准。4. 现在的推荐写法一律使用显式JOIN ... ON把连接关系写在ON里业务过滤写在WHERE里结构清晰不会出错。-- 推荐SELECT *FROM userINNER JOIN orders ON user.id orders.uid -- 连接条件WHERE orders.money 100; -- 过滤条件