SQL 进阶知识——多表关联与约束

上期文章我们初步学习了 SQL 基础,完成了一些入门级的单表查询。但实际业务中,数据会分散在多张关联表中(如用户表、订单表、商品表)。要高效处理这类数据,就必须掌握多表关联 (解决"如何拼接多表数据")和约束(解决"如何保证数据正确")两大核心技能。

1.多表关联与约束的作用

假设将不同数据集中存储在同一张表(如合并用户、订单和商品信息),我们会遇到三个主要问题:首先,数据冗余 会导致用户手机号等字段被重复存储;其次,数据修改变得繁琐 ,比如更新手机号需要修改所有相关记录;最后,可能出现数据不一致的情况,例如订单关联到不存在的用户。

采用多表关联方案可以解决这些问题:多表关联 通过共享字段实现多表连接查询,有效整合分散的数据;同时,约束设置表级和字段级的规则,能够确保数据的完整性、一致性和唯一性,从根本上避免脏数据的产生。

2.约束核心子集

在学习多表关联之前,我们需要先了解**"主键+外键+非空"**这三个核心子集,这三个约束可看作"表的基本规矩",也是关联的"连接器"。

|------|-------------|-----------|-------|-----------------|-------------------------------|
| 约束类型 | 关键字 | 核心作用 | 是否唯一 | 允许NULL | 典型场景 |
| 主键约束 | PRIMARY KEY | 唯一标识每行数据 | 表内仅1个 | 禁止 | user_id、order_id |
| 外键约束 | FOREIGN KEY | 保证多表关联一致性 | 表内可多个 | 允许(需配合NOT NULL) | orders.user_id关联users.user_id |
| 非空约束 | NOT NULL | 字段必须有值 | 表内可多个 | 禁止 | username、product_name |

2.1 主键约束:表的"身份证"

一张表必须有主键,否则无法唯一定位记录。主键是约束的核心。

示例:

sql 复制代码
sql
-- 方式1:单字段主键(最常用)
CREATE TABLE users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,  -- 自增主键
    username VARCHAR(50) NOT NULL
);

-- 方式2:复合主键(适用于中间表或无自增场景)
CREATE TABLE order_items (
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    -- 复合主键:一个订单的一个商品仅一条记录
    PRIMARY KEY (order_id, product_id)
);

-- 建表后添加主键(不推荐,易遗漏数据)
ALTER TABLE users
ADD CONSTRAINT pk_users PRIMARY KEY (user_id);

其中复合主键需合并声明,而非单独加多个主键。

2.2 外键约束:多表的 "连接器"

外键是建立从表与主表关联 的关键字段,确保从表数据始终与主表记录对应。从表外键必须引用主表的主键或唯一键,并通过操作规则(ON DELETE/ON UPDATE)定义主表数据变更时的级联处理方式。

示例:

sql 复制代码
-- 主表:商品表(被关联方)
CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL
);

-- 从表:订单明细表(关联方)
CREATE TABLE order_items (
    item_id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT NOT NULL,
    product_id INT NOT NULL,  -- 外键关联商品表
    -- 外键约束+操作规则
    FOREIGN KEY (product_id) REFERENCES products(product_id)
        ON DELETE RESTRICT  -- 商品有订单明细时,禁止删除商品
        ON UPDATE CASCADE   -- 商品ID更新时,订单明细同步更新
);

外键操作重要规则

规则 解释 适用场景举例
RESTRICT 主表有从表关联数据 → 禁止删 / 改(默认) 商品有订单,不能删商品
CASCADE 主表删 / 改 → 从表同步删 / 改 用户删除,同步删除其订单
SET NULL 主表删 / 改 → 从表外键设为 NULL 部门解散,员工的部门 ID 设为 NULL
2.3 非空约束:字段必填

有些信息不能缺失(比如用户名、商品名称),非空约束就是强制"必须填值",避免关键信息为空。

示例:

sql 复制代码
-- 创建时添加
CREATE TABLE users (
    username VARCHAR(50) NOT NULL,  -- 用户名必须填
    phone VARCHAR(20)  -- 手机号可选填(默认允许NULL)
);

-- 修改表添加/删除非空约束
ALTER TABLE users MODIFY phone VARCHAR(20) NOT NULL;  -- 手机号改为必须填
ALTER TABLE users MODIFY phone VARCHAR(20) NULL;      -- 手机号改回可选填

3.基础多表关联实践

学习完上文的约束核心子集,我们来进行初步的实践。

我们可以创建两张表,先"不用外键"做关联查询,看看会发生什么。

