简介:这份《华科数据库实验报告》面向高校数据库课程学习者与备考学生,围绕数据库系统概论课程实验,帮助读者系统梳理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会被自动排除,这会影响平均成绩的计算结果——如果某学生所有成绩都是NULL,AVG返回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.cno='c2' 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, sname,sname虽然不是聚合列但出现在了GROUP BY里,这是正确的。如果只写GROUP BY students.sno而SELECT里有sname,SQL 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验证」的流程,没有例外。希望这份拆解能帮到你,少走一些我当年踩过的弯路。
本文还有配套的精品资源,点击获取