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的表设计和约束机制!🎉

相关推荐
科技小花25 分钟前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸26 分钟前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain28 分钟前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希1 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神1 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员1 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java1 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿2 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴2 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU2 小时前
三大范式和E-R图
数据库