SQL Server 2008 数据库实验报告完整拆解:从建库建表到备份恢复的实战避坑指南
简介这份《华科数据库实验报告》面向高校数据库课程学习者与备考学生围绕数据库系统概论课程实验帮助读者系统梳理SQL Server 2008环境下的完整实验流程与操作要点。资源包内含1个doc文档大小约294KB以实验报告正文形式呈现涵盖实验目的、原理、内容、过程与心得体会等模块结构完整、条理清晰。报告详细展开DDL、DML、DCL三类SQL语句的应用并覆盖基本表创建与数据插入、多表连接与子查询、数据修改删除、视图操作、库函数与授权控制、数据库备份与恢复六大实验主题每个实验均配有具体步骤与结果记录。目前已有252人学习浏览适合需要撰写实验报告、复习SQL核心语法或对照完成课程实验的读者参考可借此快速掌握数据库对象管理与权限控制的基本方法理解备份恢复策略的实际意义为后续项目实践打下基础。1. 从一份华科数据库实验报告说起SQL Server 2008 上机到底练什么如果你正在搜「数据库实验报告」「SQL Server 2008 实验」大概率是两种情况要么课程要求交一份能跑通的实验报告要么想找一份完整的 SQL 练习素材从头到尾把 DDL、DML、DCL 串一遍。这份华科计算机学院的数据库系统概论实验报告覆盖的正是这两类需求——它不是纯理论文档而是一份带完整建库、建表、插数据、查询、视图、授权、备份恢复六个实验步骤的上机记录底层跑在 SQL Server 2008 的查询分析器里。我带过几届数据库课程的助教也帮同事排查过不少「照着报告敲但跑不出来」的问题。这份报告的价值在于它把教学管理场景学生、课程、选课三张表作为贯穿案例从CREATE DATABASE ems一路做到RESTORE DATABASE中间没有跳步。适合数据库入门阶段需要一份可复现操作手册的人也适合已经工作但想快速回顾 SQL 基础语法的人。下面我按「建库建表 → 查询与修改 → 视图与权限 → 备份恢复」的顺序拆开讲重点放在参数怎么设、哪些地方容易翻车。2. 建库建表与数据插入DDL 语句的完整落地流程2.1 为什么先建库再建表顺序不能反实验报告里第一步是CREATE DATABASE ems然后USE ems切过去再建三张表。这个顺序看起来理所当然但实际动手时很多人会跳过USE直接建表结果表建到了master库里后面查询怎么都找不到。SQL Server 的查询分析器默认连接的是master系统库你不显式切换所有 DDL 操作都会落在系统库上。另一个容易忽略的点是GO批处理分隔符。报告里CREATE DATABASE ems后面跟了GO再USE ems。这不是可有可无的装饰——CREATE DATABASE必须单独作为一个批处理执行不能和后续语句放在同一个批次里。如果你把CREATE DATABASE和USE写在一起不加GO查询分析器会报语法错误。-- 创建实验数据库注意 CREATE DATABASE 必须独立成批 CREATE DATABASE ems; GO -- 切换到新建的数据库后续所有操作都在 ems 下进行 USE ems; GOGO不是 SQL 语句是查询分析器识别的批处理结束标记。它的作用是告诉客户端「上面这段先发给服务器执行完再发下一段」。参数上没什么可调的但位置很关键CREATE DATABASE、CREATE VIEW、CREATE PROCEDURE这类语句都要求独立成批。2.2 三张表的字段类型与约束设计报告里的表结构是教学管理场景的经典设计Students存学生基本信息Courses存课程信息SC存选课成绩。三张表通过外键关联构成一个标准的参照完整性约束体系。-- 学生表sno 为主键sname 非空 CREATE TABLE students( sno CHAR(9) PRIMARY KEY, sname CHAR(20) NOT NULL, age CHAR(3), sex CHAR(6) ); -- 课程表cno 为主键cname 非空 CREATE TABLE courses( cno CHAR(9) PRIMARY KEY, cname CHAR(20) NOT NULL, score INT, pc CHAR(3) ); -- 选课表sno 和 cno 分别作为外键引用前两张表 CREATE TABLE sc( sno CHAR(9) FOREIGN KEY REFERENCES students(sno), cno CHAR(9), grade INT, FOREIGN KEY(cno) REFERENCES courses(cno) );这里有几个参数值得注意。age用了CHAR(3)而不是INT报告里插入的数据是20、19这样的字符串。这在教学场景下能跑但实际项目中年龄应该用INT否则排序和比较会出现字符串语义的问题比如9比20大。score字段在Courses表里代表学分用了INT但插入数据里有3.5这个值——SQL Server 会做隐式转换把3.5截断成3或4具体取决于舍入规则。这是一个隐藏的数据精度坑后面讲避坑时会展开。SC表没有单独定义主键只定义了两个外键。这意味着同一个学生同一门课可以插入多条记录实体完整性没有被约束住。标准做法是把(sno, cno)设为联合主键。2.3 INSERT 批量插入数据的写法与注意事项数据插入部分报告用了逐条INSERT INTO ... VALUES的方式一共插入了 6 个学生、5 门课程、若干条选课记录。这种写法在实验场景下没问题但如果数据量大逐条插入效率很低。常见做法是用一条INSERT带多个VALUES子句或者用BULK INSERT从文件导入。-- 逐条插入学生数据 INSERT INTO students VALUES(S1,LU,20,M); INSERT INTO students VALUES(S2,YIN,19,M); INSERT INTO students VALUES(S3,XU,18,F); INSERT INTO students VALUES(S4,QU,18,F); INSERT INTO students VALUES(S6,PAN,14,M); INSERT INTO students VALUES(S8,DONG,24,M); -- 插入课程数据注意 C4 的学分是 3.5 INSERT INTO courses VALUES(C1,数学,4,M); INSERT INTO courses VALUES(C2,英语,8,M); INSERT INTO courses VALUES(C3,数据结构,4,F); INSERT INTO courses VALUES(C4,数据库,3.5,F); INSERT INTO courses VALUES(C5,网络,4,M);INSERT INTO table_name VALUES(...)这种省略列名的写法要求值的顺序和表中列的定义顺序完全一致。一旦表结构改了比如加了一列所有省略列名的INSERT都会报错。我一般建议在插入时显式写出列名INSERT INTO students(sno, sname, age, sex) VALUES(...)这样即使表结构变动也不会影响已有语句。选课数据里有多条NULL成绩的记录比如INSERT INTO SC VALUES(S2,C2,NULL)。NULL在 SQL 里表示未知值不是空字符串也不是 0。后续做AVG(grade)聚合时NULL会被自动排除这会影响平均成绩的计算结果——如果某学生所有成绩都是NULLAVG返回NULL而不是 0。3. 查询、修改与删除DML 语句的实战细节3.1 四类查询场景的 SQL 写法拆解实验 2 给了四个查询需求从简单到复杂依次是查选修 C2 的学生、查选修数学的学生、查没选修 C2 的学生、查选修全部课程的学生。这四个查询覆盖了连接查询、子查询和NOT EXISTS嵌套是 SQL 查询教学里非常典型的递进设计。-- 查询 1列出选修 C2 的学生学号与姓名两表连接 SELECT sc.sno, sname FROM students, sc WHERE sc.cno C2 AND sc.sno students.sno; -- 查询 2检索选修数学的学生学号与姓名三表连接 SELECT sc.sno, sname FROM students, sc, courses WHERE courses.cname 数学 AND courses.cno sc.cno AND students.sno sc.sno; -- 查询 3检索没有选修 C2 的学生姓名与年龄NOT EXISTS 子查询 SELECT sname, age FROM students WHERE NOT EXISTS ( SELECT * FROM sc WHERE sc.cno C2 AND sno students.sno ); -- 查询 4检索选修全部课程的学生姓名双重 NOT EXISTS SELECT sname FROM students WHERE NOT EXISTS ( SELECT * FROM courses WHERE NOT EXISTS ( SELECT * FROM sc WHERE sno students.sno AND cno courses.cno ) );查询 1 和查询 2 用的是隐式连接逗号分隔表名 WHERE 条件这是 SQL Server 2008 兼容的老写法。现代写法应该用INNER JOIN ... ON可读性更好也不容易漏掉连接条件导致笛卡尔积。查询 3 的NOT EXISTS是「差集」语义的标准实现找出所有学生中不存在 C2 选课记录的那些人。查询 4 是关系代数里「除法」的 SQL 表达双重否定表示「没有一门课是他没选的」等价于「他选了所有课」。注意查询 3 里子查询的sno students.sno是相关子查询子查询的执行依赖外层students表的当前行。如果写成sno sc.sno就变成了自引用逻辑完全错误。3.2 UPDATE 与 DELETE 的边界条件实验 3 的三个操作分别是把 C2 课程的非空成绩提高 10%、删除物理课的成绩记录、删除学号 S8 的所有数据。这三个操作覆盖了UPDATE带条件、DELETE带子查询、以及多表级联删除的场景。-- 修改C2 课程非空成绩提高 10% UPDATE sc SET grade grade * 1.1 WHERE sc.cno C2 AND grade IS NOT NULL; -- 删除删除物理课对应的选课记录子查询定位 cno DELETE FROM sc WHERE cno IN ( SELECT cno FROM courses WHERE cname 物理 ); -- 删除先删子表 sc 中 S8 的记录再删父表 students 中 S8 的记录 DELETE FROM sc WHERE sno S8; DELETE FROM students WHERE sno S8;第一个UPDATE里报告原文写的是WHERE sc.cnoc2 AND sc.cno is not null这里有个明显的笔误——第二个条件应该是grade IS NOT NULL而不是cno IS NOT NULL。因为cno作为连接字段不可能为NULL它是外键这个条件永远为真起不到过滤NULL成绩的作用。如果直接对NULL做grade * 1.1结果仍然是NULL不会报错但也不会更新。所以这个笔误不影响执行结果但逻辑上是错的。第三个删除操作涉及参照完整性sc表有外键指向students如果先删students里的 S8会因为sc里还有 S8 的记录而违反外键约束系统拒绝执行。所以必须先删子表sc再删父表students。这个顺序不能反。3.3 聚合查询与分组统计实验 5 的第一个需求是计算每个学生选修课程的门数和平均成绩用到了COUNT和AVG两个聚合函数配合GROUP BY。-- 统计每个学生的选课门数和平均成绩 SELECT students.sno, students.sname, COUNT(cno) AS 选修门数, AVG(grade) AS 平均成绩 FROM students, sc WHERE students.sno sc.sno GROUP BY students.sno, sname;COUNT(cno)统计的是cno非空的记录数如果某条选课记录的cno为NULL则不计入。AVG(grade)会自动忽略NULL值只对非空成绩求平均。这意味着如果一个学生选了 5 门课但只有 3 门有成绩平均成绩是这 3 门的平均而不是 5 门把NULL当 0的平均。这个行为在成绩统计场景下通常是合理的但如果你需要把缺考按 0 分计算就得用AVG(ISNULL(grade, 0))。GROUP BY后面必须包含SELECT列表中所有非聚合列。报告里写了GROUP BY students.sno, snamesname虽然不是聚合列但出现在了GROUP BY里这是正确的。如果只写GROUP BY students.sno而SELECT里有snameSQL Server 会报错。4. 视图、权限与备份恢复DCL 与运维操作4.1 视图的创建与查询优化实验 4 要求建立一个男生视图包含学号、姓名、选修课程名和成绩然后在视图上查询平均成绩大于 80 的学生。视图的本质是一条被命名的SELECT语句每次查询视图时底层 SQL 会被展开执行。-- 创建男生视图关联三张表 CREATE VIEW student_m(sno, sname, cname, grade) AS SELECT students.sno, students.sname, cname, grade FROM sc, students, courses WHERE students.sno sc.sno AND courses.cno sc.cno AND sex M; -- 在视图上查询平均成绩大于 80 的学生 SELECT DISTINCT students.sno, students.sname FROM student_m, students WHERE student_m.sno students.sno AND grade 80;视图定义里的sex M是过滤条件只有男生的记录会出现在视图中。视图本身不存储数据它只是一个查询的别名。对视图的查询最终会被改写为对基表的查询。这里第二个查询写的是grade 80而不是AVG(grade) 80和实验要求「平均成绩大于 80」有出入——正确写法应该用GROUP BY配合HAVING AVG(grade) 80。这是报告里的一个逻辑偏差实际做实验时需要注意。CREATE VIEW和CREATE DATABASE一样必须独立成批前面要加GO。如果在一个批次里先CREATE VIEW再跟其他语句查询分析器会报错。4.2 登录账号、数据库用户与权限授予实验 5 的第二、三个需求涉及 SQL Server 的安全体系先创建登录账号再创建数据库用户最后授予权限。这三层是递进关系——登录账号是服务器级别的数据库用户是数据库级别的权限是对象级别的。-- 创建 SQL Server 认证模式的登录账号 EXEC sp_addlogin ems, ems; GO -- 在当前数据库中为登录账号创建匹配的用户 USE ems; GO EXEC sp_grantdbaccess ems, ems; GO -- 授予 guest 用户对 Courses 表的所有权限 GRANT ALL PRIVILEGES ON Courses TO guest;sp_addlogin创建的是服务器级登录账号参数分别是登录名和密码。sp_grantdbaccess在当前数据库中创建与登录账号匹配的用户第一个参数是登录名第二个参数是数据库用户名可以不同。GRANT ALL PRIVILEGES ON Courses TO guest把Courses表的所有对象权限SELECT、INSERT、UPDATE、DELETE授予guest用户。注意guest是 SQL Server 每个数据库都有的内置用户默认情况下被禁用。直接对其授权可能不生效需要先用GRANT CONNECT TO guest启用。实际实验中更稳妥的做法是创建一个新用户再授权。4.3 备份与恢复的完整操作链实验 6 是运维操作完全备份 → 删除数据库 → 恢复数据库 → 撤销表和视图。备份用BACKUP DATABASE恢复用RESTORE DATABASE中间需要先创建备份设备。-- 创建备份设备磁盘文件 EXEC sp_addumpdevice DISK, backupdevice_name, d:\backupdev\ems.bak; GO -- 完全备份数据库到备份设备 BACKUP DATABASE ems TO backupdevice_name; GO -- 删除数据库模拟故障 DROP DATABASE ems; GO -- 从备份设备恢复数据库 RESTORE DATABASE ems FROM backupdevice_name; GOsp_addumpdevice的参数依次是设备类型DISK或TAPE、逻辑设备名、物理路径。备份设备创建一次即可后续备份可以重复使用。BACKUP DATABASE执行完全备份把整个数据库的数据和日志一起写入备份文件。RESTORE DATABASE从备份文件还原如果目标数据库已存在会报错需要先DROP或者加WITH REPLACE选项。恢复完成后报告要求撤销建立的基本表和视图。这里要注意DROP TABLE的顺序和DELETE一样必须先删子表sc再删父表students和courses否则外键约束会阻止删除。DROP VIEW student_m则没有顺序要求视图不涉及外键。5. 避坑与排查六个实验里最容易翻车的地方5.1 坑一CREATE DATABASE 和 USE 写在同一批次现象执行CREATE DATABASE ems; USE ems;报错「CREATE DATABASE 语句必须是批处理中仅有的语句」。原因SQL Server 要求CREATE DATABASE、CREATE VIEW、CREATE PROCEDURE等语句必须独立成批不能和其他语句混在一起。解决在CREATE DATABASE后面加GO再写USE ems。GO是查询分析器的批处理分隔符不是 SQL 语句。5.2 坑二外键约束导致 DELETE 失败现象执行DELETE FROM students WHERE sno S8报错「DELETE 语句与 REFERENCE 约束冲突」。原因sc表中有sno S8的记录外键约束阻止删除父表中被引用的行。解决先删子表DELETE FROM sc WHERE sno S8再删父表DELETE FROM students WHERE sno S8。或者建表时定义外键时加ON DELETE CASCADE让删除父表记录时自动级联删除子表记录。5.3 坑三NULL 值参与聚合和运算的隐式行为现象AVG(grade)的结果和手工计算不一致或者UPDATE sc SET grade grade * 1.1 WHERE grade IS NULL执行后NULL仍然是NULL。原因SQL 中NULL表示未知值任何与NULL的算术运算结果都是NULL聚合函数AVG、SUM、COUNT列名会自动忽略NULL行。解决如果需要把NULL当 0 处理用ISNULL(grade, 0)或COALESCE(grade, 0)转换。如果只是排除NULL在WHERE里加grade IS NOT NULL。5.4 坑四CHAR 类型存储数值的排序陷阱现象age字段用CHAR(3)存储ORDER BY age的结果是14、18、19、20、24看起来正常但如果出现9就会排在14后面。原因CHAR类型按字符串字典序排序不是按数值大小。9的字典序大于14因为第一个字符9大于1。解决年龄、分数这类需要数值比较的字段应该用INT或NUMERIC类型。如果已经建表且数据是字符串查询时用CAST(age AS INT)转换后再排序。5.5 坑五备份设备路径不存在导致 BACKUP 失败现象执行BACKUP DATABASE ems TO backupdevice_name报错「无法打开备份设备」。原因sp_addumpdevice指定的物理路径d:\backupdev\ems.bak中backupdev目录不存在。SQL Server 不会自动创建目录。解决先在操作系统层面创建目录d:\backupdev再执行sp_addumpdevice。或者直接用BACKUP DATABASE ems TO DISK d:\backup\ems.bak指定完整路径跳过创建备份设备的步骤。6. 从实验报告到真实项目几个能直接复用的技巧这份实验报告虽然跑在 SQL Server 2008 上但里面涉及的 SQL 语法和设计思路在 MySQL、PostgreSQL 等主流数据库里基本通用。我把它当成一份 SQL 基础练习素材来用的时候会做几个改造让它更贴近实际工作场景。第一个改造是把CHAR类型换成VARCHAR或INT。CHAR(9)存储学号S1会补空格到 9 位查询时WHERE sno S1能匹配上是因为 SQL Server 自动去掉了尾部空格但在 MySQL 的某些排序规则下可能匹配失败。实际项目中字符串主键用VARCHAR数值字段用INT这是基本习惯。第二个改造是给SC表加联合主键。ALTER TABLE sc ADD PRIMARY KEY(sno, cno)能防止同一个学生同一门课插入重复记录。实验报告里没加这个约束导致INSERT INTO SC VALUES(S1,C1,85)可以执行多次产生重复行。真实场景下选课记录必须唯一。第三个改造是用INNER JOIN替代隐式连接。把FROM students, sc WHERE students.sno sc.sno写成FROM students INNER JOIN sc ON students.sno sc.sno可读性更好而且不会因为漏写WHERE条件产生笛卡尔积。我见过太多因为漏写连接条件导致查询返回几万行数据的案例用JOIN语法至少能在写的时候提醒自己「这里需要一个ON条件」。第四个改造是备份策略。实验里只做了完全备份实际项目里完全备份 差异备份 事务日志备份的组合更常见。完全备份每周一次差异备份每天一次事务日志备份每小时一次这样恢复时最多丢失一小时的数据。SQL Server 的BACKUP DATABASE ... WITH DIFFERENTIAL做差异备份BACKUP LOG做日志备份恢复时按顺序RESTORE即可。最后一个习惯每次执行DELETE或UPDATE之前先用SELECT把WHERE条件跑一遍确认影响的行数和预期一致。这个习惯帮我避免过好几次「忘了写WHERE导致全表更新」的事故。从那以后我每次做数据修改操作都强制走一遍「先SELECT确认 → 再UPDATE/DELETE→ 最后SELECT验证」的流程没有例外。希望这份拆解能帮到你少走一些我当年踩过的弯路。本文还有配套的精品资源点击获取