在 MySQL 乃至所有关系型数据库的学习与实际开发中,单表操作只是基础,多表查询才是真正的核心与灵魂。无论是电商系统、后台管理、数据分析,几乎 90% 以上的业务需求,都离不开多张表的关联操作。

很多初学者学到多表时都会陷入混乱:表关系怎么分析?外键有什么用?内连接、左连接、右连接到底什么时候用?子查询又该怎么写?

这篇文章就带你从零到一,彻底吃透 MySQL 多表查询,全程附带可直接复制运行的实战案例,图文并茂,学完就能直接上手项目。


一、关系型数据库核心:表与表之间的关系

在设计数据库时,我们绝对不能把所有数据都塞进一张表里,那样会造成大量冗余、混乱、难以维护。正确的做法是:按业务拆分表,再通过关系将表关联起来

常见的表关系有三种:

  1. 一对一例如:人 ↔ 身份证、用户 ↔ 用户详情特点:一张表的一条数据,只对应另一张表的一条数据。
  2. 一对多(最常用、最重要)例如:分类 ↔ 商品、部门 ↔ 员工、武功 ↔ 英雄特点:一方的数据,可以被多方引用。
  3. 多对多例如:学生 ↔ 课程、用户 ↔ 角色特点:需要借助中间表来维护关系。

一对多建表黄金原则

  • “一” 的一方:主表,必须设置主键
  • “多” 的一方:从表,新增一列作为外键列,用来关联主表的主键。

一句话总结:在多的一方加一列,指向一的一方的主键,关系就建立了。


二、外键约束(Foreign Key):保证数据安全与一致性

外键是用来强制两张表之间的引用完整性的约束。

外键的作用

  • 防止从表插入不存在的主表 ID
  • 防止主表数据被从表引用时强行删除
  • 保证数据一致性,避免脏数据

外键语法格式

CONSTRAINT 外键名称 FOREIGN KEY (从表外键字段) REFERENCES 主表(主键字段)

外键就像一个 “严格的校验器”,让多表数据不敢乱填。


三、实战案例一:英雄 & 武功表 —— 彻底搞懂连接查询

为了让大家直观理解,我们用武侠场景来学习:

  • 一个英雄会一种武功
  • 一种武功可以被多个英雄使用这是标准的一对多关系。

1. 创建数据库与表

-- 切换数据库
use day02;

-- 武功表(主表,一的一方)
create table kongfu
(
    kid int primary key,  -- 武功ID,主键
    kname varchar(255)    -- 武功名称
);

-- 英雄表(从表,多的一方)
create table hero
(
    hid int primary key,      -- 英雄ID
    hname varchar(255),      -- 英雄名称
    kongfu_id int            -- 外键列,关联武功表kid
);

2. 插入测试数据

-- 插入武功
insert into kongfu values
(1, '降龙十八掌'),
(2, '乾坤大挪移'),
(3, '猴子偷桃'),
(4, '天山折梅手');

-- 插入英雄
insert into hero values
(1, '鸠摩智', 9),   -- 没有对应武功
(3, '乔峰', 1),     -- 降龙十八掌
(4, '虚竹', 4),     -- 天山折梅手
(5, '段誉', 12);    -- 没有对应武功

3. 多表查询核心:三种连接一网打尽

(1)交叉连接(笛卡尔积)

千万不要在生产环境随便用!它的结果是:A 表总条数 × B 表总条数,数据会爆炸式增长。

-- 交叉连接
select * from hero,kongfu;

作用:仅用于理解连接原理,实际开发几乎不用。

(2)内连接(INNER JOIN):取两张表的交集

内连接只查询两边表能完全匹配上的数据

  • 显示内连接(推荐写法)
select * from hero h
inner join kongfu kf
on h.kongfu_id = kf.kid;
  • 隐式内连接(老式写法)
select * from hero h ,kongfu kf
where h.kongfu_id = kf.kid;

执行结果:只显示乔峰、虚竹,因为他们的武功 ID 在武功表中存在。鸠摩智、段誉匹配不到,不会出现在结果中

适用场景:只需要查询双方都存在关联的数据


(3)外连接:保留某一张表的全部数据

外连接的核心是:以某一张表为基准,全部显示

左外连接(LEFT JOIN)

以左边的表为基准,左表数据全部显示,右表能匹配就显示,匹配不上显示 NULL。

select * from hero h
left join kongfu kf
on h.kongfu_id = kf.kid;

结果:四个英雄全部显示,鸠摩智、段誉没有武功,显示 NULL。

右外连接(RIGHT JOIN)

以右边的表为基准,右表数据全部显示,左表匹配不上显示 NULL。

select * from hero h
right join kongfu kf
on h.kongfu_id = kf.kid;

结果:所有武功都显示,没有英雄使用的武功,对应的英雄字段为 NULL。


四、实战案例二:商品 & 分类 —— 外键 + 子查询实战

接下来我们用电商场景,学习更贴近企业开发的:外键约束 + 子查询

1. 创建表并添加外键

-- 分类表(一)
create table category (
     cid varchar(32) primary key ,
     cname varchar(50)
);

-- 商品表(多)
create table products(
     pid varchar(32) primary key ,
     pname varchar(50),
     price int,
     flag varchar(2), -- 1上架 0下架
     category_id varchar(32),
     -- 添加外键
     constraint products_fk
     foreign key (category_id) references category (cid)
);

2. 插入测试数据

-- 分类数据
INSERT INTO category(cid,cname) VALUES
('c001','家电'),
('c002','服饰'),
('c003','化妆品'),
('c004','奢侈品');

-- 商品数据
INSERT INTO products(pid, pname,price,flag,category_id) VALUES
('p001','联想',5000,'1','c001'),
('p002','海尔',3000,'1','c001'),
('p003','雷神',5000,'1','c001'),
('p004','JACK JONES',800,'1','c002'),
('p005','真维斯',200,'1','c002'),
('p006','花花公子',440,'1','c002'),
('p007','劲霸',2000,'1','c002'),
('p008','香奈儿',800,'1','c003'),
('p009','相宜本草',200,'1','c003');

五、子查询:嵌套查询,SQL 进阶必备

子查询的本质:把一个 SQL 的查询结果,当作另一个 SQL 的条件或表来使用。

需求 1:查询哪些分类下有上架的商品

分析:

  1. 先从商品表中,找出所有上架商品的分类 ID
  2. 再去分类表中查询这些分类
-- 子查询实现
select * from category
where cid in (
    select distinct category_id
    from products
    where flag = '1'
);

需求 2:统计每个分类下的商品数量(包含 0 个商品的分类)

这里需要用到 左连接 + 分组统计

select
    cname,
    count(category_id) as total_cnt
from category c
left join products p
on c.cid = p.category_id
group by cname;

结果会显示:

  • 家电:3 个
  • 服饰:4 个
  • 化妆品:2 个
  • 奢侈品:0 个

非常贴合真实业务需求。


六、一张图彻底理解多表连接(必看总结)

为了方便记忆,你可以直接记住下面这个核心规律:

  1. 内连接(JOIN)只显示两边都能匹配的数据。
  2. 左连接(LEFT JOIN)左表全要,右表匹配不上填 NULL。
  3. 右连接(RIGHT JOIN)右表全要,左表匹配不上填 NULL。
  4. 子查询一个查询结果当作条件给另一个查询使用。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