sql 复制代码
-- 1. 创建无外键的订单表
CREATE TABLE orders (
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,  -- 没有外键约束
    product_name VARCHAR(100) NOT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;  -- 兼容中文的字符集设置

-- 2. 插入数据:故意插一个不存在的user_id=3(用户表只有1和2)
INSERT INTO orders (user_id, product_name) 
VALUES (1, '手机'), (3, '耳机');  

-- 3. 关联查询:查用户和订单
SELECT u.username, o.order_id, o.product_name
FROM users u
JOIN orders o ON u.user_id = o.user_id;

在这之前我已手动创建好 users 表,并插入了两份数据。

之后向 order 表中插入两组数据,分别是(1, '手机') 和 (3, '耳机'),但是 user 表中是没有user_id 为3的用户的,然而由于订单表没有外键约束, (3, '耳机')这组数据能被成功插入。

此时以 user_id 相同为条件查询 user 表数据,只能得到 user_id 等于1的对象。

现在删除原 order 表,建立新的以 user_id 为外键约束的订单表。

sql 复制代码
-- 重建订单表,加外键约束,设置utf8mb4字符集
CREATE TABLE orders (
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    -- 外键约束:关联用户表,禁止插入无效user_id
    FOREIGN KEY (user_id) REFERENCES users(user_id)
        ON DELETE RESTRICT  -- 有订单的用户不能删(避免孤儿订单)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;  -- 兼容中文的字符集设置`

再插入 (3, '耳机')一定会报错。如此便可以杜绝混乱数据。

4.关联语法JOIN

上面的实践代码里我们已经用了 JOIN(内连接)。实际应用中我们需先掌握四种常见 JOIN 类型。

JOIN类型 作用 代码示例 结果特点
INNER JOIN(内连接) 只取"两张表都匹配"的记录 FROM users u JOIN orders o ON u.user_id=o.user_id 无匹配记录全丢弃(如无订单的用户不显示)
LEFT JOIN(左外连接) 左表全保留,右表只取匹配的 FROM users u LEFT JOIN orders o ON u.user_id=o.user_id 左表完整,右表无匹配补NULL(如无订单用户的order_id为NULL)
RIGHT JOIN(右外连接) 右表全保留,左表只取匹配的 FROM users u RIGHT JOIN orders o ON u.user_id=o.user_id 右表完整,左表无匹配补NULL(如无效订单的username为NULL)
FULL JOIN(全外连接) 两张表全保留,不匹配补NULL MySQL用UNION模拟(见下方) 无遗漏记录,MySQL需用UNION拼接左右连接
sql 复制代码
SELECT u.username, o.order_id
FROM users u LEFT JOIN orders o ON u.user_id=o.user_id
UNION  -- 合并结果并去重
SELECT u.username, o.order_id
FROM users u RIGHT JOIN orders o ON u.user_id=o.user_id;

5.多表关联关系

复杂的多表场景都是以下三种关系的组合:

  • 一对多(1:N):这是最常用的关系,例如一个用户可下多个订单,一个订单只属于一个用户。它的实现方式是"一"的表(用户表)设主键,"多"的表(订单表)加外键关联主键。

  • 一对一(1:1): 例如一个用户只有一份身份证信息,一份身份证只对应一个用户。用子表加"唯一外键"关联主表主键实现。

  • 多对多(N:N) :例如一个学生可选多门课,一门课可被多个学生选。这种关系较为复杂,两张表之间无法直接关联,而是需要创建"中间表",含两个外键分别关联两张主表主键,且外键组合为复合主键。

"中间表"示例:

sql 复制代码
-- 中间表:学生-课程关联表
CREATE TABLE student_course (
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    -- 复合主键:保证一个学生选同一门课仅一次
    PRIMARY KEY (student_id, course_id),
    -- 双外键关联两张主表
    FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES courses(course_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;  -- 兼容中文的字符集设置

6.其他约束

最后再补充一些其他的约束。

  • 唯一约束(UNIQUE):字段"不能重复"。比如手机号、邮箱不能重复注册,用唯一约束实现。(ADD COLUMN phone VARCHAR(20) UNIQUE NOT NULL; -- 手机号唯一且必填)

  • 检查约束(CHECK):字段"符合条件",限制字段值的范围。(ADD CHECK (price >= 0); -- 价格不能为负)

  • 默认约束(DEFAULT):字段"自动填值",插入数据时若未传值,自动填默认值。(ADD COLUMN status VARCHAR(20) DEFAULT '待付款'; -- 默认待付款)

相关推荐
一 乐2 小时前
学生宿舍管理|基于springboot + vue学生宿舍管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·助农电商系统
heze092 小时前
sqli-labs-Less-23
数据库·mysql·网络安全
米汤爱学习2 小时前
Redis-漏洞
数据库·redis·缓存
wWYy.2 小时前
详解redis(7):数据结构List
数据库·redis·缓存
砚边数影2 小时前
时序数据库国产化替代,破局迁移“三不”困局
数据库·时序数据库·kingbase·kingbasees·金仓数据库
专注于大数据技术栈2 小时前
Redis 中 USED 和 RSS
数据库·redis·缓存
一个响当当的名号3 小时前
lectrue8 表索引
数据库
独自破碎E3 小时前
MySQL是怎么实现事务的?
数据库·mysql
卜锦元3 小时前
Docker Compose 部署 MySQL 8.4 LTS(生产级实践方案)
数据库·mysql·docker·容器