在 SQL 查询中,联结(JOIN)是处理多表数据关联的核心技术,也是从 “单表查询” 迈向 “多表分析” 的关键。日常业务中,数据往往分散在不同的关系表中(比如用户表、商品表、订单表),只有通过联结才能整合这些分散的数据,实现更复杂的数据分析需求。本文将从 “为什么要用联结” 入手,系统介绍关系表、联结的创建、WHERE 子句的作用,以及最常用的内部联结和多表联结技巧。
关系表是遵循 “数据库规范化” 设计的表结构 —— 将不同维度的数据拆分到多个表中,通过关联字段(比如product_id、user_id)建立表之间的联系,避免数据冗余。
举个电商场景的例子:
商品表(products):存储商品的核心信息(ID、名称、分类 ID、价格),无需重复存储分类名称;
分类表(categories):存储分类的核心信息(ID、名称),一个分类可对应多个商品。
如下图:

这种拆分方式的优势:修改分类名称时,只需改categories表的一条数据,而非修改products表中所有该分类的商品数据,极大降低维护成本。
既然数据分散在多个关系表中,要获取 “商品名称 + 所属分类名” 这类跨表数据,就必须通过联结将多个表 “拼接” 起来。没有联结,我们只能先查商品的category_id,再手动查分类名称,效率极低。就像这样:
-- 查询找到所有商品的分类ID > select DISTINCT category_id from product p category_id| -----------+ 1| 2| 3| 4| 4 row(s) fetched. -- 然后根据分类ID找到分类名称 > select * from product_category pc where pc.category_id in ( select DISTINCT category_id from product p) category_id|name|create_time |update_time | -----------+----+-------------------+-------------------+ 1|手机 |2025-12-31 03:18:08|2025-12-31 03:18:08| 2|冰箱 |2025-12-31 03:18:08|2025-12-31 03:18:08| 3|电脑 |2025-12-31 03:18:08|2025-12-31 03:18:08| 4|风扇 |2025-12-31 03:18:08|2025-12-31 03:18:08| 4 row(s) fetched. -- 最后查询商品信息,使用服务端代码根据分类ID提取分类名称。
简单来说:联结的核心价值是在一次查询中整合多个关系表的数据,实现跨表数据的关联查询。
联结的核心是通过JOIN关键字指定要关联的表,通过ON(或WHERE)子句指定关联条件(即两个表的关联字段匹配规则)。
联结语法如下:
SELECT 字段1, 字段2,... FROM 表1 [表1别名] JOIN 表2 [表2别名] ON 表1.关联字段 = 表2.关联字段; -- 核心:指定关联条件
例如:查找所有商品的商分类名称、商品名称、价格和库存
> select pc.name as `category_name`, p.name as `product_name`, p.price, p.stock from product p join product_category pc on p.category_id=pc.category_id category_name|product_name |price |stock| -------------+-------------+--------+-----+ 手机 |小米14手机 | 4999.00| 1000| 手机 |华为Mate60 Pro | 6999.00| 800| 手机 |苹果iPhone 15 | 7999.00| 1200| 手机 |vivo X100 | 3999.00| 1500| 手机 |OPPO Find X7 | 4499.00| 900| 手机 |荣耀Magic6 | 4299.00| 1100| 手机 |红米K70 | 2499.00| 2000| 手机 |一加12 | 4599.00| 850| 手机 |真我GT Neo5 | 2599.00| 1800| 手机 |三星S24 Ultra | 8999.00| 600| 冰箱 |海尔双开门冰箱 | 3999.00| 200| 冰箱 |美的风冷无霜冰箱 | 2999.00| 150| 电脑 |联想拯救者Y9000P | 8999.00| 300| 电脑 |苹果MacBook Pro|12999.00| 180| 风扇 |格力落地扇 | 199.00| 500| 风扇 |美的循环扇 | 299.00| 400| 16 row(s) fetched.
上述 SQL 语句中,“on p.category_id=pc.category_id” 表示使用商品表(product)的 category_id 字段和商品分类表(product_category)的 categroy_id 字段进行关联,即两张表 categroy_id 字段相等,才关联成功。不相等,关联失败,不检索出来。
早期 SQL 中,联结的关联条件常写在WHERE子句中(比如WHERE p.category_id = c.category_id),虽然现代 SQL 推荐用ON子句明确关联条件,但WHERE子句在联结中仍有两个核心作用:
过滤关联后的结果集:在表联结完成后,筛选符合条件的数据(比如只保留价格 > 5000 的商品)。
兼容旧版语法:部分老数据库或简化写法中,仍用WHERE替代ON指定关联条件(但可读性差,不推荐)。
示例:查询价格 > 5000 的商品信息,含分类名称、商品名称、价格和库存信息
> select pc.name as `category_name`, p.name as `product_name`, p.price, p.stock from product p join product_category pc on p.category_id=pc.category_id where p.price > 5000 category_name|product_name |price |stock| -------------+-------------+--------+-----+ 手机 |华为Mate60 Pro | 6999.00| 800| 手机 |苹果iPhone 15 | 7999.00| 1200| 手机 |三星S24 Ultra | 8999.00| 600| 电脑 |联想拯救者Y9000P | 8999.00| 300| 电脑 |苹果MacBook Pro|12999.00| 180| 5 row(s) fetched.
关键提醒:如果联结时省略关联条件(ON/WHERE),会产生 “笛卡尔积”。
笛卡尔积(Cartesian Product)是集合论中的一个基本概念,指两个或多个集合中所有可能的有序元素对的集合。 即表 1 的每一行都与表 2 的每一行匹配,结果集行数 = 表 1 行数 × 表 2 行数(本例中 8×4=32 行),这是无意义的错误结果,务必避免!如下图:

