SQL表设计与约束教程

本文档详细讲解表的创建、修改以及各种完整性约束的使用。


目录

一、表的创建

[1.1 基本语法](#1.1 基本语法)

[1.2 简单示例](#1.2 简单示例)

二、数据类型详解

[2.1 字符串类型](#2.1 字符串类型)

[2.2 数值类型](#2.2 数值类型)

[2.3 日期时间类型](#2.3 日期时间类型)

[2.4 其他类型](#2.4 其他类型)

三、完整性约束

[3.1 NOT NULL约束 - 非空约束](#3.1 NOT NULL约束 - 非空约束)

[3.2 DEFAULT约束 - 默认值](#3.2 DEFAULT约束 - 默认值)

[3.3 CHECK约束 - 检查约束](#3.3 CHECK约束 - 检查约束)

[3.4 UNIQUE约束 - 唯一性约束](#3.4 UNIQUE约束 - 唯一性约束)

[3.5 PRIMARY KEY约束 - 主键约束](#3.5 PRIMARY KEY约束 - 主键约束)

[3.6 FOREIGN KEY约束 - 外键约束](#3.6 FOREIGN KEY约束 - 外键约束)

[3.7 约束命名规范](#3.7 约束命名规范)

四、表的修改

[4.1 添加列](#4.1 添加列)

[4.2 修改列](#4.2 修改列)

[4.3 删除列](#4.3 删除列)

[4.4 重命名列](#4.4 重命名列)

[4.5 重命名表](#4.5 重命名表)

五、表的删除

六、约束的管理

[6.1 添加约束](#6.1 添加约束)

[6.2 删除约束](#6.2 删除约束)

[6.3 禁用/启用约束](#6.3 禁用/启用约束)

[6.4 查询约束信息](#6.4 查询约束信息)

七、实战案例

案例1:完整的图书管理系统表设计

案例2:约束使用的最佳实践

案例3:使用序列生成主键

八、常见问题与解决方案

问题1:约束冲突

问题2:外键约束导致无法删除

问题3:无法修改列的数据类型

[问题4:NOT NULL约束冲突](#问题4:NOT NULL约束冲突)

九、总结与最佳实践

设计原则

约束检查清单

文档示例


一、表的创建

1.1 基本语法

sql 复制代码
CREATE TABLE 表名 (
    列名1 数据类型 [约束],
    列名2 数据类型 [约束],
    ...
    [表级约束]
);

1.2 简单示例

sql 复制代码
-- 创建最简单的表
CREATE TABLE 学生 (
    学号 VARCHAR2(10),
    姓名 VARCHAR2(50),
    年龄 NUMBER
);

二、数据类型详解

2.1 字符串类型

sql 复制代码
-- CHAR: 定长字符串
学号 CHAR(10)           -- 始终占用10个字符,不足补空格
-- 适用场景:长度固定的数据,如身份证号、手机号

-- VARCHAR2: 变长字符串(Oracle推荐)
姓名 VARCHAR2(50)       -- 最多50个字符,实际占用根据内容
-- 适用场景:长度不确定的数据,如姓名、地址

-- VARCHAR: 变长字符串(不推荐,Oracle保留)
-- 建议使用VARCHAR2

-- CLOB: 大文本
简介 CLOB              -- 可存储最多4GB的文本
-- 适用场景:长文章、详细描述等

CHAR vs VARCHAR2 示例

sql 复制代码
-- CHAR(10)
INSERT INTO test VALUES ('ABC');  
-- 实际存储:'ABC       ' (补了7个空格,始终占10字节)

-- VARCHAR2(10)
INSERT INTO test VALUES ('ABC');
-- 实际存储:'ABC' (只占3字节)

-- 性能对比:
-- CHAR: 定长,查询稍快,但浪费空间
-- VARCHAR2: 变长,节省空间,查询稍慢(可忽略)
-- 建议:除非长度确实固定,否则用VARCHAR2

2.2 数值类型

sql 复制代码
-- NUMBER: 通用数值类型
年龄 NUMBER              -- 整数或小数,精度38位
单价 NUMBER(8,2)         -- 总共8位,小数点后2位(如:123456.78)
数量 NUMBER(5)           -- 5位整数(如:12345)

-- NUMBER(p,s) 说明:
-- p: precision (精度) - 总位数(1-38)
-- s: scale (刻度) - 小数位数

-- 示例:
CREATE TABLE 商品 (
    商品价格 NUMBER(10,2),    -- 最大:99999999.99
    库存数量 NUMBER(6),        -- 最大:999999
    折扣率 NUMBER(3,2)        -- 最大:9.99 (如0.85表示85折)
);

-- INTEGER: NUMBER的子类型(整数)
数量 INTEGER             -- 等价于 NUMBER(38)

-- FLOAT: 浮点数
系数 FLOAT               -- 科学计算用,一般用NUMBER即可

NUMBER精度示例

sql 复制代码
-- NUMBER(5,2)
123.45    -- ✅ 正确
1234.5    -- ✅ 正确 (总共5位)
12345     -- ❌ 错误:超过5位
12.345    -- ❌ 错误:小数位超过2位(会四舍五入为12.35)

-- NUMBER(5,-2)  负的scale表示向左取整
12345     -- 存储为 12300 (十位和个位取整)
123456    -- ❌ 错误:超过范围

2.3 日期时间类型

sql 复制代码
-- DATE: 日期+时间(精确到秒)
出生日期 DATE                    -- 存储:年月日时分秒
-- 范围:公元前4712年1月1日 到 公元9999年12月31日

-- TIMESTAMP: 时间戳(精确到秒后9位)
创建时间 TIMESTAMP               -- 更精确的时间
-- 适用场景:需要高精度时间记录

-- 插入DATE示例
INSERT INTO 表 VALUES (
    TO_DATE('2023-10-15', 'YYYY-MM-DD'),           -- 只有日期
    TO_DATE('2023-10-15 14:30:25', 'YYYY-MM-DD HH24:MI:SS')  -- 日期+时间
);

-- SYSDATE vs SYSTIMESTAMP
插入时间 DATE DEFAULT SYSDATE         -- 默认当前日期时间
详细时间 TIMESTAMP DEFAULT SYSTIMESTAMP  -- 默认当前时间戳

2.4 其他类型

sql 复制代码
-- BLOB: 二进制大对象
照片 BLOB                       -- 存储图片、文件等二进制数据

-- RAW: 二进制数据
密码哈希 RAW(32)                -- 固定长度二进制数据

-- LONG: 长文本(已过时,不推荐)
-- 建议用CLOB代替

三、完整性约束

3.1 NOT NULL约束 - 非空约束

sql 复制代码
-- 列级约束
CREATE TABLE 学生 (
    学号 VARCHAR2(10) NOT NULL,     -- 学号不能为空
    姓名 VARCHAR2(50) NOT NULL,     -- 姓名不能为空
    年龄 NUMBER                      -- 年龄可以为空
);

-- 测试
INSERT INTO 学生 VALUES ('2001001', '张三', 20);    -- ✅ 正确
INSERT INTO 学生 VALUES (NULL, '李四', 21);         -- ❌ 错误:学号不能为NULL
INSERT INTO 学生 VALUES ('2001002', NULL, 22);      -- ❌ 错误:姓名不能为NULL
INSERT INTO 学生 VALUES ('2001003', '王五', NULL);  -- ✅ 正确:年龄可以为NULL

3.2 DEFAULT约束 - 默认值

sql 复制代码
CREATE TABLE 图书 (
    图书编号 VARCHAR2(10) NOT NULL,
    书名 VARCHAR2(100) NOT NULL,
    是否借出 VARCHAR2(2) DEFAULT '否',              -- 默认值:否
    借出次数 NUMBER DEFAULT 0,                      -- 默认值:0
    入库日期 DATE DEFAULT SYSDATE,                  -- 默认值:当前日期
    状态 VARCHAR2(10) DEFAULT '正常'                -- 默认值:正常
);

-- 测试默认值
INSERT INTO 图书 (图书编号, 书名) VALUES ('T001', '红楼梦');
-- 结果:是否借出='否', 借出次数=0, 入库日期=当前日期, 状态='正常'

INSERT INTO 图书 VALUES ('T002', '西游记', '是', 5, DATE '2023-01-01', '损坏');
-- 结果:使用指定值,不使用默认值

-- 显式使用DEFAULT关键字
INSERT INTO 图书 VALUES ('T003', '三国演义', DEFAULT, DEFAULT, DEFAULT, '正常');
-- 结果:是否借出、借出次数、入库日期都使用默认值

DEFAULT的使用场景

sql 复制代码
-- 1. 布尔值字段
是否有效 VARCHAR2(1) DEFAULT 'Y'

-- 2. 状态字段
审核状态 VARCHAR2(10) DEFAULT '待审核'

-- 3. 计数字段
访问次数 NUMBER DEFAULT 0

-- 4. 时间戳
创建时间 DATE DEFAULT SYSDATE
更新时间 TIMESTAMP DEFAULT SYSTIMESTAMP

-- 5. 标识字段
删除标记 VARCHAR2(1) DEFAULT 'N'

3.3 CHECK约束 - 检查约束

sql 复制代码
-- 基本CHECK约束
CREATE TABLE 学生 (
    学号 VARCHAR2(10) NOT NULL,
    姓名 VARCHAR2(50) NOT NULL,
    性别 VARCHAR2(2) CHECK (性别 IN ('男', '女')),           -- 只能是'男'或'女'
    年龄 NUMBER CHECK (年龄 >= 0 AND 年龄 <= 150),          -- 年龄在0-150之间
    成绩 NUMBER(5,2) CHECK (成绩 >= 0 AND 成绩 <= 100),    -- 成绩0-100分
    email VARCHAR2(100) CHECK (email LIKE '%@%')            -- 必须包含@符号
);

-- 测试CHECK约束
INSERT INTO 学生 VALUES ('2001001', '张三', '男', 20, 85.5, 'zhang@qq.com');     -- ✅ 正确
INSERT INTO 学生 VALUES ('2001002', '李四', '女', 22, 92.0, 'li@163.com');       -- ✅ 正确
INSERT INTO 学生 VALUES ('2001003', '王五', '未知', 21, 88, 'wang@qq.com');      -- ❌ 错误:性别不符合
INSERT INTO 学生 VALUES ('2001004', '赵六', '男', -5, 90, 'zhao@qq.com');        -- ❌ 错误:年龄为负
INSERT INTO 学生 VALUES ('2001005', '孙七', '女', 25, 105, 'sun@qq.com');        -- ❌ 错误:成绩超过100
INSERT INTO 学生 VALUES ('2001006', '周八', '男', 23, 88, 'zhou.com');           -- ❌ 错误:email格式不对

CHECK约束的复杂示例

sql 复制代码
CREATE TABLE 图书 (
    图书编号 VARCHAR2(10) NOT NULL,
    书名 VARCHAR2(100) NOT NULL,
    
    -- 示例1:IN列表检查
    是否借出 VARCHAR2(2) DEFAULT '否' 
        CHECK (是否借出 IN ('是', '否')),
    
    -- 示例2:范围检查
    单价 NUMBER(8,2) 
        CHECK (单价 > 0 AND 单价 <= 9999.99),
    
    -- 示例3:多条件组合
    借阅天数 NUMBER 
        CHECK (借阅天数 >= 1 AND 借阅天数 <= 90),
    
    -- 示例4:字符串长度检查
    ISBN VARCHAR2(20) 
        CHECK (LENGTH(ISBN) >= 10),
    
    -- 示例5:结合其他列(表级约束)
    出版日期 DATE,
    购买日期 DATE,
    CHECK (购买日期 >= 出版日期)  -- 购买日期不能早于出版日期
);

-- 更复杂的CHECK示例
CREATE TABLE 员工 (
    员工编号 VARCHAR2(10),
    姓名 VARCHAR2(50),
    职位 VARCHAR2(20) CHECK (职位 IN ('经理', '主管', '员工', '实习生')),
    基本工资 NUMBER(10,2),
    奖金 NUMBER(10,2),
    
    -- 根据职位检查工资范围
    CHECK (
        (职位 = '经理' AND 基本工资 >= 10000) OR
        (职位 = '主管' AND 基本工资 >= 7000) OR
        (职位 = '员工' AND 基本工资 >= 4000) OR
        (职位 = '实习生' AND 基本工资 >= 2000)
    ),
    
    -- 奖金不能超过基本工资
    CHECK (奖金 <= 基本工资)
);

3.4 UNIQUE约束 - 唯一性约束

sql 复制代码
-- 列级UNIQUE约束
CREATE TABLE 用户 (
    用户ID NUMBER PRIMARY KEY,
    用户名 VARCHAR2(50) UNIQUE,           -- 用户名不能重复
    邮箱 VARCHAR2(100) UNIQUE,            -- 邮箱不能重复
    手机号 VARCHAR2(11) UNIQUE,           -- 手机号不能重复
    密码 VARCHAR2(100) NOT NULL
);

-- ⚠️ 注意:UNIQUE允许NULL值,且多个NULL不算重复
INSERT INTO 用户 VALUES (1, 'zhangsan', 'zs@qq.com', '13800138000', 'pwd123');  -- ✅
INSERT INTO 用户 VALUES (2, 'lisi', 'ls@qq.com', NULL, 'pwd456');               -- ✅
INSERT INTO 用户 VALUES (3, 'wangwu', 'ww@qq.com', NULL, 'pwd789');             -- ✅ NULL可以重复
INSERT INTO 用户 VALUES (4, 'zhangsan', 'zl@qq.com', '13900139000', 'pwd');     -- ❌ 用户名重复

-- 表级UNIQUE约束(多列组合唯一)
CREATE TABLE 课程成绩 (
    学号 VARCHAR2(10),
    课程号 VARCHAR2(10),
    成绩 NUMBER(5,2),
    UNIQUE (学号, 课程号)  -- 同一学生的同一课程只能有一条成绩记录
);

-- 测试组合唯一
INSERT INTO 课程成绩 VALUES ('2001001', 'C001', 85);   -- ✅
INSERT INTO 课程成绩 VALUES ('2001001', 'C002', 90);   -- ✅ 不同课程
INSERT INTO 课程成绩 VALUES ('2001002', 'C001', 88);   -- ✅ 不同学生
INSERT INTO 课程成绩 VALUES ('2001001', 'C001', 95);   -- ❌ (学号,课程号)组合重复

3.5 PRIMARY KEY约束 - 主键约束

sql 复制代码
-- 列级主键
CREATE TABLE 学生 (
    学号 VARCHAR2(10) PRIMARY KEY,        -- 主键:唯一且非空
    姓名 VARCHAR2(50) NOT NULL,
    年龄 NUMBER
);

-- 等价于
CREATE TABLE 学生 (
    学号 VARCHAR2(10) NOT NULL UNIQUE,
    姓名 VARCHAR2(50) NOT NULL,
    年龄 NUMBER
);

-- 表级主键(推荐:可以命名约束)
CREATE TABLE 学生 (
    学号 VARCHAR2(10),
    姓名 VARCHAR2(50) NOT NULL,
    年龄 NUMBER,
    CONSTRAINT pk_学生 PRIMARY KEY (学号)
);

-- 复合主键(多列组成主键)
CREATE TABLE 借阅 (
    借书证号 VARCHAR2(10),
    图书编号 VARCHAR2(10),
    借书日期 DATE,
    CONSTRAINT pk_借阅 PRIMARY KEY (借书证号, 图书编号, 借书日期)
    -- 同一读者的同一本书在同一天只能借一次
);

主键的特点

sql 复制代码
-- 1. 唯一性:不能重复
-- 2. 非空性:不能为NULL
-- 3. 一张表只能有一个主键
-- 4. 自动创建唯一索引,查询快

-- 主键vs唯一约束
特性           PRIMARY KEY       UNIQUE
唯一性         是                是
允许NULL       否                是(可以多个NULL)
数量限制       每表1个           每表多个
自动索引       是                是

3.6 FOREIGN KEY约束 - 外键约束

sql 复制代码
-- 创建父表(被引用表)
CREATE TABLE 书目 (
    ISBN VARCHAR2(20) PRIMARY KEY,
    书名 VARCHAR2(100) NOT NULL,
    作者 VARCHAR2(50)
);

-- 创建子表(引用表)
CREATE TABLE 图书 (
    图书编号 VARCHAR2(10) PRIMARY KEY,
    ISBN VARCHAR2(20),
    是否借出 VARCHAR2(2) DEFAULT '否',
    
    -- 外键约束:ISBN必须在书目表中存在
    CONSTRAINT fk_图书_书目 FOREIGN KEY (ISBN) 
        REFERENCES 书目(ISBN)
);

-- 测试外键约束
INSERT INTO 书目 VALUES ('978-7-01-001', '红楼梦', '曹雪芹');           -- ✅ 先插入父表
INSERT INTO 图书 VALUES ('T001', '978-7-01-001', '否');                -- ✅ ISBN存在
INSERT INTO 图书 VALUES ('T002', '978-7-01-999', '否');                -- ❌ ISBN不存在

-- 外键的级联操作
CREATE TABLE 图书 (
    图书编号 VARCHAR2(10) PRIMARY KEY,
    ISBN VARCHAR2(20),
    
    CONSTRAINT fk_图书_书目 FOREIGN KEY (ISBN) 
        REFERENCES 书目(ISBN)
        ON DELETE CASCADE              -- 删除父表记录时,自动删除子表相关记录
        -- ON DELETE SET NULL          -- 删除父表记录时,子表外键设为NULL
        -- ON DELETE RESTRICT          -- 如果子表有相关记录,禁止删除父表(默认)
);

外键级联操作详解

sql 复制代码
-- 1. ON DELETE CASCADE - 级联删除
CREATE TABLE 订单明细 (
    明细ID NUMBER PRIMARY KEY,
    订单ID NUMBER,
    商品名 VARCHAR2(100),
    FOREIGN KEY (订单ID) REFERENCES 订单(订单ID) ON DELETE CASCADE
);
-- 删除订单时,自动删除该订单的所有明细

-- 2. ON DELETE SET NULL - 设置为NULL
CREATE TABLE 员工 (
    员工ID NUMBER PRIMARY KEY,
    姓名 VARCHAR2(50),
    部门ID NUMBER,
    FOREIGN KEY (部门ID) REFERENCES 部门(部门ID) ON DELETE SET NULL
);
-- 删除部门时,员工的部门ID设为NULL(员工保留)

-- 3. ON DELETE RESTRICT - 限制删除(Oracle默认)
-- 如果子表有相关记录,禁止删除父表记录
DELETE FROM 书目 WHERE ISBN = '978-7-01-001';
-- ❌ 如果图书表中有这个ISBN的记录,删除失败

-- 4. ON UPDATE CASCADE - 级联更新(Oracle不支持,MySQL支持)
-- Oracle中主键一般不更新,所以不提供此功能

完整的外键示例

sql 复制代码
-- 多层外键关系
CREATE TABLE 书目 (
    ISBN VARCHAR2(20) PRIMARY KEY,
    书名 VARCHAR2(100) NOT NULL
);

CREATE TABLE 图书 (
    图书编号 VARCHAR2(10) PRIMARY KEY,
    ISBN VARCHAR2(20),
    FOREIGN KEY (ISBN) REFERENCES 书目(ISBN)
);

CREATE TABLE 读者 (
    借书证号 VARCHAR2(10) PRIMARY KEY,
    姓名 VARCHAR2(50) NOT NULL
);

CREATE TABLE 借阅 (
    借阅流水号 NUMBER PRIMARY KEY,
    借书证号 VARCHAR2(10),
    图书编号 VARCHAR2(10),
    借书日期 DATE DEFAULT SYSDATE,
    
    -- 多个外键
    CONSTRAINT fk_借阅_读者 FOREIGN KEY (借书证号) 
        REFERENCES 读者(借书证号),
    CONSTRAINT fk_借阅_图书 FOREIGN KEY (图书编号) 
        REFERENCES 图书(图书编号)
);

3.7 约束命名规范

sql 复制代码
-- 推荐的命名规范
CREATE TABLE 学生 (
    学号 VARCHAR2(10),
    姓名 VARCHAR2(50) NOT NULL,
    性别 VARCHAR2(2),
    身份证号 VARCHAR2(18),
    班级ID NUMBER,
    
    -- 主键:pk_表名
    CONSTRAINT pk_学生 PRIMARY KEY (学号),
    
    -- 唯一约束:uk_表名_列名
    CONSTRAINT uk_学生_身份证号 UNIQUE (身份证号),
    
    -- 外键:fk_表名_引用表名
    CONSTRAINT fk_学生_班级 FOREIGN KEY (班级ID) 
        REFERENCES 班级(班级ID),
    
    -- 检查约束:ck_表名_列名
    CONSTRAINT ck_学生_性别 CHECK (性别 IN ('男', '女'))
);

-- 命名的好处:
-- 1. 错误信息更清晰
-- 2. 便于后续管理和修改
-- 3. 提高可维护性

四、表的修改

4.1 添加列

sql 复制代码
-- 添加单列
ALTER TABLE 学生 ADD (联系电话 VARCHAR2(11));

-- 添加多列
ALTER TABLE 学生 ADD (
    邮箱 VARCHAR2(100),
    地址 VARCHAR2(200)
);

-- 添加带约束的列
ALTER TABLE 学生 ADD (
    身份证号 VARCHAR2(18) UNIQUE,
    出生日期 DATE DEFAULT DATE '2000-01-01'
);

4.2 修改列

sql 复制代码
-- 修改列的数据类型
ALTER TABLE 学生 MODIFY (姓名 VARCHAR2(100));

-- 修改列的默认值
ALTER TABLE 图书 MODIFY (是否借出 DEFAULT '是');

-- 修改列为非空
ALTER TABLE 学生 MODIFY (联系电话 NOT NULL);

-- 修改多个列
ALTER TABLE 学生 MODIFY (
    姓名 VARCHAR2(100),
    年龄 NUMBER DEFAULT 18
);

-- ⚠️ 注意事项:
-- 1. 如果列中已有数据,修改类型可能失败
-- 2. 扩大长度通常没问题,缩小长度需要检查数据
-- 3. 修改为NOT NULL前,确保列中没有NULL值

4.3 删除列

sql 复制代码
-- 删除单列
ALTER TABLE 学生 DROP COLUMN 联系电话;

-- 删除多列
ALTER TABLE 学生 DROP (邮箱, 地址);

-- ⚠️ 删除列是永久性的,无法恢复!

4.4 重命名列

sql 复制代码
-- 重命名列
ALTER TABLE 学生 RENAME COLUMN 联系电话 TO 手机号;

4.5 重命名表

sql 复制代码
-- 重命名表
ALTER TABLE 学生 RENAME TO 学生信息表;

-- 或者使用
RENAME 学生 TO 学生信息表;

五、表的删除

sql 复制代码
-- 删除表(保留结构)
TRUNCATE TABLE 学生;  
-- 删除所有数据,但保留表结构
-- 速度快,不能回滚

-- 删除表(完全删除)
DROP TABLE 学生;
-- 删除表结构和数据
-- 不能回滚

-- 删除表并清除依赖
DROP TABLE 学生 CASCADE CONSTRAINTS;
-- 同时删除相关的外键约束

-- 删除数据(可回滚)
DELETE FROM 学生;
-- 删除所有数据,可以回滚
-- 速度较慢

三种删除方式对比

|---------|--------|----------|--------|
| 操作 | DELETE | TRUNCATE | DROP |
| 删除内容 | 数据 | 数据 | 表结构+数据 |
| 保留结构 | 是 | 是 | 否 |
| 可回滚 | 是 | 否 | 否 |
| 速度 | 慢 | 快 | 快 |
| WHERE条件 | 支持 | 不支持 | 不支持 |


六、约束的管理

6.1 添加约束

sql 复制代码
-- 添加主键
ALTER TABLE 学生 ADD CONSTRAINT pk_学生 PRIMARY KEY (学号);

-- 添加外键
ALTER TABLE 图书 ADD CONSTRAINT fk_图书_书目 
    FOREIGN KEY (ISBN) REFERENCES 书目(ISBN);

-- 添加唯一约束
ALTER TABLE 学生 ADD CONSTRAINT uk_学生_身份证 UNIQUE (身份证号);

-- 添加检查约束
ALTER TABLE 学生 ADD CONSTRAINT ck_学生_年龄 
    CHECK (年龄 >= 0 AND 年龄 <= 150);

-- 添加非空约束
ALTER TABLE 学生 MODIFY (姓名 NOT NULL);

6.2 删除约束

sql 复制代码
-- 删除约束(通过约束名)
ALTER TABLE 学生 DROP CONSTRAINT ck_学生_年龄;
ALTER TABLE 学生 DROP CONSTRAINT uk_学生_身份证;
ALTER TABLE 学生 DROP CONSTRAINT fk_图书_书目;

-- 删除主键
ALTER TABLE 学生 DROP PRIMARY KEY;

-- 删除主键(并级联删除相关外键)
ALTER TABLE 学生 DROP PRIMARY KEY CASCADE;

-- 删除非空约束
ALTER TABLE 学生 MODIFY (姓名 NULL);

6.3 禁用/启用约束

sql 复制代码
-- 禁用约束(临时关闭检查)
ALTER TABLE 学生 DISABLE CONSTRAINT ck_学生_年龄;

-- 启用约束
ALTER TABLE 学生 ENABLE CONSTRAINT ck_学生_年龄;

-- 使用场景:
-- 1. 批量导入数据时,暂时禁用约束提高性能
-- 2. 数据修复时,暂时放宽限制
-- 3. 测试时临时禁用

-- 示例:批量导入
ALTER TABLE 学生 DISABLE CONSTRAINT ck_学生_年龄;
-- ... 执行批量INSERT ...
ALTER TABLE 学生 ENABLE CONSTRAINT ck_学生_年龄;

6.4 查询约束信息

sql 复制代码
-- 查询表的所有约束
SELECT 
    constraint_name AS 约束名,
    constraint_type AS 约束类型,
    search_condition AS 检查条件
FROM user_constraints
WHERE table_name = '学生';

-- 约束类型:
-- P: Primary Key (主键)
-- R: Referential (外键)
-- U: Unique (唯一)
-- C: Check (检查,包括NOT NULL)

-- 查询外键详细信息
SELECT 
    a.constraint_name AS 外键名,
    a.table_name AS 子表,
    b.table_name AS 父表,
    a.column_name AS 子表列,
    b.column_name AS 父表列
FROM user_cons_columns a
JOIN user_constraints c ON a.constraint_name = c.constraint_name
JOIN user_cons_columns b ON c.r_constraint_name = b.constraint_name
WHERE c.constraint_type = 'R'
AND a.table_name = '图书';

七、实战案例

案例1:完整的图书管理系统表设计

sql 复制代码
-- 1. 图书分类表
CREATE TABLE 图书分类 (
    分类编号 VARCHAR2(10),
    分类名称 VARCHAR2(50) NOT NULL,
    父分类编号 VARCHAR2(10),
    
    CONSTRAINT pk_图书分类 PRIMARY KEY (分类编号),
    CONSTRAINT fk_分类_父分类 FOREIGN KEY (父分类编号)
        REFERENCES 图书分类(分类编号)
);

-- 2. 书目表
CREATE TABLE 书目 (
    ISBN VARCHAR2(20),
    书名 VARCHAR2(100) NOT NULL,
    作者 VARCHAR2(50),
    出版单位 VARCHAR2(100),
    出版日期 DATE,
    单价 NUMBER(8,2) CHECK (单价 > 0),
    图书分类号 VARCHAR2(10),
    
    CONSTRAINT pk_书目 PRIMARY KEY (ISBN),
    CONSTRAINT fk_书目_分类 FOREIGN KEY (图书分类号)
        REFERENCES 图书分类(分类编号),
    CONSTRAINT ck_书目_ISBN长度 CHECK (LENGTH(ISBN) >= 10)
);

-- 3. 图书表(具体的每一本实体书)
CREATE TABLE 图书 (
    图书编号 VARCHAR2(10),
    ISBN VARCHAR2(20) NOT NULL,
    是否借出 VARCHAR2(2) DEFAULT '否',
    状态 VARCHAR2(10) DEFAULT '正常',
    入库日期 DATE DEFAULT SYSDATE,
    
    CONSTRAINT pk_图书 PRIMARY KEY (图书编号),
    CONSTRAINT fk_图书_书目 FOREIGN KEY (ISBN)
        REFERENCES 书目(ISBN),
    CONSTRAINT ck_图书_是否借出 CHECK (是否借出 IN ('是', '否')),
    CONSTRAINT ck_图书_状态 CHECK (状态 IN ('正常', '损坏', '丢失', '维修中'))
);

-- 4. 读者表
CREATE TABLE 读者 (
    借书证号 VARCHAR2(10),
    姓名 VARCHAR2(50) NOT NULL,
    性别 VARCHAR2(2),
    单位 VARCHAR2(100),
    职称 VARCHAR2(50),
    电话 VARCHAR2(20),
    身份证号 VARCHAR2(18),
    注册日期 DATE DEFAULT SYSDATE,
    状态 VARCHAR2(10) DEFAULT '正常',
    
    CONSTRAINT pk_读者 PRIMARY KEY (借书证号),
    CONSTRAINT uk_读者_身份证 UNIQUE (身份证号),
    CONSTRAINT ck_读者_性别 CHECK (性别 IN ('男', '女')),
    CONSTRAINT ck_读者_状态 CHECK (状态 IN ('正常', '挂失', '注销')),
    CONSTRAINT ck_读者_电话 CHECK (LENGTH(电话) >= 7)
);

-- 5. 借阅表
CREATE TABLE 借阅 (
    借阅流水号 NUMBER,
    借书证号 VARCHAR2(10) NOT NULL,
    图书编号 VARCHAR2(10) NOT NULL,
    借书日期 DATE DEFAULT SYSDATE NOT NULL,
    应还日期 DATE NOT NULL,
    归还日期 DATE,
    续借次数 NUMBER DEFAULT 0,
    备注 VARCHAR2(200),
    
    CONSTRAINT pk_借阅 PRIMARY KEY (借阅流水号),
    CONSTRAINT fk_借阅_读者 FOREIGN KEY (借书证号)
        REFERENCES 读者(借书证号),
    CONSTRAINT fk_借阅_图书 FOREIGN KEY (图书编号)
        REFERENCES 图书(图书编号),
    CONSTRAINT ck_借阅_日期 CHECK (应还日期 >= 借书日期),
    CONSTRAINT ck_借阅_归还日期 CHECK (归还日期 IS NULL OR 归还日期 >= 借书日期),
    CONSTRAINT ck_借阅_续借次数 CHECK (续借次数 >= 0 AND 续借次数 <= 3)
);

-- 6. 罚款表
CREATE TABLE 罚款 (
    罚款编号 NUMBER,
    借阅流水号 NUMBER NOT NULL,
    罚款金额 NUMBER(8,2) NOT NULL,
    罚款原因 VARCHAR2(100) NOT NULL,
    是否缴纳 VARCHAR2(2) DEFAULT '否',
    罚款日期 DATE DEFAULT SYSDATE,
    缴纳日期 DATE,
    
    CONSTRAINT pk_罚款 PRIMARY KEY (罚款编号),
    CONSTRAINT fk_罚款_借阅 FOREIGN KEY (借阅流水号)
        REFERENCES 借阅(借阅流水号),
    CONSTRAINT ck_罚款_金额 CHECK (罚款金额 > 0),
    CONSTRAINT ck_罚款_是否缴纳 CHECK (是否缴纳 IN ('是', '否')),
    CONSTRAINT ck_罚款_缴纳日期 CHECK (缴纳日期 IS NULL OR 缴纳日期 >= 罚款日期)
);

案例2:约束使用的最佳实践

sql 复制代码
CREATE TABLE 员工 (
    -- 1. 主键:使用有意义的业务主键或序列
    员工编号 VARCHAR2(10),
    
    -- 2. 必填字段:NOT NULL
    姓名 VARCHAR2(50) NOT NULL,
    部门 VARCHAR2(50) NOT NULL,
    
    -- 3. 有默认值的字段
    入职日期 DATE DEFAULT SYSDATE,
    状态 VARCHAR2(10) DEFAULT '在职',
    
    -- 4. 需要唯一的字段
    身份证号 VARCHAR2(18) NOT NULL,
    工号 VARCHAR2(20) NOT NULL,
    
    -- 5. 有范围限制的字段
    年龄 NUMBER CHECK (年龄 >= 18 AND 年龄 <= 65),
    基本工资 NUMBER(10,2) CHECK (基本工资 > 0),
    
    -- 6. 枚举值字段
    性别 VARCHAR2(2) CHECK (性别 IN ('男', '女')),
    婚姻状况 VARCHAR2(10) CHECK (婚姻状况 IN ('未婚', '已婚', '离异', '丧偶')),
    学历 VARCHAR2(20) CHECK (学历 IN ('高中', '专科', '本科', '硕士', '博士')),
    
    -- 7. 格式要求的字段
    手机号 VARCHAR2(11) CHECK (REGEXP_LIKE(手机号, '^1[3-9]\d{9}$')),
    邮箱 VARCHAR2(100) CHECK (邮箱 LIKE '%@%.%'),
    
    -- 表级约束
    CONSTRAINT pk_员工 PRIMARY KEY (员工编号),
    CONSTRAINT uk_员工_身份证 UNIQUE (身份证号),
    CONSTRAINT uk_员工_工号 UNIQUE (工号),
    
    -- 复杂的业务规则检查
    CONSTRAINT ck_员工_工资学历 CHECK (
        (学历 = '博士' AND 基本工资 >= 15000) OR
        (学历 = '硕士' AND 基本工资 >= 10000) OR
        (学历 = '本科' AND 基本工资 >= 6000) OR
        (学历 IN ('专科', '高中'))
    )
);

案例3:使用序列生成主键

sql 复制代码
-- 创建序列
CREATE SEQUENCE seq_借阅流水号
    START WITH 1        -- 从1开始
    INCREMENT BY 1      -- 每次增加1
    NOCACHE            -- 不缓存
    NOCYCLE;           -- 不循环

-- 创建表
CREATE TABLE 借阅 (
    借阅流水号 NUMBER PRIMARY KEY,
    借书证号 VARCHAR2(10) NOT NULL,
    图书编号 VARCHAR2(10) NOT NULL,
    借书日期 DATE DEFAULT SYSDATE
);

-- 插入数据时使用序列
INSERT INTO 借阅 VALUES (
    seq_借阅流水号.NEXTVAL,    -- 获取序列的下一个值
    '20051001',
    'T001',
    SYSDATE
);

-- Oracle 12c及以上:可以使用IDENTITY列(自增列)
CREATE TABLE 借阅 (
    借阅流水号 NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    借书证号 VARCHAR2(10) NOT NULL,
    图书编号 VARCHAR2(10) NOT NULL,
    借书日期 DATE DEFAULT SYSDATE
);

-- 插入时不需要指定ID
INSERT INTO 借阅 (借书证号, 图书编号) VALUES ('20051001', 'T001');

八、常见问题与解决方案

问题1:约束冲突

sql 复制代码
-- 问题:插入数据时违反CHECK约束
INSERT INTO 学生 VALUES ('2001001', '张三', '未知', 20);
-- ORA-02290: check constraint (USER.CK_学生_性别) violated

-- 解决:
-- 1. 修改数据符合约束
INSERT INTO 学生 VALUES ('2001001', '张三', '男', 20);

-- 2. 或者修改约束
ALTER TABLE 学生 DROP CONSTRAINT ck_学生_性别;
ALTER TABLE 学生 ADD CONSTRAINT ck_学生_性别 
    CHECK (性别 IN ('男', '女', '未知'));

问题2:外键约束导致无法删除

sql 复制代码
-- 问题:删除父表记录时,子表有引用
DELETE FROM 书目 WHERE ISBN = '978-7-01-001';
-- ORA-02292: integrity constraint (USER.FK_图书_书目) violated - child record found

-- 解决方案1:先删除子表记录
DELETE FROM 图书 WHERE ISBN = '978-7-01-001';
DELETE FROM 书目 WHERE ISBN = '978-7-01-001';

-- 解决方案2:使用级联删除
ALTER TABLE 图书 DROP CONSTRAINT fk_图书_书目;
ALTER TABLE 图书 ADD CONSTRAINT fk_图书_书目
    FOREIGN KEY (ISBN) REFERENCES 书目(ISBN) ON DELETE CASCADE;
-- 现在删除书目会自动删除相关图书
DELETE FROM 书目 WHERE ISBN = '978-7-01-001';

问题3:无法修改列的数据类型

sql 复制代码
-- 问题:列中有数据时无法缩小长度
ALTER TABLE 学生 MODIFY (姓名 VARCHAR2(10));
-- ORA-01441: cannot decrease column length because some value is too big

-- 解决:
-- 1. 检查最大长度
SELECT MAX(LENGTH(姓名)) FROM 学生;

-- 2. 确保新长度足够后再修改
ALTER TABLE 学生 MODIFY (姓名 VARCHAR2(50));

-- 3. 或者先清空数据,修改后再导入

问题4:NOT NULL约束冲突

sql 复制代码
-- 问题:现有数据中有NULL值,无法添加NOT NULL约束
ALTER TABLE 学生 MODIFY (联系电话 NOT NULL);
-- ORA-01407: cannot update ("USER"."学生"."联系电话") to NULL

-- 解决:先更新NULL值
UPDATE 学生 SET 联系电话 = '未知' WHERE 联系电话 IS NULL;
COMMIT;
-- 然后再添加约束
ALTER TABLE 学生 MODIFY (联系电话 NOT NULL);

希望这份教程能帮助您全面理解SQL的表设计和约束机制!🎉

相关推荐
员大头硬花生2 小时前
七、InnoDB引擎-架构-后台线程
java·数据库·mysql
Ryan ZX2 小时前
etcd 高可用分布式键值存储
数据库·分布式·etcd
研究司马懿2 小时前
【ETCD】ETCD——confd配置管理
数据库·golang·自动化·运维开发·etcd·argocd·gitops
..Cherry..2 小时前
Etcd详解(raft算法保证强一致性)
数据库·算法·etcd
Tadas-Gao3 小时前
MySQL存储架构解析:从数据无序到索引艺术的演进
数据库·分布式·mysql·微服务·云原生·架构
ANGLAL4 小时前
17.MyBatis动态SQL语法整理
java·sql·mybatis
懒羊羊不懒@4 小时前
【MySQL | 基础】通用语法及SQL分类
数据库·oracle
llxxyy卢4 小时前
基于时间的 SQL 盲注-延时判断和基于布尔的 SQL 盲注
数据库·sql·oracle
Yeats_Liao5 小时前
时序数据库系列(六):物联网监控系统实战
数据库·后端·物联网·时序数据库