资讯动态

数据库系统概论第3章例题代码SQL Server实现与避坑指南

发布时间:2026/10/9 10:16:20 来源:尧图企业网站定制
简介这份文档面向正在学习《数据库系统概论》的高校学生与数据库入门者聚焦教材第3章数据定义相关例题的代码实现帮助读者把课堂上的SQL理论落到可运行的语句上。包内仅含1个doc文件约1.49MB集中收录了学生表student、课程表course与成绩表sc的建表语句以及向三张表插入记录、添加与修改字段、创建和删除索引等操作的完整代码。内容覆盖列级与表级完整性约束、主码与外码约束、CHECK检查约束、DEFAULT默认值约束还包含ALTER TABLE修改表结构、DROP TABLE与DROP INDEX等语句并针对SQL Server环境给出了datetime类型、聚集索引唯一性、外码依赖导致删表失败等实际报错与调整思路。已有241人学习适合对照教材例题逐条验证、理解约束机制与常见语法差异也可作为课后实验与复习的参考素材。1. 从一份 .doc 说起为什么第 3 章的例题代码值得单独拆一遍很多人学《数据库系统概论》时第 3 章是第一个真正意义上的分水岭。前面讲关系模型、关系代数还能靠纸笔推一推到了第 3 章SQL 的 DDL 和 DML 一股脑砸下来CREATE TABLE、ALTER TABLE、CREATE INDEX、INSERT、SELECT 全挤在一起书上例题看着都懂一上机就报错。这份《数据库系统概论》第 3 章所有例题实现代码.doc干的就是把书上那些例题从纸面搬到 SQL Server 里跑通并且把跑不通的地方标出来、改掉、写清楚为什么。它覆盖的是数据定义建表、约束、外码、数据插入、修改表结构、索引的创建与删除以及数据查询里那些容易翻车的细节比如 LIKE 里的下划线匹配、NULL 在排序里的位置。适合谁正在跟这门课做实验的在校生、准备数据库相关考试要动手验证的人以及工作里需要快速回顾 SQL Server 语法边界的人。它不是一份“标准答案”更像一份带批注的上机记录里面那些“改为”“提示”“OK”才是真正值钱的部分。2. 建表与约束三个母表的 DDL 逐句拆解2.1 student、course、sc 三张表的字段设计与约束选型先看 student 表。sno 用 CHAR(9) 做主码列级约束直接写在字段后面这是最省事的写法。sname 是 CHAR(20) 加 not null注意原文注释里写“要求 sname 属性的值唯一”但代码里并没有加 UNIQUE这是一个典型的注释与代码不一致实际建表时如果真按唯一来要求得自己补上 UNIQUE 或者建唯一索引。ssex 用 CHAR(2)DEFAULT 男 加 CHECK(ssex IN (男,女))默认值和检查约束叠在一起插入时如果不给 ssex自动填男给了别的值直接拒绝。sage 用 SMALLINT 加 CHECK(sage15 AND sage45)把年龄卡在一个合理区间。sdept 是 CHAR(20)没加约束。course 表里 cno 是 CHAR(4)cname 是 CHAR(40)cpno 是 CHAR(4) 用来存先修课号ccredit 是 SMALLINT 存学分。主码用表级约束 PRIMARY KEY(cno) 写在最后和 student 的列级写法形成对照书上是故意让你看到两种写法都合法。sc 表是重点。sno 和 cno 都是 CHARgrade 是 SMALLINTCHECK 写成 (grade IS NULL) OR (grade BETWEEN 0 AND 100)这个 OR 很关键因为成绩允许为空比如刚选课还没考试如果不写 IS NULL空值插入会被 CHECK 拦下来。主码是 PRIMARY KEY(sno,cno)两个外码分别 REFERENCES student(sno) 和 course(cno)都是表级约束。这里有一个执行顺序的坑sc 表引用了 student 和 course所以建表顺序必须是先 student、再 course、最后 sc反过来一定报外码引用失败。-- 建表顺序不能乱被引用的表先建 CREATE TABLE student( sno CHAR(9) PRIMARY KEY, sname CHAR(20) NOT NULL, ssex CHAR(2) DEFAULT 男 CHECK(ssex IN (男,女)), sage SMALLINT CHECK(sage15 AND sage45), sdept CHAR(20) ); CREATE TABLE course( cno CHAR(4), cname CHAR(40), cpno CHAR(4), ccredit SMALLINT, PRIMARY KEY(cno) ); CREATE TABLE sc( sno CHAR(9), cno CHAR(4), grade SMALLINT CHECK((grade IS NULL) OR (grade BETWEEN 0 AND 100)), PRIMARY KEY(sno,cno), FOREIGN KEY(sno) REFERENCES student(sno), FOREIGN KEY(cno) REFERENCES course(cno) );参数说明CHAR 是定长写 CHAR(9) 就固定占 9 个字符不够补空格所以 sno 存 200215121 刚好 9 位。SMALLINT 范围是 -32768 到 32767存年龄和成绩足够。CHECK 约束在插入和更新时都会触发不是只检查一次。外码约束要求被引用列必须是主码或 UNIQUEstudent.sno 和 course.cno 都满足。2.2 插入数据时的顺序与空值处理插入顺序同样受外码约束限制。student 和 course 先插sc 最后插。原文里 student 插了四条course 插了七条sc 插了五条。注意 course 表里 cpno 有 null比如“数学”的先修课为空这是合法的因为 cpno 没有 not null 约束。sc 表里 grade 都是具体数字没有 null但 CHECK 里已经预留了 null 的通道。INSERT INTO student VALUES(200215121,李勇,男,20,CS); INSERT INTO student VALUES(200215122,刘晨,女,19,CS); INSERT INTO student VALUES(200215123,王敏,女,18,MA); INSERT INTO student VALUES(200215125,张立,男,19,IS); INSERT INTO course VALUES(1,数据库,5,4); INSERT INTO course VALUES(2,数学,null,2); INSERT INTO course VALUES(3,信息系统,1,4); INSERT INTO course VALUES(4,操作系统,6,3); INSERT INTO course VALUES(5,数据结构,7,4); INSERT INTO course VALUES(6,数据处理,null,2); INSERT INTO course VALUES(7,PASCAL 语言,6,4); INSERT INTO sc VALUES(200215121,1,92); INSERT INTO sc VALUES(200215121,2,85); INSERT INTO sc VALUES(200215121,3,88); INSERT INTO sc VALUES(200215122,2,90); INSERT INTO sc VALUES(200215122,3,80);这里有个细节原文里 INSERT 语句末尾没有分号在 SQL Server 的查询窗口里单条执行没问题但批量执行时最好补上分号否则解析器可能把下一条语句粘上来。另外 course 表插入 PASCAL 语言 时字符串里有空格CHAR(40) 会自动补空格到 40 位查询时用等号比较可能因为尾部空格出问题这是 CHAR 类型的经典坑后面排查章节会细说。3. 改表与索引ALTER 和 CREATE INDEX 的报错与修正3.1 ALTER TABLE 加列、改类型时约束依赖怎么处理原文例 8 是给 student 加一列 s_entrance书上写的是 datet明显是 datetime 的笔误改成 datetime 后执行成功。例 9 是把 sage 的类型从 SMALLINT 改成 int直接执行报错对象 CK__student__sage__1CF15040 依赖于列 sageALTER TABLE ALTER COLUMN sage 失败。原因很清楚sage 上挂着 CHECK 约束改类型之前必须先把这个约束删掉。原文的批注写“看来得先删除 sage 字段的约束再做就对了”这就是血泪经验。-- 例 8加列注意 datetime 拼写 ALTER TABLE student ADD s_entrance datetime; -- 例 9直接改类型会失败因为 CHECK 约束依赖 sage -- 先查出约束名实际名字可能不同用系统视图查 SELECT name FROM sys.check_constraints WHERE parent_column_id COLUMNPROPERTY(OBJECT_ID(student),sage,ColumnId); -- 删约束名字按上一步查出来的填 ALTER TABLE student DROP CONSTRAINT CK__student__sage__1CF15040; -- 再改类型 ALTER TABLE student ALTER COLUMN sage int; -- 如果还需要 CHECK重新加上 ALTER TABLE student ADD CONSTRAINT CK_student_sage CHECK(sage15 AND sage45);参数说明sys.check_constraints 是 SQL Server 的系统视图parent_column_id 和 COLUMNPROPERTY 配合能定位到具体列上的检查约束。约束名是系统自动生成的不同机器上可能不一样所以不要硬编码先查再删。改完类型后如果业务还需要年龄范围限制记得把 CHECK 补回去否则约束就永久丢了。例 10 是给 course 的 cname 加 UNIQUE 约束ALTER TABLE course ADD UNIQUE(cname)执行 OK。这个操作会隐式创建一个唯一索引如果表里已有重复的 cname加约束会失败所以加之前最好先查一下有没有重复值。3.2 索引的创建与删除聚簇与非聚簇的边界例 13 是 create cluster index stusname on student(sname)书上拼写有误正确的是 clustered。执行时报错不能在表 student 上创建多个聚集索引。原因是 student 表上已经有主码主码默认就是聚集索引一个表只能有一个聚集索引。原文的提示写得很清楚要么先删掉现有的聚集索引要么改用非聚集索引。-- 例 13聚集索引一个表只能有一个 -- 如果 student 已有主码聚集索引这句会失败 CREATE CLUSTERED INDEX stusname ON student(sname); -- 改成非聚集索引就能过 CREATE INDEX stusname ON student(sname); -- 例 14唯一索引 CREATE UNIQUE INDEX stusno ON student(sno); CREATE UNIQUE INDEX coucno ON course(cno); CREATE UNIQUE INDEX scno ON sc(sno ASC, cno DESC); -- 例 15删除索引必须带表名 DROP INDEX student.stusname; -- 如果索引不存在会报错先确认 DROP INDEX student.stusno;参数说明CREATE INDEX 的语法是 create [unique] [clustered|nonclustered] index 索引名 on 表名(列名 [ASC|DESC], ...)。不写 clustered 或 nonclustered 默认是非聚集。唯一索引要求列值不重复sc 表的 scno 建在 (sno ASC, cno DESC) 上组合唯一ASC 和 DESC 只影响索引内部排序不影响唯一性判断。DROP INDEX 必须写成 表名.索引名不能只写索引名这是 SQL Server 和某些其他数据库的差异点。原文例 15 里先删 stusname 报“在系统目录中不存在”因为前面创建聚集索引失败了根本没建成后来改删 stusno 才成功。这个连锁反应说明前一步失败后后面依赖它的操作要重新确认对象是否存在。4. 查询例题里的 NULL、LIKE 与排序陷阱4.1 LIKE 中下划线的单字符语义原文例 16 特别强调了下划线 _ 的含义表示任意单个字符只能匹配一个字符一个汉字也只算一个 _。很多人以为 _ 能匹配多个字符或者以为汉字占两个在 SQL Server 里 CHAR 和 VARCHAR 的字符计数是按字符数来的不是字节数所以 张 能匹配 张立但匹配不了 张立明。-- 匹配姓张且名字只有一个字的学生 SELECT * FROM student WHERE sname LIKE 张_; -- 匹配姓张且名字至少两个字的学生 SELECT * FROM student WHERE sname LIKE 张__; -- 如果要匹配真正的下划线字符需要转义 SELECT * FROM student WHERE sname LIKE %\_% ESCAPE \;参数说明LIKE 里的 % 匹配任意长度包括零_ 匹配恰好一个字符。ESCAPE 子句用来定义转义符把通配符还原成普通字符。如果不用 ESCAPE想查名字里带下划线的记录就没办法了。4.2 NULL 在排序和比较中的行为差异原文例 24 提到一个关键差异SQL Server 认为 NULL 最小Oracle 认为 NULL 最大。这直接影响 ORDER BY 的结果。在 SQL Server 里ORDER BY grade ASC 时 NULL 排在最前面ORDER BY grade DESC 时 NULL 排在最后面。如果业务上希望 NULL 统一排最后需要显式处理。-- SQL Server 中 NULL 默认最小升序时排最前 SELECT * FROM sc ORDER BY grade ASC; -- 让 NULL 排最后用 CASE 或 ISNULL 处理 SELECT * FROM sc ORDER BY CASE WHEN grade IS NULL THEN 1 ELSE 0 END, grade ASC; -- 或者用 ISNULL 给个默认值会改变显示值慎用 SELECT sno, cno, ISNULL(grade, -1) AS grade FROM sc ORDER BY ISNULL(grade, -1) ASC;参数说明CASE WHEN grade IS NULL THEN 1 ELSE 0 END 生成一个排序辅助列非空为 0空为 1升序时非空在前、空在后。ISNULL(grade, -1) 把空值替换成 -1排序时 -1 最小会排最前所以如果要排最后得用一个比所有成绩都大的数比如 999。另外 NULL 和任何值比较包括 NULL 和 NULL结果都是 UNKNOWN不是 TRUE 也不是 FALSE所以 WHERE grade NULL 永远查不到东西必须用 IS NULL。5. 避坑与排查五个上机必踩的坑5.1 外码引用导致建表和插入顺序报错现象先建 sc 表再建 student 表报“外码引用的表不存在”。或者先插 sc 再插 student报“INSERT 语句与 FOREIGN KEY 约束冲突”。 原因外码约束要求被引用的表和列必须已经存在且被引用列上要有主码或唯一约束。插入时 sc 的 sno 和 cno 必须在 student 和 course 里能找到对应值。 解决建表顺序按 student → course → sc插入顺序同样。如果已经建错先删 sc 再重建。删除时顺序反过来先删 sc 再删 student 和 course否则 DROP TABLE student 会因为被 sc 引用而失败。5.2 ALTER COLUMN 改类型时约束依赖未清理现象ALTER TABLE student ALTER COLUMN sage int 报错提示有对象依赖于此列。 原因sage 上有 CHECK 约束改类型会导致约束失效数据库不允许直接改。 解决先用 sys.check_constraints 查出约束名DROP CONSTRAINT 删掉改完类型后再 ADD CONSTRAINT 加回来。如果列上还有默认值约束、索引同样需要先清理。5.3 聚集索引重复创建现象CREATE CLUSTERED INDEX 报“不能在表上创建多个聚集索引”。 原因主码默认创建聚集索引一个表只能有一个聚集索引。 解决要么先删掉现有聚集索引注意主码约束可能依赖它删之前要确认要么改用非聚集索引。如果确实需要换聚集索引的列先 DROP 旧的再 CREATE 新的中间表会短暂无聚集索引生产环境要避开高峰期。5.4 DROP INDEX 语法与对象不存在现象DROP INDEX stusname 报语法错误或者 DROP INDEX student.stusname 报“在系统目录中不存在”。 原因SQL Server 要求 DROP INDEX 必须写成 表名.索引名。索引不存在通常是因为前面创建失败了但后续语句没检查就继续执行。 解决删除前先用 sys.indexes 查一下索引是否存在。批量脚本里每一步执行后检查 ERROR 或使用 TRY...CATCH避免前一步失败后后面全乱。5.5 CHAR 类型尾部空格导致等值比较失效现象WHERE sname 张立 查不到明明存在的记录。 原因CHAR(20) 存储 张立 时会补 18 个空格而字面量 张立 没有尾部空格等值比较时可能不匹配取决于数据库的填充设置。 解决查询时用 RTRIM(sname) 张立或者建表时改用 VARCHAR。如果已经用了 CHAR插入时也尽量用 RTRIM 处理或者接受尾部空格的存在查询时用 LIKE 张立% 代替等号。6. 把例题代码变成自己的验证脚本一个可复用的检查习惯我后来再碰这类教材例题代码不会直接从头到尾粘贴执行而是先做一件事把整个脚本拆成“建表段”“插入段”“修改段”“查询段”四块每块之间加一个检查点。建表段执行完先查 sys.tables 确认三张表都在插入段执行完查 COUNT(*) 确认行数对得上修改段每执行一条查一下 sys.columns 和 sys.indexes 看结构变没变查询段跑完把结果和书上预期结果对一遍。这个习惯是从例 9 和例 15 的连锁报错里逼出来的——前一步失败后面全错但错误信息只报最后一条排查起来像黑匣子。具体做法是用一个简单的验证脚本包住关键步骤-- 建表后检查 SELECT name FROM sys.tables WHERE name IN (student,course,sc); -- 插入后检查行数 SELECT student AS tbl, COUNT(*) AS cnt FROM student UNION ALL SELECT course, COUNT(*) FROM course UNION ALL SELECT sc, COUNT(*) FROM sc; -- 改表后检查列类型 SELECT c.name, t.name AS type_name, c.max_length FROM sys.columns c JOIN sys.types t ON c.user_type_id t.user_type_id WHERE c.object_id OBJECT_ID(student) AND c.name sage; -- 索引检查 SELECT i.name, i.type_desc, i.is_unique FROM sys.indexes i WHERE i.object_id OBJECT_ID(student) AND i.name IS NOT NULL;参数说明sys.tables、sys.columns、sys.indexes 是 SQL Server 的元数据视图type_desc 显示 CLUSTERED 或 NONCLUSTEREDis_unique 是 1 表示唯一索引。这些查询不改变数据可以反复跑。把这几段固定成一个模板每次做新例题先跑一遍基线检查再执行改动出问题时对比前后差异定位速度会快很多。还有一个习惯所有 DROP 和 ALTER 语句执行前先把对象名复制到查询窗口里单独查一次存在性。比如要 DROP INDEX student.stusno先跑 SELECT * FROM sys.indexes WHERE object_id OBJECT_ID(student) AND name stusno有结果再删。这个动作多花五秒钟但能避免“对象不存在”这种低级报错打断节奏。从那以后我每次拿到类似的例题代码包都强制走一遍“分段执行 检查点 存在性确认”的流程翻车次数明显少了。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