【SQL入门之坎】完全攻克JOIN!INNER JOIN与LEFT JOIN的区别是什么?
“有员工花名册,但部门名称在另一个文件里…想把员工名和部门名一起列出来!”
“想一目了然地看到哪个商品属于哪个分类!”
作为一名Web开发者,在与数据库打交道的过程中,几乎必然会遇到的一个课题就是“希望将分散在多个表中的信息整合到一起查看”。无论是客户信息和购买记录,还是商品主数据和库存表,在规范化的数据库中,信息通常会按目的分门别类地存放在不同的表中。
能将这些零散的信息像拼图一样连接起来的,就是SQL的强大武器——“JOIN(连接)”。一旦掌握了JOIN,你用SQL能处理的数据范围将爆炸式增长,从而能够提取出更复杂、更有价值的信息。
本文将聚焦于众多JOIN中尤其常用的INNER JOIN和LEFT JOIN,通过初学者也能一看就懂的图解和即取即用的代码,为你彻底剖析它们各自的职责与使用场景!
准备:用于学习JOIN的数据表
为了清晰地理解JOIN的运作方式,这次我们同样使用“员工(employees)”表和“部门(departments)”表。为了让JOIN之间的区别更易于理解,这次我们特意加入了“尚未分配部门的实习新人”和“还没有任何员工的新设公关部”这两条数据。
请复制下方的SQL语句,在你自己的数据库环境中执行,或者在后文的“SQL执行环境”中尝试。
-- 如果表已存在则删除
DROP TABLE IF EXISTS employees;
DROP TABLE IF EXISTS departments;
-- 创建部门表
CREATE TABLE departments (
id INT PRIMARY KEY,
department_name VARCHAR(50)
);
-- 插入部门数据
INSERT INTO departments (id, department_name) VALUES
(1, '営業部'),
(2, '開発部'),
(3, '人事部'),
(4, '広報部'); -- 尚无员工的部门
-- 创建员工表
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department_id INT -- 允许NULL值
);
-- 插入员工数据
INSERT INTO employees (id, name, department_id) VALUES
(1, '山田 太郎', 1),
(2, '鈴木 花子', 2),
(3, '佐藤 次郎', 1),
(4, '高橋 三郎', 3),
(5, '田中 恵子', 2),
(6, '中村 さくら', NULL); -- 尚未分配部门的新人
准备好了吗?让我们开启连接两张表的旅程吧!
1. INNER JOIN:只连接两个表的共同部分
INNER JOIN是最基础、最常用的JOIN。一言以蔽之,它是一种用于“只提取在两个表中都存在且相关联的数据”的连接方法。
如果用文氏图来表示,就像是只取两个圆重叠的“交集”部分。换句话说,它适用于“只想获取已分配部门的员工信息”这类场景。
基本用法
在FROM子句中指定第一张表,用INNER JOIN指定第二张表,然后通过ON子句定义用哪个列作为键(标记)来关联两个表。
SELECT
e.name,
d.department_name
FROM
employees AS e
INNER JOIN
departments AS d ON e.department_id = d.id;
请看这个结果,有两个关键点:
- 尚未分配部门的“中村 さくら”没有出现在结果中。
- 没有任何员工的“広報部”(公关部)也没有出现在结果中。
这就是INNER JOIN最大的特点。只有满足连接条件ON e.department_id = d.id的数据,也就是在两个表中都存在的数据,才会被返回。
※ 像employees AS e这样,在表名后使用AS定义别名,可以使查询语句更简短,非常方便哦。
2. LEFT JOIN:显示左表的所有记录
LEFT JOIN是一种“显示左表(FROM子句中指定的第一个表)的全部数据,并附加上右表中与之关联的数据”的连接方法。它也被称为LEFT OUTER JOIN,但OUTER通常可以省略。
在文氏图中,这就像是包含了左边的整个圆,以及右边圆的重叠部分。如果右边的表没有对应的数据,那么这部分将显示为NULL(空值)。
示例1:以“员工”为轴心显示所有数据
我们来考虑一个场景:“我想要所有员工的列表。如果他们有所属部门,也想知道部门名称。” 在这种情况下,我们将作为轴心的“员工(employees)”表放在左边。
SELECT
e.name,
d.department_name
FROM
employees AS e
LEFT JOIN
departments AS d ON e.department_id = d.id;
请注意看结果!和INNER JOIN不同,尚未分配部门的“中村 さくら”这次出现在了结果中。并且,她的department_name显示为NULL。
这就是LEFT JOIN的威力。由于左表employees的所有数据都会被显示,所以它非常适合于“查找尚未关联到部门的员工”这类用途。
示例2:以“部门”为轴心显示所有数据
那么,如果交换左右两个表的位置会怎么样呢?我们来看看这个场景:“我想看所有部门的列表。如果部门里有员工,也想知道他们的名字。”这次我们把“部门(departments)”表放在左边。
SELECT
d.department_name,
e.name
FROM
departments AS d
LEFT JOIN
employees AS e ON d.id = e.department_id;
这次的结果又不一样了!没有任何员工的“広報部”(公关部)被正确地显示了出来,而对应的员工姓名(name)则为NULL。可见,在使用LEFT JOIN时,将哪个表放在“左边”是至关重要的。
INNER JOIN vs LEFT JOIN 的使用场景区分
如果你在纠结到底该用哪个,不妨想一想你想让什么数据当“主角”。
-
INNER JOIN:“我只想看那些同时具有员工和部门完整信息的数据!”
→ 适用于希望严格查看两个表关联性的情况。 -
LEFT JOIN:“我想看所有员工。部门信息有没有都行!”
→ 适用于希望以一个表为轴心(主表),显示其所有记录的情况。
按照这个标准来思考,你自然就能判断出哪种JOIN更合适了。
实践!在浏览器中试试JOIN吧
久等了!这里是一个能让你亲手实践所学INNER JOIN和LEFT JOIN的环境。
请把下面的代码完整地复制下来,保存成一个如join_practice.html这样的文件,然后在浏览器中打开它。试试交换左右表的位置,或者切换INNER和LEFT,亲身体验一下结果会如何变化吧!
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SQL JOIN练习场</title>
<script src="https://cdn.jsdelivr.net/npm/alasql@4"></script>
<style>
body { font-family: sans-serif; padding: 2rem; background-color: #f9f9f9; }
.container { max-width: 800px; margin: auto; background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
h1 { color: #333; }
textarea { width: 100%; height: 180px; font-family: monospace; font-size: 16px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; margin-bottom: 1rem; }
button { background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background-color: #0056b3; }
#result-area { margin-top: 2rem; }
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.error { color: red; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>来试试JOIN吧!</h1>
<p>在下方的文本框中输入SQL语句,然后点击“执行SQL”按钮。</p>
<textarea id="sql-input">-- 在这里写下你的SQL
SELECT
e.name,
d.department_name
FROM
employees AS e
LEFT JOIN
departments AS d ON e.department_id = d.id;</textarea>
<button onclick="executeSQL()">执行SQL</button>
<div id="result-area"></div>
</div>
<script>
// 初始化数据库并准备数据
const db = new alasql.Database();
db.exec(`
CREATE TABLE departments (id INT, department_name STRING);
INSERT INTO departments VALUES (1, '営業部'), (2, '開発部'), (3, '人事部'), (4, '広報部');
CREATE TABLE employees (id INT, name STRING, department_id INT);
INSERT INTO employees VALUES (1, '山田 太郎', 1), (2, '鈴木 花子', 2), (3, '佐藤 次郎', 1), (4, '高橋 三郎', 3), (5, '田中 恵子', 2), (6, '中村 さくら', NULL);
`);
function executeSQL() {
const sql = document.getElementById('sql-input').value;
const resultArea = document.getElementById('result-area');
resultArea.innerHTML = '';
try {
const result = db.exec(sql);
if (result.length > 0) {
resultArea.appendChild(createTable(result));
} else {
resultArea.innerHTML = '<p>查询结果为0条。</p>';
}
} catch (e) {
resultArea.innerHTML = `<p class="error">错误: ${e.message}</p>`;
}
}
function createTable(data) {
const table = document.createElement('table');
const thead = table.createTHead();
const tbody = table.createTBody();
const headerRow = thead.insertRow();
for (const key in data[0]) {
const th = document.createElement('th');
th.textContent = key;
headerRow.appendChild(th);
}
data.forEach(rowData => {
const row = tbody.insertRow();
for (const key in rowData) {
const cell = row.insertCell();
cell.textContent = (rowData[key] === null || rowData[key] === undefined) ? 'NULL' : rowData[key];
}
});
return table;
}
// 初始显示
executeSQL();
</script>
</body>
</html>
注意事项与相关技巧
ON与WHERE的区别
初学者容易混淆ON和WHERE的用法。简单记一下就好:
- ON: 定义表与表之间“用哪个键来连接”的连接规则。
- WHERE: 定义从连接后形成的大表中“筛选哪些行”的过滤规则。
请记住这个流程:首先用ON正确地连接表,然后用WHERE筛选出需要的数据。
RIGHT JOIN与FULL OUTER JOIN
JOIN还有其他一些小伙伴。
- RIGHT JOIN: 这是
LEFT JOIN的反向版本。它以右边的表为轴心显示所有记录。 - FULL OUTER JOIN: 显示左右两个表的所有数据,在没有对应数据的地方填入
NULL。
在实际工作中,LEFT JOIN的使用频率是压倒性的,但知道还有这些小伙伴的存在,把它们记在脑海一角也是件好事。
总结
辛苦了!这次我们解说了表连接的基础——INNER JOIN和LEFT JOIN的区别与用法。
- INNER JOIN: 当你只想获取两个表的交集部分时使用。不相关的记录会从结果中排除。
- LEFT JOIN: 当你想以一个表为主角,显示其所有数据时使用。缺失的关联数据会用
NULL来补充。 - 如何选择使用哪种JOIN,取决于“你想让什么数据当主角”!
JOIN是发挥关系型数据库真正价值的核心功能。一开始可能会觉得有点难,但越用就会越发现它的便利,提取数据的过程也会变得越来越有趣。请务必多尝试各种模式,把JOIN变成你的看家本领吧!