一文带你掌握MySQL中的连表查询
作者:盛夏绽放
一、准备工作:创建真实业务表并插入测试数据
我们以电商场景的用户表(user)、订单表(order)、 商品表(product) 为例,先创建表并插入测试数据(注意:order是 MySQL 关键字,需用反引号``包裹)。
1. 创建表结构
-- 1. 用户表(存储用户基本信息)
CREATE TABLE `user` (
user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID(主键)',
user_name VARCHAR(50) NOT NULL COMMENT '用户名',
user_phone VARCHAR(20) COMMENT '用户手机号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2. 商品表(存储商品信息)
CREATE TABLE `product` (
product_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID(主键)',
product_name VARCHAR(100) NOT NULL COMMENT '商品名称',
price DECIMAL(10,2) NOT NULL COMMENT '商品价格',
stock INT DEFAULT 0 COMMENT '商品库存'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3. 订单表(存储用户订单,关联用户表和商品表)
CREATE TABLE `order` (
order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID(主键)',
user_id INT NOT NULL COMMENT '用户ID(关联user表的user_id)',
product_id INT NOT NULL COMMENT '商品ID(关联product表的product_id)',
order_amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
order_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
-- 外键约束(可选,用于强制数据完整性)
FOREIGN KEY (user_id) REFERENCES `user`(user_id),
FOREIGN KEY (product_id) REFERENCES `product`(product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 插入测试数据
-- 插入用户数据(3个用户:张三、李四、王五,其中王五暂无订单)
INSERT INTO `user` (user_name, user_phone) VALUES
('张三', '13800138000'),
('李四', '13900139000'),
('王五', '13700137000');
-- 插入商品数据(3个商品:手机、电脑、耳机)
INSERT INTO `product` (product_name, price, stock) VALUES
('华为Mate60', 6999.00, 100),
('苹果MacBook Pro', 12999.00, 50),
('索尼WH-1000XM5', 2499.00, 200);
-- 插入订单数据(4个订单:张三买了手机和耳机,李四买了电脑,无王五的订单,且有一个订单关联的商品ID为4(不存在的商品))
INSERT INTO `order` (user_id, product_id, order_amount) VALUES
(1, 1, 6999.00), -- 张三买华为Mate60
(1, 3, 2499.00), -- 张三买索尼耳机
(2, 2, 12999.00), -- 李四买苹果电脑
(2, 4, 0.00); -- 李四买了一个不存在的商品(product_id=4)
3. 表数据预览
| 表名 | 数据内容 |
|---|---|
| user | user_id:1(张三)、2(李四)、3(王五) |
| product | product_id:1(华为 Mate60)、2(苹果 MacBook Pro)、3(索尼耳机) |
| order | order_id:1(1-1-6999)、2(1-3-2499)、3(2-2-12999)、4(2-4-0) |
二、各类连表查询详解(附语法、结果、作用)
1. 交叉连接(CROSS JOIN):笛卡尔积连接
作用
返回两个表的笛卡尔积(表 A 的每一行与表 B 的每一行组合),无任何条件匹配,实际业务中极少直接使用,通常需配合WHERE过滤。
语法
-- 显式交叉连接(用户表 × 商品表) SELECT u.user_name, p.product_name FROM `user` u CROSS JOIN `product` p;
执行结果(共 3×3=9 行)
| user_name | product_name |
|---|---|
| 张三 | 华为 Mate60 |
| 张三 | 苹果 MacBook Pro |
| 张三 | 索尼 WH-1000XM5 |
| 李四 | 华为 Mate60 |
| 李四 | 苹果 MacBook Pro |
| 李四 | 索尼 WH-1000XM5 |
| 王五 | 华为 Mate60 |
| 王五 | 苹果 MacBook Pro |
| 王五 | 索尼 WH-1000XM5 |
特点
- 结果行数 = 表 A 行数 × 表 B 行数;
- 无业务意义,仅用于测试或特殊数据生成。
2. 内连接(INNER JOIN):匹配连接(最常用)
作用
只返回两个 / 多个表中满足关联条件的行(即交集),是业务中使用频率最高的连表查询。
语法(查询用户的订单及对应商品信息)
-- 显式内连接(推荐,可读性高)
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `user` u
INNER JOIN `order` o ON u.user_id = o.user_id
INNER JOIN `product` p ON o.product_id = p.product_id;
-- 隐式内连接(省略INNER JOIN,用WHERE指定条件,功能一致)
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `user` u, `order` o, `product` p
WHERE u.user_id = o.user_id AND o.product_id = p.product_id;
执行结果(仅保留匹配的行,共 3 行)
| user_name | order_id | product_name | order_amount |
|---|---|---|---|
| 张三 | 1 | 华为 Mate60 | 6999.00 |
| 张三 | 2 | 索尼 WH-1000XM5 | 2499.00 |
| 李四 | 3 | 苹果 MacBook Pro | 12999.00 |
关键说明
- 过滤掉了:王五(无订单)、李四的 order_id=4(product_id=4 无匹配商品);
- 可关联多个表(上述示例关联了 3 个表);
- 显式内连接更符合 SQL 标准,推荐使用。
3. 外连接(OUTER JOIN):保留单侧 / 双侧未匹配行
外连接分为左外连接、右外连接、全外连接(MySQL 不直接支持全外连接,需用 UNION 实现),核心是保留一侧 / 双侧表的所有行,无匹配时显示NULL。
(1)左外连接(LEFT JOIN / LEFT OUTER JOIN)
作用:保留左表(JOIN左侧的表)的所有行,右表仅匹配满足条件的行;若右表无匹配,右表字段显示NULL。
语法(查询所有用户的订单,包括无订单的用户)
-- 左外连接:保留用户表的所有行
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `user` u
LEFT JOIN `order` o ON u.user_id = o.user_id
LEFT JOIN `product` p ON o.product_id = p.product_id;
执行结果(共 5 行,包含王五和李四的无效订单)
| user_name | order_id | product_name | order_amount |
|---|---|---|---|
| 张三 | 1 | 华为 Mate60 | 6999.00 |
| 张三 | 2 | 索尼 WH-1000XM5 | 2499.00 |
| 李四 | 3 | 苹果 MacBook Pro | 12999.00 |
| 李四 | 4 | NULL | 0.00 |
| 王五 | NULL | NULL | NULL |
关键说明
- 保留了左表(
user)的所有用户:王五(无订单,字段为 NULL); - 保留了李四的 order_id=4(product_id=4 无匹配商品,product_name 为 NULL)。
在这段左外连接 SQL 中,第一个LEFT JOIN的左表是FROM后的user表(别名u),而后续的LEFT JOIN的左表是前一个连接的结果集(即user和order连接后的临时表)。
分步拆解说明
我们把 SQL 拆成两步,你会更清晰:
第一步:user LEFT JOIN order
-- 左表:user(u),右表:order(o) SELECT u.user_name, o.order_id FROM `user` u LEFT JOIN `order` o ON u.user_id = o.user_id;
- 左表:
user(FROM后的主表),保留其所有行; - 右表:
order,仅匹配满足u.user_id = o.user_id的行,无匹配则显示NULL。
第二步:再 LEFT JOIN product
-- 左表:第一步得到的临时表(user+order),右表:product(p) SELECT ... FROM (user u LEFT JOIN order o ON ...) -- 这是新的左表 LEFT JOIN `product` p ON o.product_id = p.product_id;
- 左表:前一次连接的结果集(
user和order连接后的临时表),保留其所有行; - 右表:
product,仅匹配满足o.product_id = p.product_id的行,无匹配则显示NULL。
核心规律(通用)
在 MySQL 的多表连接中:
LEFT JOIN的左表 =LEFT JOIN关键字左侧的表 / 结果集;LEFT JOIN的右表 =LEFT JOIN关键字右侧的表 / 结果集;- 多表连续
LEFT JOIN时,左表会依次继承前一次连接的结果,这也是为什么你的 SQL 能保留user表所有行的原因(因为每一步都是左连接,最终不会过滤掉user的行)。
比如你的 SQL 中,即使后续连接product,也不会丢失user表的行(比如王五无订单的行、李四的无效订单行都会保留)。
(2)右外连接(RIGHT JOIN / RIGHT OUTER JOIN)
作用:保留右表(JOIN右侧的表)的所有行,左表仅匹配满足条件的行;若左表无匹配,左表字段显示NULL。
语法(查询所有商品的订单,包括无订单的商品)
-- 右外连接:保留商品表的所有行
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `user` u
INNER JOIN `order` o ON u.user_id = o.user_id
RIGHT JOIN `product` p ON o.product_id = p.product_id;
执行结果(共 3 行,无订单的商品显示 NULL)
| user_name | order_id | product_name | order_amount |
|---|---|---|---|
| 张三 | 1 | 华为 Mate60 | 6999.00 |
| 李四 | 3 | 苹果 MacBook Pro | 12999.00 |
| 张三 | 2 | 索尼 WH-1000XM5 | 2499.00 |
扩展:若要保留商品表全量(包括无订单的商品,需调整连接顺序)
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `product` p
RIGHT JOIN `order` o ON p.product_id = o.product_id
LEFT JOIN `user` u ON o.user_id = u.user_id;
-- 结果会包含product_id=4的无效订单,以及所有商品(若有商品无订单则显示NULL)
(3)全外连接(FULL JOIN / FULL OUTER JOIN)
作用:保留左表和右表的所有行,无匹配的一侧字段显示NULL(即左外连接 + 右外连接的并集)。
注意:MySQL 不直接支持 FULL JOIN,需通过LEFT JOIN UNION RIGHT JOIN实现
语法(查询所有用户和所有商品的订单,无匹配则显示 NULL)
-- 左外连接部分:所有用户 + 匹配的订单/商品
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `user` u
LEFT JOIN `order` o ON u.user_id = o.user_id
LEFT JOIN `product` p ON o.product_id = p.product_id
UNION -- UNION去重,UNION ALL保留重复行
-- 右外连接部分:所有商品 + 匹配的订单/用户
SELECT
u.user_name,
o.order_id,
p.product_name,
o.order_amount
FROM `user` u
RIGHT JOIN `order` o ON u.user_id = o.user_id
RIGHT JOIN `product` p ON o.product_id = p.product_id;
执行结果(包含所有用户、所有商品、所有订单,无匹配则 NULL)
| user_name | order_id | product_name | order_amount |
|---|---|---|---|
| 张三 | 1 | 华为 Mate60 | 6999.00 |
| 张三 | 2 | 索尼 WH-1000XM5 | 2499.00 |
| 李四 | 3 | 苹果 MacBook Pro | 12999.00 |
| 李四 | 4 | NULL | 0.00 |
| 王五 | NULL | NULL | NULL |
4. 自然连接(NATURAL JOIN):自动匹配同名字段
作用
自动根据两个表中名称相同的字段进行连接(无需手动写ON条件),分为自然内连接、自然左 / 右外连接。
语法(自动匹配user_id字段,查询用户和订单)
-- 自然内连接(自动匹配user_id字段)
SELECT
user_name,
order_id,
order_amount
FROM `user` u
NATURAL JOIN `order` o;
执行结果(等同于 INNER JOIN ON u.user_id = o.user_id,共 4 行)
| user_name | order_id | order_amount |
|---|---|---|
| 张三 | 1 | 6999.00 |
| 张三 | 2 | 2499.00 |
| 李四 | 3 | 12999.00 |
| 李四 | 4 | 0.00 |
特点
- 自动匹配同名字段,无需写
ON条件; - 风险高:若表中有多个同名字段(如
create_time),会同时匹配所有同名字段,导致非预期结果; - 生产环境极少使用,推荐显式指定
ON条件。
5. 自连接(SELF JOIN):表自身连接
作用
将表与自身进行连接(把一个表当作两个表使用),用于查询表内的层级关系或关联数据(如员工与上级、分类与子分类)。
扩展:新增员工表并演示自连接
-- 创建员工表(包含员工ID和上级ID,上级ID关联自身的员工ID)
CREATE TABLE `employee` (
emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
emp_name VARCHAR(50) NOT NULL COMMENT '员工姓名',
manager_id INT COMMENT '上级ID(关联emp_id)',
department VARCHAR(50) COMMENT '部门'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO `employee` (emp_name, manager_id, department) VALUES
('马云', NULL, '董事会'), -- 马云无上级
('张勇', 1, 'CEO办公室'), -- 张勇的上级是马云
('王坚', 2, '阿里云'), -- 王坚的上级是张勇
('蒋芳', 2, '廉政部'); -- 蒋芳的上级是张勇
-- 自连接查询:每个员工的姓名及对应的上级姓名
SELECT
e.emp_name AS 员工姓名,
m.emp_name AS 上级姓名,
e.department AS 部门
FROM `employee` e
LEFT JOIN `employee` m ON e.manager_id = m.emp_id;
执行结果
| 员工姓名 | 上级姓名 | 部门 |
|---|---|---|
| 马云 | NULL | 董事会 |
| 张勇 | 马云 | CEO 办公室 |
| 王坚 | 张勇 | 阿里云 |
| 蒋芳 | 张勇 | 廉政部 |
特点
- 本质是内连接 / 外连接的特殊形式(表自身关联);
- 必须为表取不同的别名(如
e代表员工,m代表上级); - 常用于处理层级数据(如组织架构、评论回复)。
三、各类连表查询的核心差异对比表
| 连接类型 | 核心逻辑 | 结果集特点 | 匹配条件指定方式 | 常用场景 |
|---|---|---|---|---|
| 交叉连接(CROSS) | 笛卡尔积,无匹配 | 行数 = 表 A× 表 B,无意义组合多 | 无(或 WHERE 过滤) | 测试数据、特殊数据生成 |
| 内连接(INNER) | 仅匹配满足条件的行(交集) | 只保留匹配行,无 NULL | ON/WHERE | 业务核心查询(如用户 + 订单) |
| 左外连接(LEFT) | 保留左表所有行,右表匹配 | 左表全量,右表无匹配则 NULL | ON | 查主表全量 + 关联表数据(所有用户 + 订单) |
| 右外连接(RIGHT) | 保留右表所有行,左表匹配 | 右表全量,左表无匹配则 NULL | ON | 查关联表全量 + 主表数据(所有订单 + 用户) |
| 全外连接(FULL) | 保留左右表所有行(并集) | 左右表全量,无匹配则 NULL | ON(MySQL 需 UNION 实现) | 查两个表的所有数据(极少用) |
| 自然连接(NATURAL) | 自动匹配同名字段 | 同内 / 外连接,但依赖字段名 | 自动(无需 ON) | 简单场景(不推荐生产使用) |
| 自连接(SELF) | 表自身关联 | 同内 / 外连接,处理表内层级 | ON(表别名区分) | 层级数据查询(员工 - 上级、分类 - 子分类) |
四、关键注意事项
关联键索引:连表查询的关联键(如user.user_id、order.user_id)需建立索引,否则会触发全表扫描,性能极差;
NULL 值处理:外连接的 NULL 字段需用IFNULL()/COALESCE()处理(如IFNULL(o.order_amount, 0.00)),避免业务逻辑异常;
表别名:多表连接时建议使用表别名(如u代表user,o代表order),简化 SQL 并提高可读性;
优先级:JOIN的执行优先级高于WHERE,因此关联条件写在ON中比WHERE中更高效(尤其是外连接)。
到此这篇关于一文带你掌握MySQL中的连表查询的文章就介绍到这了,更多相关MySQL连表查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
