Skip to content

多表连接

在实际应用中,数据通常分散在多个表中。SQL 的 JOIN 操作允许我们将多个表的数据组合在一起进行查询。本章将详细介绍各种 JOIN 类型及其使用场景。

为什么需要 JOIN?

数据规范化

为了避免数据冗余,我们通常将数据分散到多个表中:

sql
-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    email VARCHAR(100)
);

-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    total_amount DECIMAL(10, 2),
    order_date DATE,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

查询关联数据

要查询用户及其订单信息,就需要使用 JOIN:

sql
SELECT 
    users.name,
    orders.total_amount,
    orders.order_date
FROM users
JOIN orders ON users.id = orders.user_id;

准备示例数据

创建表

sql
-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    city VARCHAR(50)
);

-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    product VARCHAR(50),
    amount DECIMAL(10, 2)
);

插入数据

sql
-- 插入用户数据
INSERT INTO users VALUES
(1, '张三', '北京'),
(2, '李四', '上海'),
(3, '王五', '广州'),
(4, '赵六', '深圳');

-- 插入订单数据
INSERT INTO orders VALUES
(101, 1, '笔记本电脑', 5000),
(102, 1, '鼠标', 50),
(103, 2, '键盘', 200),
(104, 3, '显示器', 1500);

数据概览:

users 表:

idnamecity
1张三北京
2李四上海
3王五广州
4赵六深圳

orders 表:

iduser_idproductamount
1011笔记本电脑5000
1021鼠标50
1032键盘200
1043显示器1500

INNER JOIN(内连接)

基本语法

sql
SELECT columns
FROM table1
INNER JOIN table2 ON table1.column = table2.column;

示例

sql
SELECT 
    users.name,
    users.city,
    orders.product,
    orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;

结果:

namecityproductamount
张三北京笔记本电脑5000
张三北京鼠标50
李四上海键盘200
王五广州显示器1500

注意:赵六没有订单,所以不在结果中。

简写形式

INNER 关键字可以省略:

sql
SELECT users.name, orders.product
FROM users
JOIN orders ON users.id = orders.user_id;

使用表别名

sql
SELECT 
    u.name,
    o.product,
    o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;

LEFT JOIN(左连接)

基本语法

sql
SELECT columns
FROM table1
LEFT JOIN table2 ON table1.column = table2.column;

示例

sql
SELECT 
    users.name,
    users.city,
    orders.product,
    orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id;

结果:

namecityproductamount
张三北京笔记本电脑5000
张三北京鼠标50
李四上海键盘200
王五广州显示器1500
赵六深圳NULLNULL

注意:赵六没有订单,但仍然出现在结果中,订单信息为 NULL。

查找没有订单的用户

sql
SELECT 
    users.name,
    users.city
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.id IS NULL;

结果:

namecity
赵六深圳

RIGHT JOIN(右连接)

基本语法

sql
SELECT columns
FROM table1
RIGHT JOIN table2 ON table1.column = table2.column;

示例

sql
SELECT 
    users.name,
    orders.product,
    orders.amount
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;

说明:RIGHT JOIN 返回右表(orders)的所有记录,即使左表(users)中没有匹配的记录。

注意:在实际应用中,LEFT JOIN 更常用,RIGHT JOIN 可以通过调换表的位置用 LEFT JOIN 实现。

FULL OUTER JOIN(全外连接)

基本语法

sql
SELECT columns
FROM table1
FULL OUTER JOIN table2 ON table1.column = table2.column;

示例

sql
SELECT 
    users.name,
    orders.product
FROM users
FULL OUTER JOIN orders ON users.id = orders.user_id;

说明:返回两个表中的所有记录,没有匹配的地方用 NULL 填充。

注意:MySQL 不支持 FULL OUTER JOIN,但可以用 UNION 实现:

sql
SELECT users.name, orders.product
FROM users
LEFT JOIN orders ON users.id = orders.user_id
UNION
SELECT users.name, orders.product
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;

CROSS JOIN(交叉连接)

基本语法

sql
SELECT columns
FROM table1
CROSS JOIN table2;

示例

sql
SELECT 
    users.name,
    orders.product
FROM users
CROSS JOIN orders;

说明:返回两个表的笛卡尔积,即每个用户与每个订单的组合。

结果行数:4 个用户 × 4 个订单 = 16 行

使用场景:生成所有可能的组合,如排班表、测试数据等。

SELF JOIN(自连接)

概念

表与自身进行连接,通常用于处理层级关系。

示例:员工和经理

sql
-- 员工表
CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    manager_id INT
);

INSERT INTO employees VALUES
(1, '张总', NULL),
(2, '李经理', 1),
(3, '王主管', 2),
(4, '赵员工', 2);

查询员工及其经理:

sql
SELECT 
    e.name AS 员工,
    m.name AS 经理
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

结果:

员工经理
张总NULL
李经理张总
王主管李经理
赵员工李经理

多表连接

连接三个表

sql
-- 添加产品表
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    category VARCHAR(50)
);

