ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数据库实验四:T-SQL主键、唯一约束与外键参照完整性实战

数据库实验四:T-SQL主键、唯一约束与外键参照完整性实战 简介这份数据库实验四文档面向高校数据库课程学习者聚焦T-SQL语句在完整性约束中的实际应用帮助读者掌握主键、唯一约束、引用完整性与级联引用的操作与验证方法。资源包内含1个docx文件大小约711KB以实验报告形式呈现便于直接对照练习与复盘。内容覆盖创建与删除联合主键、删除dept表部门名称列的唯一约束、测试主表与从表增删改对参照完整性的影响以及级联更新失败后的排错思路并附有思考题与课外任务分析。目前已有343人学习下载适合正在完成数据库实验、需要理解约束冲突原因与数据一致性保障手段的学生参考也可作为实验报告撰写与知识点梳理的辅助材料。1. 数据库实验四到底在练什么从一份 docx 到能跑通的约束体系很多人看到「数据库实验四.docx」这个文件名第一反应是又一份照着截图敲 SQL 的作业。但我带过几届学生做这个实验后发现它真正想让你掌握的是用 T-SQL 把主键、唯一约束、外键和参照完整性串成一套能自洽的数据规则。换句话说实验四不是让你背语法而是让你体会「约束写错一个整张表的插入顺序全乱」这种连锁反应。热搜里频繁出现的「主键索引」「navicat 怎么设置唯一约束」「citus 分布式表不能有主键」本质上都是同一件事在不同场景下的变体——约束不是装饰它决定了数据能不能进、以什么顺序进、删的时候会不会被拦住。这篇笔记就按「先建表定约束 → 再插数据验证 → 最后排错」的路径把实验四从一份 docx 变成你本地能复现的完整流程。适合正在做实验但卡在外键报错、或者想搞清楚约束底层逻辑的从业者。2. 建表阶段主键、唯一约束、外键的 T-SQL 写法与选型2.1 主键和唯一约束到底差在哪什么时候该用哪个实验四里最常见的两张表是「学生表」和「选课表」学生表用学号做主键选课表用学号加课程号做复合主键。这里第一个容易翻车的点就是主键和唯一约束都能保证列值不重复但主键不允许 NULL唯一约束允许一个 NULL。T-SQL 里唯一约束实际上是通过唯一索引实现的而主键默认创建聚集索引除非你显式指定 NONCLUSTERED。选型上我的习惯是能唯一标识一行的自然键优先做主键比如学号、订单号如果业务上需要多个列组合才能唯一就用复合主键如果只是「这个字段不允许重复但可以空」那就用唯一约束而不是主键。热搜里「主键索引」这个词其实点出了一个事实——主键本身就是一种索引你不需要再给主键列单独建索引重复建只会浪费写入性能。下面是一段可以直接在 SSMS 或 Azure Data Studio 里跑的最小建表脚本-- 学生表学号做主键姓名不允许为空 CREATE TABLE Students ( StudentID CHAR(10) NOT NULL, Name NVARCHAR(20) NOT NULL, Email VARCHAR(50) NULL, CONSTRAINT PK_Students PRIMARY KEY (StudentID), CONSTRAINT UQ_Students_Email UNIQUE (Email) -- 邮箱唯一但允许为空 ); -- 课程表课程号做主键 CREATE TABLE Courses ( CourseID CHAR(6) NOT NULL, CourseName NVARCHAR(30) NOT NULL, Credit TINYINT NOT NULL, CONSTRAINT PK_Courses PRIMARY KEY (CourseID) ); -- 选课表学号课程号复合主键两个外键分别指向学生表和课程表 CREATE TABLE Enrollments ( StudentID CHAR(10) NOT NULL, CourseID CHAR(6) NOT NULL, Score DECIMAL(5,2) NULL, EnrollDate DATE NOT NULL DEFAULT GETDATE(), CONSTRAINT PK_Enrollments PRIMARY KEY (StudentID, CourseID), CONSTRAINT FK_Enroll_Student FOREIGN KEY (StudentID) REFERENCES Students(StudentID) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT FK_Enroll_Course FOREIGN KEY (CourseID) REFERENCES Courses(CourseID) ON DELETE NO ACTION ON UPDATE CASCADE );逻辑说明Students 表用 CHAR(10) 存学号固定长度比 VARCHAR 在索引上更紧凑Email 列加了唯一约束但允许 NULL因为不是每个学生都有邮箱。Enrollments 表用两列复合主键天然防止同一个学生重复选同一门课。两个外键的删除行为故意设成不一样——学生删除时选课记录跟着删CASCADE课程删除时如果还有人选就拦住NO ACTION这是为了让你在实验里能观察到不同参照完整性动作的差异。参数说明ON DELETE CASCADE 表示主表删一行子表对应行自动删ON UPDATE CASCADE 表示主键值改了子表跟着改NO ACTION 表示有引用就报错。注意 T-SQL 里 NO ACTION 和 RESTRICT 行为基本一致但 NO ACTION 允许在事务里延迟检查。2.2 外键的参照完整性插入顺序和删除顺序为什么总报错参照完整性说白了就是子表里的外键值必须在主表里存在。这带来两个硬性顺序——插入时先插主表再插子表删除时先删子表再删主表除非你配了 CASCADE。实验四里十有八九的报错都是这个顺序搞反了。我一般会按这个顺序插数据-- 第一步先插主表 Students 和 Courses INSERT INTO Students (StudentID, Name, Email) VALUES (2024000001, N张三, zhangsanexample.com), (2024000002, N李四, NULL), (2024000003, N王五, wangwuexample.com); INSERT INTO Courses (CourseID, CourseName, Credit) VALUES (CS101, N数据库原理, 4), (CS102, N操作系统, 3), (MA201, N离散数学, 3); -- 第二步再插子表 Enrollments外键值必须已存在 INSERT INTO Enrollments (StudentID, CourseID, Score) VALUES (2024000001, CS101, 88.5), (2024000001, CS102, 76.0), (2024000002, CS101, 92.0), (2024000003, MA201, 81.5);逻辑说明先插主表保证外键有引用目标再插子表。如果你反过来先插 EnrollmentsSQL Server 会直接抛「INSERT 语句与 FOREIGN KEY 约束冲突」错误号 547。这个错误号记住实验里出现频率最高。参数说明Score 用 DECIMAL(5,2) 能存到 999.99够用且不浪费EnrollDate 有默认值 GETDATE()插入时不写也会自动填当天。验证约束是否生效可以故意插一条不存在的外键-- 这行会报错课程号 CS999 在 Courses 表里不存在 INSERT INTO Enrollments (StudentID, CourseID, Score) VALUES (2024000001, CS999, 60);报错信息里会明确写出冲突的约束名 FK_Enroll_Course这就是你排查时的抓手。3. 验证与排错约束冲突的定位方法和常见报错3.1 用系统视图查约束定义比翻 docx 快得多实验做到一半忘了某个外键叫什么名字、引用了哪张表不用回去翻文档。T-SQL 提供了几个系统视图可以直接查-- 查某张表上所有约束的名称、类型和定义 SELECT t.name AS TableName, c.name AS ConstraintName, c.type_desc AS ConstraintType, c.definition AS ConstraintDef FROM sys.check_constraints c JOIN sys.tables t ON c.parent_object_id t.object_id WHERE t.name Enrollments UNION ALL SELECT t.name, fk.name, FOREIGN KEY, OBJECT_NAME(fk.referenced_object_id) FROM sys.foreign_keys fk JOIN sys.tables t ON fk.parent_object_id t.object_id WHERE t.name Enrollments;逻辑说明sys.foreign_keys 存外键元数据referenced_object_id 指向主表sys.check_constraints 存 CHECK 约束。用 UNION ALL 把两类拼一起看。这个查询在实验报告里也能直接当「约束清单」用。参数说明OBJECT_NAME() 函数把 object_id 转成表名比手动 join sys.tables 更省事。3.2 唯一约束和主键冲突的报错区别这两种冲突报错信息很像但错误号不同主键冲突是 2627唯一约束冲突是 2601。知道这个区别排错时能直接定位是哪个约束被触发。-- 主键冲突学号重复报错 2627 INSERT INTO Students (StudentID, Name) VALUES (2024000001, N赵六); -- 唯一约束冲突邮箱重复报错 2601 INSERT INTO Students (StudentID, Name, Email) VALUES (2024000004, N赵六, zhangsanexample.com);逻辑说明2627 对应 PRIMARY KEY 或 UNIQUE 约束的违反2601 对应唯一索引的违反。实际用的时候两者经常混着出现但看错误号能快速区分。参数说明如果想在插入前先判断是否存在可以用 IF NOT EXISTS 包一层避免直接报错中断批处理。4. 避坑与排查实验四里最容易翻车的 5 个点4.1 现象外键插入报错 547但主表明明有这条数据原因最常见的是字符类型不匹配。主表 StudentID 是 CHAR(10)子表如果写成 VARCHAR(10)值看起来一样但比较时可能因为尾随空格或排序规则不同导致匹配失败。另一个原因是主表数据在未提交的事务里子表看不到。解决用SELECT SQL_VARIANT_PROPERTY(StudentID, BaseType) FROM Students确认两边类型一致检查是否在同一个事务里必要时先 COMMIT。4.2 现象删除主表数据时报错明明配了 CASCADE原因一个子表配了 CASCADE另一个子表配了 NO ACTION删除主表行时 NO ACTION 那个先拦住。或者外键创建时没写 ON DELETE CASCADE默认就是 NO ACTION。解决用 3.1 的查询确认每个外键的删除行为把需要级联的都显式写上 ON DELETE CASCADE。注意多个级联路径可能触发「可能导致循环或多重级联路径」错误这时要改成触发器处理。4.3 现象唯一约束允许插入多个 NULL和预期不符原因SQL Server 的唯一约束把 NULL 当作未知值多个 NULL 不违反唯一性。这是 SQL 标准行为不是 bug。解决如果业务要求 NULL 也只能有一个用筛选唯一索引CREATE UNIQUE INDEX UQ_Email ON Students(Email) WHERE Email IS NOT NULL再配合 CHECK 约束限制。或者干脆把列设为 NOT NULL。4.4 现象navicat 里设置唯一约束后插入还是重复成功原因navicat 的可视化界面有时只改了表结构没真正提交或者你改的是设计视图但没点保存。另一种情况是表里已有重复数据加唯一约束时被静默跳过。解决在 navicat 里改完结构后用EXEC sp_help Students确认约束真的存在如果有重复数据先清理再建约束。4.5 现象复合主键的列顺序影响查询性能原因复合主键 (StudentID, CourseID) 的索引先按 StudentID 排序再按 CourseID 排序。如果查询条件只有 CourseID索引用不上会全表扫。解决如果经常按 CourseID 单独查给 CourseID 单独建一个非聚集索引。实验里数据量小看不出来但这是实际项目里必须考虑的。5. 进阶技巧用 T-SQL 动态生成约束脚本与验证清单实验做完不是终点能把这套约束体系导出成可复现的脚本才算真正掌握。我一般用这段 T-SQL 自动生成所有外键的创建语句方便迁移到另一台机器-- 动态生成当前库所有外键的创建脚本 SELECT ALTER TABLE QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id)) . QUOTENAME(OBJECT_NAME(fk.parent_object_id)) ADD CONSTRAINT QUOTENAME(fk.name) FOREIGN KEY ( QUOTENAME(COL_NAME(fkc.parent_object_id, fkc.parent_column_id)) ) REFERENCES QUOTENAME(OBJECT_SCHEMA_NAME(fk.referenced_object_id)) . QUOTENAME(OBJECT_NAME(fk.referenced_object_id)) ( QUOTENAME(COL_NAME(fkc.referenced_object_id, fkc.referenced_column_id)) ) CASE fk.delete_referential_action WHEN 1 THEN ON DELETE CASCADE WHEN 2 THEN ON DELETE SET NULL WHEN 3 THEN ON DELETE SET DEFAULT ELSE ON DELETE NO ACTION END ; AS CreateScript FROM sys.foreign_keys fk JOIN sys.foreign_key_columns fkc ON fk.object_id fkc.constraint_object_id;逻辑说明这段查询把系统视图里的外键元数据拼成可执行的 ALTER TABLE 语句。delete_referential_action 的枚举值 1/2/3 分别对应 CASCADE/SET NULL/SET DEFAULT0 是 NO ACTION。跑出来的结果直接复制就能在另一个库重建约束。参数说明QUOTENAME 处理带空格或特殊字符的标识符避免脚本报错。如果外键是多列的这段会为每列生成一行需要再按约束名分组拼接实验里单列外键够用。验证约束是否全部生效我习惯跑一个「约束体检」查询检查项查询视图预期结果主键是否存在sys.key_constraints typePK每表恰好 1 个外键引用是否有效sys.foreign_keys is_disabled0全部为 0唯一约束是否可信sys.key_constraints is_system_named0自定义命名索引碎片sys.dm_db_index_physical_stats实验数据量下可忽略最后说个血泪经验实验四的 docx 里如果给了截图别照着截图里的列名一字不差地敲先看清楚类型和长度。我见过太多人因为把 NVARCHAR(20) 写成 VARCHAR(20)插入中文变成问号然后花半小时找原因。约束这东西建的时候多花五分钟核对比事后排错省一小时。希望帮到你。本文还有配套的精品资源点击获取
返回列表