🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

【SQL入门之坎】完全攻克JOIN!INNER JOIN与LEFT JOIN的区别是什么?

“有员工花名册,但部门名称在另一个文件里…想把员工名和部门名一起列出来!”
“想一目了然地看到哪个商品属于哪个分类!”

作为一名Web开发者,在与数据库打交道的过程中,几乎必然会遇到的一个课题就是“希望将分散在多个表中的信息整合到一起查看”。无论是客户信息和购买记录,还是商品主数据和库存表,在规范化的数据库中,信息通常会按目的分门别类地存放在不同的表中。

能将这些零散的信息像拼图一样连接起来的,就是SQL的强大武器——“JOIN(连接)”。一旦掌握了JOIN,你用SQL能处理的数据范围将爆炸式增长,从而能够提取出更复杂、更有价值的信息。

本文将聚焦于众多JOIN中尤其常用的INNER JOINLEFT 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 的使用场景区分

如果你在纠结到底该用哪个,不妨想一想你想让什么数据当“主角”。

按照这个标准来思考,你自然就能判断出哪种JOIN更合适了。


实践!在浏览器中试试JOIN吧

久等了!这里是一个能让你亲手实践所学INNER JOINLEFT JOIN的环境。

请把下面的代码完整地复制下来,保存成一个如join_practice.html这样的文件,然后在浏览器中打开它。试试交换左右表的位置,或者切换INNERLEFT,亲身体验一下结果会如何变化吧!


<!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的区别

初学者容易混淆ONWHERE的用法。简单记一下就好:

请记住这个流程:首先用ON正确地连接表,然后用WHERE筛选出需要的数据。


RIGHT JOIN与FULL OUTER JOIN

JOIN还有其他一些小伙伴。

在实际工作中,LEFT JOIN的使用频率是压倒性的,但知道还有这些小伙伴的存在,把它们记在脑海一角也是件好事。


总结

辛苦了!这次我们解说了表连接的基础——INNER JOINLEFT JOIN的区别与用法。

JOIN是发挥关系型数据库真正价值的核心功能。一开始可能会觉得有点难,但越用就会越发现它的便利,提取数据的过程也会变得越来越有趣。请务必多尝试各种模式,把JOIN变成你的看家本领吧!