-- 查询用户、订单和产品信息
SELECT 
    u.name AS 用户,
    o.amount AS 金额,
    p.name AS 产品,
    p.category AS 分类
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id;

连接顺序

sql
-- 先连接 users 和 orders,再连接 products
SELECT *
FROM users
JOIN orders ON users.id = orders.user_id
JOIN products ON orders.product_id = products.id;

连接条件

等值连接

sql
SELECT *
FROM users
JOIN orders ON users.id = orders.user_id;

非等值连接

sql
-- 查询价格在某个范围内的订单
SELECT *
FROM orders o
JOIN price_ranges pr ON o.amount BETWEEN pr.min_price AND pr.max_price;

多条件连接

sql
SELECT *
FROM users u
JOIN orders o ON u.id = o.user_id 
    AND o.order_date >= '2024-01-01';

使用 WHERE 过滤

在 JOIN 后过滤

sql
SELECT 
    u.name,
    o.product,
    o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;

JOIN 条件 vs WHERE 条件

sql
-- 在 JOIN 条件中过滤(推荐)
SELECT *
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.amount > 100;

-- 在 WHERE 中过滤
SELECT *
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;

区别

  • JOIN 条件中的过滤不影响 LEFT JOIN 返回左表的所有记录
  • WHERE 中的过滤会影响最终结果

聚合与 JOIN

统计每个用户的订单数

sql
SELECT 
    u.name,
    COUNT(o.id) AS 订单数,
    COALESCE(SUM(o.amount), 0) AS 总金额
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

结果:

name订单数总金额
张三25050
李四1200
王五11500
赵六00

性能优化

1. 使用索引

sql
-- 为连接列创建索引
CREATE INDEX idx_orders_user_id ON orders(user_id);

2. 只选择需要的列

sql
-- 不推荐
SELECT * FROM users JOIN orders ON users.id = orders.user_id;

-- 推荐
SELECT users.name, orders.product 
FROM users JOIN orders ON users.id = orders.user_id;

3. 先过滤再连接

sql
-- 先过滤数据,减少连接的数据量
SELECT u.name, o.product
FROM users u
JOIN (
    SELECT * FROM orders WHERE amount > 1000
) o ON u.id = o.user_id;

4. 使用 EXPLAIN 分析

sql
EXPLAIN SELECT *
FROM users
JOIN orders ON users.id = orders.user_id;

实战示例

示例 1:查询用户订单统计

sql
SELECT 
    u.name AS 用户名,
    u.city AS 城市,
    COUNT(o.id) AS 订单数,
    COALESCE(SUM(o.amount), 0) AS 总消费,
    COALESCE(AVG(o.amount), 0) AS 平均消费
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.city
ORDER BY 总消费 DESC;

示例 2:查询最近订单

sql
SELECT 
    u.name,
    o.product,
    o.amount,
    o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.order_date >= DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY o.order_date DESC;

示例 3:查询高价值客户

sql
SELECT 
    u.name,
    u.email,
    SUM(o.amount) AS 总消费
FROM users u
JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email
HAVING 总消费 > 5000
ORDER BY 总消费 DESC;

常见错误

错误 1:忘记 ON 条件

sql
-- 错误:缺少 ON 条件
SELECT * FROM users JOIN orders;

-- 正确
SELECT * FROM users JOIN orders ON users.id = orders.user_id;

错误 2:列名歧义

sql
-- 错误:id 列在两个表中都存在
SELECT id, name FROM users JOIN orders ON users.id = orders.user_id;

-- 正确:使用表名或别名限定列名
SELECT users.id, users.name FROM users JOIN orders ON users.id = orders.user_id;

错误 3:混淆 LEFT JOIN 和 INNER JOIN

sql
-- 如果想要所有用户(包括没有订单的),使用 LEFT JOIN
SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id;

-- 如果只想要有订单的用户,使用 INNER JOIN
SELECT * FROM users JOIN orders ON users.id = orders.user_id;

JOIN 类型对比

JOIN 类型说明使用场景
INNER JOIN返回两表匹配的记录查询有关联的数据
LEFT JOIN返回左表所有记录查询主表及其关联数据
RIGHT JOIN返回右表所有记录较少使用,可用 LEFT JOIN 替代
FULL OUTER JOIN返回两表所有记录查询所有数据,包括未匹配的
CROSS JOIN返回笛卡尔积生成所有可能的组合
SELF JOIN表与自身连接处理层级关系

小结

本章介绍了 SQL 中的多表连接:

  • INNER JOIN:返回两表匹配的记录
  • LEFT JOIN:返回左表所有记录,右表没有匹配时返回 NULL
  • RIGHT JOIN:返回右表所有记录,左表没有匹配时返回 NULL
  • FULL OUTER JOIN:返回两表所有记录
  • CROSS JOIN:返回笛卡尔积
  • SELF JOIN:表与自身连接
  • 多表连接:可以连接多个表
  • 性能优化:使用索引、只选择需要的列、先过滤再连接

掌握 JOIN 操作是 SQL 学习的重要里程碑,它让你能够处理复杂的多表查询。

下一步: 学习 子查询,学习如何在查询中嵌套查询。