本文档详细讲解表的创建、修改以及各种完整性约束的使用。
目录
[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 查询约束信息)
[问题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的表设计和约束机制!🎉