本页内容受版权保护 · 已添加水印 · 禁止任何形式转载
查询基础(SELECT / JOIN / 聚合 / 排序) · JOIN 多表连接

USING / NATURAL JOIN 与 ON 的区别?为什么大多数生产环境不推荐 NATURAL JOIN?

What is the difference between USING / NATURAL JOIN and ON? Why is NATURAL JOIN not recommended in most

答案语言

考察要点

这道题面试官想看你是否深入理解了 SQL 中不同 JOIN 写法的语义差异、边界条件和工程实践中的取舍。核心考察点包括:

  1. 语法精确性:你是否清楚 ON, USING, NATURAL JOIN 在指定连接条件上的显式与隐式差异。
  2. 结果集结构:你是否知道这三种写法对连接列(Join Key)在最终输出结果中的处理方式是不同的。
  3. 代码健壮性与可维护性:你是否能从软件工程角度,分析出为什么隐式约定(NATURAL JOIN)在多人协作和需求变更频繁的生产环境中是高风险的。

核心概念

ON, USING, NATURAL JOIN 都是用于定义两个表如何连接的子句,但它们的显式程度、灵活性和对结果集的影响各不相同。

我们使用下面两张表作为示例: employees 表

sql
1CREATE TABLE employees (
2 emp_id INT PRIMARY KEY,
3 emp_name VARCHAR(50),
4 dept_id INT,
5 location_id INT
6);
7INSERT INTO employees VALUES
8(101, 'Alice', 10, 1),
9(102, 'Bob', 20, 2),
10(103, 'Charlie', 10, 1);

departments 表

sql
1CREATE TABLE departments (
2 dept_id INT PRIMARY KEY,
3 dept_name VARCHAR(50),
4 location_id INT
5);
6INSERT INTO departments VALUES
7(10, 'Engineering', 1),
8(20, 'Human Resources', 2),
9(30, 'Sales', 3);

1. ON 子句

ON 是最通用、最显式、功能最强大的连接条件。

  • 语法:... JOIN table2 ON table1.column_name = table2.column_name

  • 特点:

    • 显式指定:必须明确写出完整的连接条件,包括表名和列名。
    • 高度灵活:连接列可以有不同名称(ON e.dept_id = d.department_number),可以使用非等值连接(ON e.salary > s.min_salary),也可以包含复杂的 AND/OR 逻辑。
    • 结果集:连接列会分别出现在结果集中。如果列名相同,需要使用表别名来区分。
  • 示例:

sql
1SELECT *
2FROM employees e
3JOIN departments d ON e.dept_id = d.dept_id;
  • 输出:注意 dept_id 和 location_id 列都出现了两次。 | emp_id | emp_name | e.dept_id | e.location_id | d.dept_id | dept_name | d.location_id | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | 101 | Alice | 10 | 1 | 10 | Engineering | 1 | | 102 | Bob | 20 | 2 | 20 | Human Resources | 2 | | 103 | Charlie | 10 | 1 | 10 | Engineering | 1 |

2. USING 子句

USING 是当连接列在两个表中名称完全相同时,ON 的一种简写形式。

  • 语法:... JOIN table2 USING (column_name1, column_name2)

  • 特点:

    • 前提:连接列在两个表中必须同名。
    • 简洁:只需列出同名的连接列即可,比 ON 更简洁。
    • 结果集:连接列在结果集中只出现一次,自动合并,无需使用别名区分。
  • 示例:

sql
1SELECT *
2FROM employees
3JOIN departments USING (dept_id);
  • 输出:注意 dept_id 只出现一次。location_id 因为没有在 USING 中指定,所以仍然是两列。 | dept_id | emp_id | emp_name | location_id | dept_name | location_id | | :--- | :--- | :--- | :--- | :--- | :--- | | 10 | 101 | Alice | 1 | Engineering | 1 | | 20 | 102 | Bob | 2 | Human Resources | 2 | | 10 | 103 | Charlie | 1 | Engineering | 1 |

3. NATURAL JOIN

NATURAL JOIN 是一种更彻底的简写,它会自动连接两个表中所有同名的列。

  • 语法:... NATURAL JOIN table2

  • 特点:

    • 完全隐式:无需指定任何连接列,数据库会自动寻找两个表中所有名称相同的列作为连接条件。
    • 极度简洁:写法最简单。
    • 结果集:所有同名的连接列在结果集中都只出现一次。
    • 高风险:这是其在生产环境中不被推荐的核心原因。
  • 示例:

sql
1SELECT *
2FROM employees
3NATURAL JOIN departments;
  • 分析:employees 和 departments 表有 dept_id 和 location_id 两个同名列。因此,NATURAL JOIN 等价于 ... ON e.dept_id = d.dept_id AND e.location_id = d.location_id。
  • 输出: | dept_id | location_id | emp_id | emp_name | dept_name | | :--- | :--- | :--- | :--- | :--- | | 10 | 1 | 101 | Alice | Engineering | | 20 | 2 | 102 | Bob | Human Resources | | 10 | 1 | 103 | Charlie | Engineering |

为什么不推荐 NATURAL JOIN?

核心原因:隐式约定导致代码脆弱,极易在未来维护中引入不易察觉的 BUG。

假设 DBA 为了记录修改时间,给 employees 和 departments 两张表都增加了一个名为 last_updated 的字段。

sql
1ALTER TABLE employees ADD COLUMN last_updated TIMESTAMP;
2ALTER TABLE departments ADD COLUMN last_updated TIMESTAMP;

此时,原有的 NATURAL JOIN 查询的逻辑就被隐式地改变了。它的连接条件会从原来的: ON e.dept_id = d.dept_id AND e.location_id = d.location_id 悄悄地变成: ON e.dept_id = d.dept_id AND e.location_id = d.location_id AND e.last_updated = d.last_updated