上图,表 1 有 3 条数据,表 2 有 2 条数据,因此笛卡尔积总共有 3 x 2 = 6。
内部联结(也叫 “等值联结”)是最常用的联结类型,只返回两个表中满足关联条件的行(即两个表的 “交集” 数据)。
语法如下:
-- 标准写法(推荐):用INNER JOIN + ON指定关联条件 SELECT 字段 FROM 表1 INNER JOIN 表2 ON 表1.关联字段 = 表2.关联字段; -- 或 SELECT 字段 FROM 表1 JOIN 表2 ON 表1.关联字段 = 表2.关联字段;
等价如下写法,用逗号分隔表,WHERE 指定关联条件:
SELECT 字段 FROM 表1, 表2 WHERE 表1.关联字段 = 表2.关联字段;
示例:查询价格 > 5000 的商品信息,含分类名称、商品名称、价格和库存信息
-- 使用 JOIN ... ON 方式 > select pc.name as `category_name`, p.name as `product_name`, p.price, p.stock from product p join product_category pc on p.category_id=pc.category_id where p.price > 5000 category_name|product_name |price |stock| -------------+-------------+--------+-----+ 手机 |华为Mate60 Pro | 6999.00| 800| 手机 |苹果iPhone 15 | 7999.00| 1200| 手机 |三星S24 Ultra | 8999.00| 600| 电脑 |联想拯救者Y9000P | 8999.00| 300| 电脑 |苹果MacBook Pro|12999.00| 180| 5 row(s) fetched. -- 使用 INNER JOIN ... ON 方式 > select pc.name as `category_name`, p.name as `product_name`, p.price, p.stock from product p inner join product_category pc on p.category_id=pc.category_id where p.price > 5000 category_name|product_name |price |stock| -------------+-------------+--------+-----+ 手机 |华为Mate60 Pro | 6999.00| 800| 手机 |苹果iPhone 15 | 7999.00| 1200| 手机 |三星S24 Ultra | 8999.00| 600| 电脑 |联想拯救者Y9000P | 8999.00| 300| 电脑 |苹果MacBook Pro|12999.00| 180| 5 row(s) fetched. -- 使用 WHERE 方式 > select pc.name as `category_name`, p.name as `product_name`, p.price, p.stock from product p, product_category pc where p.category_id=pc.category_id and p.price > 5000 category_name|product_name |price |stock| -------------+-------------+--------+-----+ 手机 |华为Mate60 Pro | 6999.00| 800| 手机 |苹果iPhone 15 | 7999.00| 1200| 手机 |三星S24 Ultra | 8999.00| 600| 电脑 |联想拯救者Y9000P | 8999.00| 300| 电脑 |苹果MacBook Pro|12999.00| 180| 5 row(s) fetched.
实际业务中,常需要关联 3 张及以上表(比如商品表 + 分类表 + 订单表),核心逻辑是依次联结,逐个指定关联条件。
示例:查询每个订单的商品购买信息,包含 “订单 ID + 商品名称 + 分类名称 + 购买数量”。
> select o.order_id as '订单ID', p.name as '商品名称', pc.name as '分类名称', op.quantity as '购买数量' from `order` o join `order_product` op ON o.order_id=op.order_id join `product` p on p.product_id=op.product_id join `product_category` pc on p.category_id=pc.category_id order by o.order_id 订单ID|商品名称 |分类名称|购买数量| ----+------------+----+----+ 1|小米14手机 |手机 | 1| 2|小米14手机 |手机 | 1| 2|华为Mate60 Pro|手机 | 1| 3|红米K70 |手机 | 1| 4|华为Mate60 Pro|手机 | 1| 5|OPPO Find X7|手机 | 1| 6|三星S24 Ultra |手机 | 1| 7|vivo X100 |手机 | 1| 8|荣耀Magic6 |手机 | 1| 9|苹果iPhone 15 |手机 | 1| 10|一加12 |手机 | 1| 11|真我GT Neo5 |手机 | 1| 12|小米14手机 |手机 | 1| 13|vivo X100 |手机 | 1| 14|华为Mate60 Pro|手机 | 1| 15|红米K70 |手机 | 1| 16|三星S24 Ultra |手机 | 1| 17|荣耀Magic6 |手机 | 1| 18|OPPO Find X7|手机 | 1| 19|真我GT Neo5 |手机 | 1| 20|红米K70 |手机 | 1| 20|vivo X100 |手机 | 2| 20|苹果iPhone 15 |手机 | 1| 23 row(s) fetched.
多表联结的核心原则如下:
(1)顺序无关:先联结表 A 和表 B,再联结表 C;与先联结表 A 和表 C,再联结表 B,结果一致(但性能可能有差异)。注意:SQL 执行多表连接时,数据库会先将两张表连接生成一个中间结果集,再用这个中间结果集和下一张表连接。中间结果集的大小直接决定了后续操作的耗时:
如果先连接小表 / 过滤后结果少的表,中间结果集小,后续计算快。
如果先连接大表 / 过滤后结果多的表,中间结果集大,后续计算慢。
(2)别名必用:多表联结时,必须给表起别名(如oi、p、c),否则字段易产生歧义(比如多个表都有id字段,应该使用 “别名.id”)。
(3)条件清晰:每个JOIN对应一个ON子句,明确当前关联的两个表的条件,避免混乱。
很多需求既可以用联结实现,也可以用子查询实现(比如 “查询 "手机" 分类的商品”),实现如下:
> select p.product_id, p.name, p.price, p.stock from product p join product_category pc on p.category_id=pc.category_id where pc.name='手机' product_id|name |price |stock| ----------+------------+-------+-----+ 1|小米14手机 |4999.00| 1000| 2|华为Mate60 Pro|6999.00| 800| 3|苹果iPhone 15 |7999.00| 1200| 4|vivo X100 |3999.00| 1500| 5|OPPO Find X7|4499.00| 900| 6|荣耀Magic6 |4299.00| 1100| 7|红米K70 |2499.00| 2000| 8|一加12 |4599.00| 850| 9|真我GT Neo5 |2599.00| 1800| 10|三星S24 Ultra |8999.00| 600| 10 row(s) fetched.
或者,使用子查询实现:
> select p.product_id, p.name, p.price, p.stock from product p where p.category_id in (select category_id from product_category where name='手机') product_id|name |price |stock| ----------+------------+-------+-----+ 1|小米14手机 |4999.00| 1000| 2|华为Mate60 Pro|6999.00| 800| 3|苹果iPhone 15 |7999.00| 1200| 4|vivo X100 |3999.00| 1500| 5|OPPO Find X7|4499.00| 900| 6|荣耀Magic6 |4299.00| 1100| 7|红米K70 |2499.00| 2000| 8|一加12 |4599.00| 850| 9|真我GT Neo5 |2599.00| 1800| 10|三星S24 Ultra |8999.00| 600| 10 row(s) fetched.
联结和子查询的核心区别如下:
特性 | 联结(JOIN) | 子查询 |
核心优势 | 性能更优(数据库优化更好)、支持跨表显示字段 | 逻辑更直观(先查条件再查数据)、新手易理解 |
适用场景 | 需要显示多个表的字段、大数据量查询 | 仅需显示单个表的字段、简单条件过滤 |
可读性 | 多表关联时结构清晰 | 嵌套层级多时可读性差 |
实战建议:
若需要整合多个表的字段(比如商品名 + 分类名 + 订单号),优先用联结。
若仅需用一个表的条件过滤另一个表(且只显示单表字段),子查询或联结均可,新手可先选子查询,熟悉后用联结提升性能。