资讯动态

SQL Server数据库实验实操指南:从建表约束到查询优化的避坑手册

发布时间:2026/10/13 14:01:08 来源:尧图企业网站定制
简介太原理工大学软件工程专业《数据库概论》课程实验报告适合正在学习数据库原理与SQL实践的高校学生参考。报告基于Microsoft SQL Server 2016完整覆盖交互式SQL语句使用、数据完整性、用户鉴别与数据控制等实验环节包含创建/修改/删除数据表、设置主外键与唯一约束、建立普通/唯一/聚簇索引、创建视图以及数据插入和更新等完整操作过程并配有具体SQL语句和运行结果可直接对照复现数据库管理关键流程。报告按照实验目的、实验平台、主要实验内容及结果进行组织便于按步骤对照练习。压缩包内共1个docx文档1.69MB为完整实验报告文件下载后即可阅读或打印。该资源已有311人学习适合期末复习、课程实验备查以及SQL实操练习尤其对太原理工大学相关课程学生具有直接参考价值。1. 数据库概论实验报告一份能从上机到避坑的 SQL Server 实操地图这份实验报告是我见过的少数「知识点覆盖密度」很高的数据库课程作业。它不跟你讲抽象理论直接把 SQL Server 2016 里建表、加约束、建索引、建视图、增删改查、分组统计、连接查询、嵌套查询、集合运算、完整性验证全部拆成一条条可执行的 SQL连出错时的报错点都保留在文本里。对软件工程专业刚开数据库课的人来说它是很好的语法速查表对准备实验考试的人来说它是一份能照着跑的真题。更难得的是这份报告里留了不少新手必踩的坑——表名拼错、列名混用、外键顺序不对、约束放行 NULL每条我都帮你标出来。2. 建库建表与数据定义主键、外键、CHECK、DEFAULT 的排布与索引视图数据库实验的第一步永远是 DDL而这份报告第一部分的主题就是「创建、修改、删除数据库对象」。很多人觉得建表简单真正动手才发现约束怎么写、外键引用哪个表、索引要不要建 UNIQUE全是细节。这一章把报告中数据定义部分拆开讲。2.1 从 CREATE TABLE 开始主键、外键与 NOT NULL 怎么排布报告中一共出现了两套建表语句第一套在实验二里只带主键第二套在实验三里约束更全。我建议直接看第二套因为它把一张业务表该有的约束都列出来了CREATE TABLE Student( Sno CHAR(8) PRIMARY KEY, Sname CHAR(8) NOT NULL, Ssex CHAR(2) CHECK(Ssex IN (男,女)), Sage SMALLINT, Sdept CHAR(20), Sclass CHAR(4) NOT NULL, Stotal SMALLINT DEFAULT 0 );这段建表语句一次用到了五种约束主键约束PRIMARY KEY、非空约束NOT NULL、检查约束CHECK、默认值约束DEFAULT。先说类型选择Sno CHAR(8)表示学号固定 8 位用定长字符串比用VARCHAR(8)更省存储且语义明确Sage SMALLINT用短整型存年龄范围 -32768 到 32767完全够用Stotal SMALLINT DEFAULT 0的意思是学生总学分默认从 0 开始之后通过 UPDATE 累加。这里有个容易被忽视的点Sname CHAR(8) NOT NULL保证姓名不能为空Sclass CHAR(4) NOT NULL保证班级不能为空但Sage、Sdept没加 NOT NULL说明这两个字段允许 NULL。业务上这是合理的——年龄可能采集不到系别可能后续再补。换句话说建表时每一列要不要允许 NULL本身就是一次数据模型设计决策不是顺手写的。再看选课表 SC它的结构涉及复合主键和两个外键CREATE TABLE SC( Sno CHAR(8), Cno CHAR(4), Grade SMALLINT CONSTRAINT SC_CHECK CHECK(Grade 0 AND Grade 100), PRIMARY KEY (Sno, Cno), CONSTRAINT FK_SC_Sno FOREIGN KEY (Sno) REFERENCES Student(Sno), CONSTRAINT FK_SC_Cno FOREIGN KEY (Cno) REFERENCES Course(Cno) );复合主键(Sno, Cno)表达的是「一个学生选一门课只有一条成绩记录」这比单列主键更贴近业务。两个外键分别指向 Student 和 Course保证选课记录里不能出现不存在的学号或课程号。注意外键创建时我加上了名字FK_SC_Sno和FK_SC_Cno这是强烈推荐的习惯——约束没名字后续 DROP 的时候只能猜系统生成的随机名。建表顺序同样有讲究。必须先建 Student 和 Course再建 SC因为 SC 的外键依赖前两张表如果顺序反了SQL Server 会直接报 引用的表不存在。同理删除表的时候要反过来先删 SC再删 Student 和 Course。2.2 自引用外键Course.Cpno 引用 Course.Cno 的先修课设计课程表里有个容易看懵的设计课程先修课字段Cpno引用的是本表的Cno。这是很典型的自引用外键表达「一门课的先修课是另一门课」。CREATE TABLE Course( Cno CHAR(4) PRIMARY KEY, Cname CHAR(40), Cpno CHAR(4), Ccredit SMALLINT ); ALTER TABLE Course ADD FOREIGN KEY (Cpno) REFERENCES Course(Cno);这里的业务语义是Cpno存放的是本课程的前置课程号。比如课程 1「数据库系统原理」的先修课是课程 5「数据结构」那么课程 1 这一行的 Cpno 就是 5。自引用外键的插入顺序是个隐藏炸弹先修课必须先于引用它的课程插入。以报告中的数据顺序为例课程 1 引用课程 5、课程 4 引用课程 6如果数据库严格校验外键插入顺序就要先插 5、6、7 再插 1、4否则会报外键冲突。课堂环境里 SQL Server 对外键的默认校验行为可能不会立刻暴露问题但生产环境一定会拦你。常见做法是先分析依赖图再决定插入顺序或者先把 Cpno 都置 NULL数据齐了再 UPDATE。2.3 索引的三种形态普通索引、唯一索引与聚簇索引报告里创建索引的语句集中在 3.1 节虽然只有四条但覆盖了索引的三种主要形态CREATE INDEX iCname ON Course(Cname); CREATE UNIQUE INDEX iSname ON Student(Sname); CREATE CLUSTERED INDEX iSnoCno ON SC(Sno, Cno DESC); CREATE UNIQUE INDEX iCno ON Course(Cno);普通索引CREATE INDEX iCname ON Course(Cname)用于加速按课程名查询比如频繁执行WHERE Cname 数据库系统原理。唯一索引CREATE UNIQUE INDEX iSname ON Student(Sname)除了加速查找还强制姓名不能重复——注意如果 Student 表里已经存在两个同名同学这条语句会直接报重复键错误所以唯一索引最好在数据导入前建立。聚簇索引CREATE CLUSTERED INDEX iSnoCno ON SC(Sno, Cno DESC)影响的是数据页的物理存储顺序SC 表的记录会先按 Sno 排序同 Sno 内再按 Cno 降序排列。聚簇索引不是越多越好一张表只能有一个因为物理顺序只有一种。这里有一个容易翻车的现象如果 Student 表的主键Sno默认就是聚簇索引再执行CREATE CLUSTERED INDEX建第二个聚簇索引SQL Server 会拒绝。理论上主键不一定要求聚簇但 SQL Server 默认PRIMARY KEY就是聚簇索引所以实操中创建聚簇索引前先确认表上是否已有。报告里给 SC 建聚簇索引没问题因为 SC 的主键约束会自动建一个唯一索引但不会和聚簇索引冲突。2.4 视图一段被命名的 SELECT不是独立数据副本报告中视图部分最典型的是这一段CREATE VIEW IS_Student AS SELECT Sno, Sname, Sage FROM Student WHERE Sdept IS;执行完这条语句之后IS_Student看起来像一张表但你SELECT * FROM IS_Student查到的是实时从 Student 表过滤出来的数据。视图不占用额外存储Student 表数据一变视图查出来的结果跟着变。很多人第一次学视图会误以为它创建了数据副本其实它只是保存了一条 SQL 定义。视图的价值在于封装复杂查询。比如报告里的另一个视图CREATE VIEW S_G(Sno, Gavg) AS SELECT Sno, AVG(Grade) FROM SC GROUP BY Sno;这个视图把「按学号统计平均成绩」的聚合逻辑固化下来上层应用只要查 S_G不需要每次写 GROUP BY。需要注意聚合视图的列必须显式命名视图列名写在视图名后面的括号里也就是S_G(Sno, Gavg)这一段的含义。生产环境里视图常用于权限控制只暴露需要的列隐藏敏感字段这是数据库安全设计里的常见做法。2.5 ALTER TABLE 修改表结构加列、改类型、加约束实验报告里 3.1 节的「修改表结构及约束」部分正好展示了 ALTER TABLE 的几种用法ALTER TABLE Student ADD Sclass CHAR(4); ALTER TABLE Student ALTER COLUMN Sage SMALLINT; ALTER TABLE Course ADD UNIQUE(Cname); ALTER TABLE SC DROP CONSTRAINT SC_CHECK; ALTER TABLE SC ADD CONSTRAINT SC_CHECK CHECK(Grade 0 AND Grade 100);第一条给 Student 表追加班级列新列默认允许 NULL所以已有数据不受影响。第二条把 Sage 从整型改成 SMALLINT这是缩窄类型的操作如果已有数据超出 SMALLINT 范围会失败。第三条给 Cname 加唯一约束同理如果课程表里已经有重名课程这条语句会直接报错。第四、五条的组合是「先删再建」因为 SQL Server 不直接支持修改 CHECK 约束内容只能 DROP 掉再 ADD。这也是为什么建约束时命名如此重要——SC_CHECK这个名字在这里就是给 DROP 语句用的地址。还有一个细节报告第二套建表语句里 CHECK 写的是Grade 0 AND Grade 100而第五行的 ALTER 改成Grade 0 AND Grade 100这个变化暴露了约束边界问题。成绩 0 分和 100 分到底允不允许课堂实验里可以随便改生产环境的规则要先跟业务确认不要凭感觉定边界。3. 数据操作INSERT、UPDATE、DELETE 的四种形态与临时表后悔药实验二中 3.2 节覆盖了数据操作的三类语句。这段内容初看像流水账细看会发现它几乎把 INSERT、UPDATE、DELETE 的所有常用形态都走了一遍而且埋着两个不细心就会踩的坑。3.1 INSERT 的几种形态VALUES、按列插入与 INSERT...SELECT报告里的插入语句分成两组。第一组是标准的 VALUES 插入INSERT INTO Student VALUES(20100001,李勇,男,20,CS,1001); INSERT INTO Student(Sno, Sname, Ssex, Sclass) VALUES(20100102,张盛,男,1008);第二条只给四个列赋值没出现的 Sdept、Sage、Stotal 会被填默认值或 NULL。这正好演示了列级插入的用法不是每次都要把所有字段写全没写的列走 DEFAULT 或 NULL 逻辑。SQL Server 2008 之后还支持多行 VALUESINSERT INTO Student VALUES (20100002,刘晨,女,19,CS,1001), (20100021,王敏,女,18,MA,1002);一条语句插多行比逐行执行效率高很多这在导入测试数据时很实用。紧接着报告里有一段「把查询结果插入另一张表」的写法CREATE TABLE cs_Student( 学号 CHAR(8), 姓名 CHAR(8), 年龄 SMALLINT ); INSERT INTO cs_Student SELECT Sno, Sname, Sage FROM Student WHERE Sdept CS;这是 INSERT...SELECT 形态目标表 cs_Student 的列顺序要和 SELECT 出来的列一一对应而且目标表必须提前建好。如果目标表不存在还有个更快的写法SELECT ... INTO 新表 FROM ...后面 DELETE 部分会用到。性能上 INSERT...SELECT 是一次性批量写入比逐行 INSERT 快一个数量级大批量导数据时优先考虑。3.2 UPDATE单行更新、全体更新与关联子查询更新报告里的 UPDATE 语句按影响行数可以分成三个层级UPDATE Student SET Sage 22 WHERE Sno 20100001; UPDATE Student SET Sage Sage 1; UPDATE Sc SET Score Score 5 WHERE CS (SELECT Sdept FROM Student WHERE Student.Sno Sc.Sno);第一条是单行更新靠主键定位影响一行。第二条没有 WHERE更新全表把所有学生年龄加一岁。这类语句在生产环境极其危险忘记 WHERE 就是事故。第三条是关联子查询更新右边子查询根据Student.Sno Sc.Sno关联到 SC 表的每一行判断该学生是否属于 CS 系如果是就把成绩加 5 分。注意这里把子查询写在等号左边常量CS右边语法上完全合法但可读性一般我更习惯写成WHERE Student.Sdept CS这种显式关联。这里要专门提醒一个报告里隐藏的坑SC 表在实验二建表时列名叫Score但在查询部分又大量出现Grade实验三里改成了Grade。如果按原报告顺序执行前一半用 Score、后一半用 GradeSQL Server 会在第一次执行 Grade 时报 列名无效。拿到这份报告后第一件事应该是全局搜索替换把列名统一成你实际建表用的那个名字。3.3 DELETE 与 SELECT INTO 临时表误删之前的最后一道后悔药删除部分的安排很有意思。报告先直接删 SC 表数据然后复制出一个临时表 tmpSC在临时表上做各种删除实验SELECT * INTO tmpSC FROM Sc; DELETE FROM tmpSC WHERE Sno 20100001 AND Cno 1; DELETE FROM tmpSC WHERE Sno 20100002; DELETE FROM tmpSC WHERE CS (SELECT Sdept FROM Student WHERE Student.Sno tmpSC.Sno); DELETE FROM tmpSC;SELECT * INTO tmpSC FROM Sc是 SQL Server 特有的建表复制语法一条语句同时完成「建新表」和「把旧表数据复制进去」两个动作。在临时表上做破坏性实验不影响原始数据这个思路本身就是 DELETE 操作的推荐做法。删除顺序也是个常见翻车点。如果直接执行DELETE FROM Student WHERE Sno20100001而 SC 表里还有这个学生的选课记录外键约束会拦下这条语句并报错。正确顺序永远是先删子表引用行再删主表行。报告里的 tmpSC 实验其实就是在规避这个问题。另外报告里有一条看起来执行成功的语句DELETE FROM Student WHERE Sno 20100022;但插入时用的学号是 8 位20100022这里写了 9 位201000022。结果就是 DELETE 影响 0 行数据还在。这是典型的「写错匹配值导致静默失败」没有任何报错但逻辑没生效。以后排查 DELETE 或 UPDATE 不见效时先看影响行数是不是 0如果是多半是匹配条件根本没对上。4. 查询操作单表、聚合、连接、嵌套与集合运算的完整走查实验二的 3.3 节是整份报告里篇幅最长的部分几乎把 SELECT 查询的所有基础语法过了一遍。这一章的练习质量很高值得一段一段跟着查。4.1 单表查询DISTINCT、BETWEEN、LIKE、IS NULL 与排序报告里单表查询的语句很多挑几个容易用错的看。第一个是去重SELECT Sno FROM Sc; SELECT DISTINCT Sno FROM Sc;第一句返回所有选课记录对应的学号7 行里学号有重复第二句加 DISTINCT只返回不重复的学号。注意 DISTINCT 作用的是整行如果 SELECT 后面跟了多个列只要整行不完全相同就不会去重。SELECT Sname, Sdept, Sage FROM Student WHERE Sage BETWEEN 20 AND 23;BETWEEN 20 AND 23在 SQL Server 里是闭区间包含 20 和 23 本身。这个边界容易被忽略写条件时如果业务要求的是开区间就得改成Sage 20 AND Sage 23。SELECT Sname, Sno, Sage FROM Student WHERE Sname LIKE 刘%;LIKE 的%匹配任意长度字符串_匹配单个字符如果想要精确匹配「刘洋」两个字但不确定第二个字可以写LIKE 刘_。注意 LIKE 后的模式串要加引号忘了加引号会直接语法报错。SELECT Sno, Cno FROM Sc WHERE Score IS NULL;空值判断必须用IS NULL不能用 NULL。SQL 里 NULL 和任何值做比较的结果都是 UNKNOWN用等号判断永远查不出数据。这一点新手踩坑率极高。排序语句SELECT * FROM Student ORDER BY Sdept, Sage DESC的含义是先按 Sdept 升序同一系内按年龄降序。DESC只作用于紧挨着它的 SageSdept 默认升序。如果想两个字段都降序不要写成ORDER BY Sdept, Sage DESC而是要写成ORDER BY Sdept DESC, Sage DESC。4.2 聚合与 GROUP BY聚合函数忽略 NULL 的边界条件报告里的分组统计部分有一组经典练习SELECT COUNT(*) FROM Student; SELECT COUNT(DISTINCT Sno) FROM Sc; SELECT MAX(Score) FROM Sc; SELECT Cno, COUNT(*), AVG(Score), MAX(Score) FROM Sc GROUP BY Cno;COUNT(*)统计全部行数不管列是否 NULLCOUNT(Column)只统计该列非 NULL 的行数AVG(Score)计算平均分时会自动跳过 NULL 成绩。所以一个班如果有三个缺考的学生AVG(Score)算出来的平均分是三门有效成绩的平均不是按全员分母算的。要不要把缺考当 0 分计取决于业务口径但 SQL 默认行为是忽略 NULL。GROUP BY 之后的 HAVING 是分组后的过滤SELECT Cno, COUNT(*), AVG(Score), MAX(Score) FROM Sc GROUP BY Cno HAVING AVG(Score) 90;WHERE 在分组之前过滤行HAVING 在分组之后过滤组。选课人数不足、均分不达标的课程组用 HAVING 处理。还有一个规则SELECT 后面出现的非聚合列必须出现在 GROUP BY 里否则 SQL Server 会报错或返回不确定结果。报告里有一条统计混用了列名AVG(grade)和MAX(grade)如果表里实际列叫 Score这条语句会报列名无效。我在前面已经提醒过列名统一的重要性这里不再重复但建议跑这段脚本前先花一分钟确认每一处列名。4.3 连接查询内连接、左外连接与三表连接的区别报告里同一道题用了两种写法正好可以对比。第一种是传统 WHERE 连接SELECT Student.Sno, Sname, Cno, Grade FROM Student, SC WHERE Student.Sno SC.Sno;第二种是 ANSI JOIN 写法SELECT Student.Sno, Sname, Cno, Grade FROM Student JOIN SC ON (Student.Sno SC.Sno);两种写法结果一样都是内连接只返回两边都匹配的行。没有选课的学生不会出现在结果里。在 FROM 里声明连接条件是现在更推荐的方式因为连接条件和过滤条件分得更清楚。左外连接是重点SELECT Student.Sno, Sname, Cno, Grade FROM Student LEFT OUTER JOIN SC ON (Student.Sno SC.Sno);这条语句返回所有学生没有选课的学生也会出现对应 SC 的字段显示为 NULL。LEFT OUTER JOIN 的含义是「左侧表的所有行都保留右侧表只取匹配行」。这在统计「哪些学生没选课」时特别好用配合WHERE SC.Sno IS NULL就能筛出来。三表连接需要两个连接条件SELECT Student.Sno, Sname, Cname, Grade FROM Student, SC, Course WHERE Student.Sno SC.Sno AND SC.Cno Course.Cno;表越多越容易忘条件。三个表连接至少需要两个连接条件四个表至少三个。少写一个条件结果集就会变成笛卡尔积行数暴涨。遇到多表查询结果莫名其妙变多时先检查连接条件有没有漏。4.4 嵌套查询与集合运算IN、ALL、EXISTS 的适用场景嵌套查询部分是 SQL 里思维跳跃最明显的地方。报告里从简单到复杂给了四类。IN 引出的子查询最简单SELECT Sno, Sname, Sdept FROM Student WHERE Sdept IN (SELECT Sdept FROM Student WHERE Sname 刘晨);子查询先找出刘晨所在系外层再找同一系的学生。注意这里子查询结果集是「多个可能值」所以外层用 IN 而不是。相关子查询用到了表别名SELECT Sno, Cno FROM SC x WHERE Grade (SELECT AVG(Grade) FROM SC y WHERE y.Sno x.Sno);这条查询找出每个学生超过自己平均成绩的选课记录。x 和 y 是同一张表的两个实例内层子查询通过y.Sno x.Sno关联外层当前行。这就是相关子查询的核心——子查询每执行一次用的都是外层当前行的值。这类查询的执行方式和 IN 子查询不同逻辑上更像个循环数据量大时要留意性能。ALL 的语义是「比子查询所有值都……」比如SELECT Sname, Sage FROM Student WHERE Sage ALL (SELECT Sage FROM Student WHERE Sdept CS) AND Sdept CS;含义是非 CS 系中年龄比 CS 系所有学生都小的学生。 ALL等价于「小于子查询结果最小值」 ANY等价于「小于最大值」。这两个修饰符在特定需求下好用但可读性一般实际开发中我更常写成聚合子查询Sage (SELECT MIN(Sage) FROM Student WHERE Sdept CS)语义更直白。EXISTS 处理的是「存在性判断」SELECT Sname FROM Student WHERE EXISTS (SELECT * FROM SC WHERE Sno Student.Sno AND Cno 1);EXISTS 只关心子查询有没有返回行不关心返回什么内容所以子查询写SELECT *或SELECT 1都行。它也是表达「所有」「不存在」语义的基础工具比如「找出没选修任何课程的学生」EXISTS 配合 NOT 就能做。集合运算部分 SQL Server 的关键字和别的数据库略有差异SELECT * FROM Student WHERE Sdept CS UNION SELECT * FROM Student WHERE Sage 19; SELECT * FROM Student WHERE Sdept CS EXCEPT SELECT * FROM Student WHERE Sage 19;UNION 去重合并UNION ALL 保留重复INTERSECT 取交集EXCEPT 取差集意思是「属于第一个集合但不属于第二个集合」。注意 SQL Server 用 EXCEPT而 Oracle 里同样的语义是 MINUS。跨数据库迁移时这个关键字要记得改。4.5 执行顺序与常见的低级错误SQL 语句的逻辑执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY很多人会误以为 SELECT 先执行其实 SELECT 的列别名在 WHERE 阶段根本不可见。这也解释了为什么WHERE Sage 20可以但WHERE 出生年份 2000不行——后者如果「出生年份」是 SELECT 里定义的别名WHERE 阶段还没有这个列。报告里还有一条隐性问题有一句查询把表名写成了Sudent。SQL Server 会立刻报 对象名无效但实际上只需要把表名改回Student就能通过。这类错误不是逻辑问题纯靠细心。拿到一个陌生环境的数据库先SELECT TOP 10 * FROM 表名确认表和列真实存在再写完整查询能省不少排错时间。5. 避坑手册五个真实数据库实验翻车现场这一章的五个问题全部来自实际跑这份报告时的常见失败场景。每条都按「现象 → 原因 → 解决」整理可以当速查表用。5.1 建表时末尾多一个逗号SQL Server 直接语法报错现象执行实验二里 Student 表的建表语句SQL Server 报Incorrect syntax near )。原因原始语句末尾是Sdept char(20), )最后一个列定义后面多了一个英文逗号。SQL Server 不允许列清单以逗号结尾。解决删掉最后一个列名后面的逗号改成Sdept char(20))。以后写 CREATE TABLE 时所有逗号都只放在列与列之间最后一列后不写逗号。5.2 学号多打一位DELETE 静默返回 0 行现象执行DELETE FROM Student WHERE Sno201000022结果显示(0 rows affected)但这条学生记录明明存在。原因表里插入的学号是 8 位20100022DELETE 条件里写成了 9 位的201000022匹配不到任何记录。SQL Server 不会报错只是不干活。解决执行 DELETE 或 UPDATE 之后养成看影响行数的习惯。影响行数是 0 不代表执行失败而是代表条件没有命中。先SELECT Sno FROM Student WHERE Sno 201000022确认一下匹配值是否正确再决定要不要执行。5.3 外键拦截删除先删子表再删主表现象直接执行DELETE FROM Course WHERE Cno1SQL Server 报DELETE statement conflicted with the REFERENCE constraint FK_SC_Cno。原因SC 表通过外键引用了 Course(Cno)课程 1 还有学生在选外键约束不允许直接删除被引用的父表记录。解决先删除 SC 表中该课程对应的选课记录再删除 Course 中的课程记录。如果有临时备份表也可以先在备份表上练习删除逻辑确认无误后再对正式表操作。5.4 主键重复与 UNIQUE 冲突分清 2627 和 2601现象重复执行INSERT INTO Student VALUES(20100001, ...)SQL Server 报Violation of PRIMARY KEY constraint给 Course 表加 UNIQUE(Cname) 时如果已有重复课程名报Violation of UNIQUE KEY constraint。原因重复插入主键值会触发错误码 2627重复插入唯一索引值触发错误码 2601。两类错误本质相同插入的数据和已有约束冲突。解决插入前先查重或者用IF NOT EXISTS包一层。如果错误码是 2601还要检查是不是唯一索引建立在已有重复数据的列上这种情况下要先清理重复数据再建唯一约束。5.5 CHECK 约束放行 NULL三值逻辑下的漏网之鱼现象INSERT INTO SC VALUES(20100003,1,NULL)执行成功没有触发Grade 0 AND Grade 100的 CHECK 约束。原因SQL 里 NULL 参与的判断结果是 UNKNOWN不是 FALSE。CHECK 约束只拦截结果为 FALSE 的情况UNKNOWN 会被放行。解决如果业务要求缺考成绩也不能为空要把列改成Grade SMALLINT NOT NULL。CHECK 约束管范围NOT NULL 管是否允许空两个约束缺一不可。6. 进阶验证把实验脚本变成一份可复用的 DDL/DML 自检清单这份报告最有价值的地方是它完整覆盖了从建库到约束验证的全流程但直接按原顺序跑会因为列名混用、表名拼写等问题中断。我一般会把实验脚本按模块重排成五段每段一个 GO放在同一个 .sql 文件里作为新环境自检清单模块顺序脚本内容验证点第一段CREATE TABLE 三张表和索引视图表结构、约束、对象是否存在第二段INSERT 全部样例数据主键唯一、外键引用、CHECK 生效第三段SELECT 查询单表、聚合、连接、嵌套、集合结果行数与逻辑预期一致第四段UPDATE / DELETE 实验影响行数、外键拦截行为第五段违规插入与约束阻断实验错误码 2627、2601、外键冲突整理时把每段开头用注释写清楚预期结果比如「预期 4 行DISTINCT 后 3 行」。这样每次拿到新库把脚本从头跑一遍数据库基础功能有没有问题一目了然。另外报告里提到数据完整性但没有展开触发器。如果想验证 CHECK 约束之外的强制逻辑可以补一段最简单的 AFTER INSERT 触发器CREATE TRIGGER trg_sc_grade ON SC AFTER INSERT, UPDATE AS BEGIN IF EXISTS(SELECT 1 FROM inserted WHERE Grade 100 OR Grade 0) BEGIN RAISERROR(成绩超出合法范围, 16, 1); ROLLBACK TRANSACTION; END END这段触发器在每次插入或更新 SC 表之后检查inserted逻辑表里的新数据发现成绩越界就报错并回滚事务。inserted表是 DML 触发器中自动生成的临时表保存受影响行的新版本数据。如果有更新操作另一个逻辑表deleted保存旧版本行。触发器和 CHECK 约束的区别在于CHECK 只能做简单的行内范围判断触发器可以写更复杂的业务规则比如根据其他表的状态决定是否允许操作。从那以后我每次接触一个新数据库都会先花十分钟把约束关系和外键依赖理清楚再把这份自检脚本从头跑一遍而不是上来就写业务查询。这个习惯救过我好几次——有一次差点在带外键的表上直接 DROP就是靠脚本里的删除顺序提醒拦下来的。这份实验报告的完整脚本按上述模块重排后在 SQL Server 2016 的 SSMS 里建一个空库逐段执行即可复现所有结果适合拿去做实验对照和考前复习希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