这个新增的 last_updated 条件几乎不可能满足,将导致查询返回空集或错误的结果,而代码本身一行未改,这使得问题排查变得异常困难。ON 和 USING 则完全不受此影响,因为它们的连接条件是显式声明的。

常见低分回答

  1. 混淆 USING 和 NATURAL JOIN:“NATURAL JOIN 就是 USING 的简写,差不多。” —— 错误。USING 让你显式选择哪些同名列用于连接,而 NATURAL JOIN 是自动使用所有同名列,这是本质区别。
  2. 不清楚结果集列的差异:“它们都能连表,功能一样。” —— 忽略了 ON 会保留两个表的连接列,而 USING/NATURAL JOIN 会合并它们。这个差异在 SELECT * 或后续操作中至关重要。
  3. 对 NATURAL JOIN 的风险解释不清:“NATURAL JOIN 不好,因为不清晰。” —— 太笼统。高分回答必须能举出类似“增加 updated_at 或 version 公共字段导致连接条件隐式改变”的具体工程灾难场景。
  4. 误认为有性能差异:“ON 更灵活,所以性能可能更好/更差。” —— 错误。对于等值连接,JOIN ON t1.id = t2.id 和 JOIN USING(id) 在现代数据库优化器下生成的执行计划通常是完全相同的。它们的区别在于SQL语义和可维护性,而非性能。
  5. 忽略 SELECT * 陷阱:当使用 ON 时,SELECT * 会产生重复的列名,这可能导致依赖列名的应用层代码(如 ORM 框架)出错或产生歧义。虽然 USING 能避免,但在生产代码中应始终避免 SELECT *,明确列出所需字段。

工程实践与优化

  • 编码规范:强制使用 ON。这是最明确、最无歧义、最健壮的写法。代码的读者无需检查表结构就能理解连接逻辑。
  • 可维护性:ON 将连接逻辑和业务逻辑(WHERE 过滤)清晰地分离。NATURAL JOIN 将连接逻辑隐藏在表结构中,是高耦合的坏味道。
  • 调试效率:当 JOIN 结果不符合预期时,使用 ON 的查询,问题几乎总是在 ON 子句本身。而 NATURAL JOIN 则需要你去 DESC table1, DESC table2,然后人肉比对所有列名,才能找出实际生效的连接条件,效率低下。
  • 执行计划分析:虽然语法不同,但 EXPLAIN 一个 ... JOIN ... ON t1.id = t2.id 和 ... JOIN ... USING(id) 的查询,你会发现它们的 type, key, rows, Extra 等核心指标通常完全一致。优化器在解析完 SQL 后,会生成相同的内部连接逻辑。这证明了它们的区别不在于执行性能。
  • USING 的有限场景:在一些设计极度规范的数据仓库(DW)环境中,事实表和维度表的连接键(Surrogate Key)命名完全一致,USING 可以作为一种提升代码简洁度的选择。但即便如此,ON 的明确性依然更具优势。

常见追问与延伸题目

  1. 追问:既然 NATURAL JOIN 这么危险,那 SQL 标准为什么要设计它?

    • 回答要点:它的设计初衷是为了极致的简洁,特别是在交互式查询和某些理论模型中。当时的设计者可能低估了在大型、长期演进的软件工程项目中,这种隐式约定会带来的巨大风险。可以把它看作一个理想化设计与工程现实冲突的典型例子。在个人做数据探索、一次性分析时,如果对表结构了如指掌,用它来少打字也未尝不可,但绝不能进入生产代码库。
  2. 追问:SELECT * 在 JOIN 查询中有什么问题?USING 在这方面比 ON 有优势吗?

    • 回答要点:
      • 问题:在生产代码中 SELECT * 是反模式。1)它会查询不必要的列,增加网络IO和内存消耗。2)当表结构变更(如增删列)时,依赖列顺序或列数量的客户端代码可能会崩溃。3)对于 JOIN ... ON,SELECT * 会导出重名列,导致应用层(如Java的ResultSet.getString("id"))产生歧义或直接报错。
      • USING 的优势:JOIN ... USING(id) 通过合并连接列,避免了 SELECT * 时的列名重复问题,输出结果更干净。这是一个微小的优势,但远不足以抵消 ON 在明确性和灵活性上的巨大好处。最佳实践永远是:使用 ON 进行连接,并明确指定 SELECT 的列清单。
  3. 追问:在 LEFT JOIN 中,把过滤条件放在 ON 子句和 WHERE 子句有什么本质区别?

    • 回答要点:这是一个非常经典且重要的区分点,能体现你对 OUTER JOIN 的理解深度。
      • 条件在 ON 子句:... LEFT JOIN t2 ON t1.id = t2.id AND t2.status = 'active'。这里的 t2.status = 'active' 是作为连接条件的一部分。它在匹配 t2 表时生效。如果 t2 的某行满足 id 匹配但 status 不匹配,这行t2记录被视为“不存在”,但 t1 的记录依然会保留,t2 对应的列填充为 NULL。
      • 条件在 WHERE 子句:... LEFT JOIN t2 ON t1.id = t2.id WHERE t2.status = 'active'。这里的 WHERE 子句在 LEFT JOIN 完成后对整个结果集进行过滤。对于那些在 t2 中没有匹配项的 t1 行,t2.status 的值是 NULL。WHERE t2.status = 'active' 会将这些 NULL 的行过滤掉,这导致 LEFT JOIN 的“保留左表所有行”的特性失效,实质上等价于一个 INNER JOIN。这是新手极易犯的错误。