☰
数据库原理实验报告:SQL Server建库建表与查询实战指南
2026/10/17 14:58:59 网站建设 项目流程

简介:这份南华大学《数据库原理》实验报告面向大数据技术等专业学生,以及正在学习数据库课程、需要完成实验作业或复习备考的读者,帮助梳理从DBMS认识到SQL查询的完整实验脉络。资源包内含1个doc文档,大小约5.38MB,以实验报告正文形式呈现,结构清晰、便于直接参考。报告围绕SQL Server Management Studio展开,涵盖利用管理工具与SQL语句创建“学生选课200905”“StuCou200905”数据库,建立学生、课程、选修三张表并定义主码、外码、默认值与空值约束,进而理解一对多、多对多等表间关系。实验2、实验3则聚焦单表查询与多表连接查询,涉及条件筛选、排序、分组、JOIN及间接先行课等典型题目,并延伸至数据更新与实验总结。目前已有375人学习下载,适合需要对照实验步骤、理解SQL语句写法与数据完整性约束的学习者参考。

1. 从一份实验报告说起:数据库原理到底怎么落地

很多人拿到《数据库原理》这门课,第一反应是背 SQL 语法、记范式定义,考完就忘。但真正到了工作里,让你从零建一个库、设计三张表、把主外键关系理清楚,再写几条能跑出正确结果的查询,不少人还是会卡壳。这份实验报告的价值就在这儿——它不是纯理论,而是把 SQL Server Management Studio 这个工具和 SQL 语句两条线并排走了一遍,从建库建表到单表查询、多表连接、嵌套与组合查询,每一步都有对应的题目和代码。适合正在上数据库课、需要交实验报告的学生,也适合想系统过一遍 SQL 基础、把「学生选课」这个经典模型吃透的开发者。下面我按实验顺序拆一遍,重点讲清楚每步在干什么、参数怎么定、哪里容易翻车。

2. 实验一:建库建表与表间关系,先把地基打对

2.1 用管理工具建库建表:图形界面不是点着玩

实验一要求先用 SQL Server Management Studio 的图形界面创建数据库【学生选课200905】,再建三张表:学生200905、课程200905、选修200905。很多人觉得图形界面就是右键新建、填名字、点确定,但真正容易出问题的是字段类型和约束。

学生表里学号用 char(10) 还是 varchar(10)?常见做法是学号固定长度,用 char(10),姓名用 nvarchar(20) 支持中文,性别用 nchar(2) 并设默认值 '男',出生日期用 date,院系名称用 nvarchar(50),备注用 nvarchar(200) 允许为空。课程表里课程号用 char(6),课程名 nvarchar(50),先行课 char(6) 允许为空(因为有些课没有先行课),学分用 decimal(3,1) 并设默认值 2.0。选修表里学号和课程号都是 char 类型,分数用 decimal(5,1) 允许为空(因为可能还没出成绩)。

主键的定义直接决定后续查询能不能正确关联。学生表主键是学号,课程表主键是课程号,选修表的主键是(学号,课程号)组合主键。外键方面,选修表的学号引用学生表的学号,选修表的课程号引用课程表的课程号。这里有个血泪经验:如果先建选修表再建学生表和课程表,外键会创建失败,因为被引用的表还不存在。正确顺序是先建学生表和课程表,最后建选修表。

提示:图形界面建表时,默认值是在列属性窗口的「默认值或绑定」里填,不是直接在类型里写。填 '男' 要带单引号,填 2.0 不用带。

2.2 用 SQL 语句重建一遍:StuCou200905 的完整脚本

图形界面点完之后,实验要求用 SQL 语句再建一个库叫 StuCou200905,表名变成 Stu200905、Cou200905、SC200905。这一步才是真正要掌握的东西,因为工作中很少让你点鼠标建表,都是写脚本。

-- 创建数据库 CREATE DATABASE StuCou200905; GO USE StuCou200905; GO -- 学生表 CREATE TABLE Stu200905 ( 学号 CHAR(10) PRIMARY KEY, 姓名 NVARCHAR(20) NOT NULL, 性别 NCHAR(2) DEFAULT '男', 出生日期 DATE, 院系名称 NVARCHAR(50), 备注 NVARCHAR(200) ); -- 课程表 CREATE TABLE Cou200905 ( 课程号 CHAR(6) PRIMARY KEY, 课程名 NVARCHAR(50) NOT NULL, 先行课 CHAR(6) NULL, 学分 DECIMAL(3,1) DEFAULT 2.0 ); -- 选修表 CREATE TABLE SC200905 ( 学号 CHAR(10), 课程号 CHAR(6), 分数 DECIMAL(5,1), PRIMARY KEY (学号, 课程号), FOREIGN KEY (学号) REFERENCES Stu200905(学号), FOREIGN KEY (课程号) REFERENCES Cou200905(课程号) );

这段脚本里,GO 是批处理分隔符,不是 SQL 语句本身,在 SSMS 里用来分批执行。PRIMARY KEY 直接跟在列后面是列级约束,写在最后是表级约束,组合主键必须用表级约束。FOREIGN KEY 后面跟 REFERENCES 指定被引用的表和列,被引用的列必须是主键或唯一键。DEFAULT 后面跟的值要和列类型匹配,'男' 是字符串所以带引号,2.0 是数字所以不带。

建完表之后插入数据,注意插入顺序也要先插学生和课程,再插选修,否则外键约束会报错。插入语句里字符串用单引号,日期用 '1999-01-01' 这种格式,SQL Server 一般能自动识别。

2.3 表间关系到底怎么理解:一对多与多对多

学生表和课程表之间没有直接外键,它们通过选修表关联。一个学生可以选多门课,一门课可以被多个学生选,所以学生和课程是多对多关系,选修表就是中间表。选修表的组合主键(学号,课程号)保证了同一个学生不能重复选同一门课,这就是实体完整性的体现。

外键约束保证了参照完整性:你不能往选修表里插一个学生表里不存在的学号,也不能删掉一个已经被选修表引用的学生记录。如果非要删,得先删选修表里的对应记录,或者建外键时加 ON DELETE CASCADE。实验里没要求级联删除,所以默认是 RESTRICT,删被引用的记录会直接报错。

注意:如果建表时忘了设外键,后面可以用 ALTER TABLE 补,但前提是现有数据必须满足外键约束,否则加不上去。

3. 实验二:单表查询与多表连接,把 SELECT 写明白

3.1 单表查询:WHERE 后面那些条件怎么写才不翻车

实验二的单表查询有 10 个题目,覆盖了比较运算符、逻辑运算符、LIKE、BETWEEN、ORDER BY、GROUP BY 和聚合函数。挑几个容易出错的讲。

查询姓“李”的学生学号和姓名,用WHERE 姓名 LIKE '李%'。百分号是通配符,匹配任意多个字符。如果写成LIKE '李_',下划线只匹配一个字符,那就只能查出姓李且名字只有两个字的学生。这里有个玄学问题:如果数据库排序规则区分大小写,LIKE '李%' 可能受排序规则影响,但中文一般不受影响。

查询考试成绩有不及格的学生的学号,用WHERE 分数 < 60。但要注意,分数为 NULL 的记录不会被查出来,因为 NULL 和任何值比较都返回 UNKNOWN。如果想把没出成绩的也算上,得写WHERE 分数 < 60 OR 分数 IS NULL。

查询选修了两门以上课程的学生学号与课程数,用GROUP BY 学号 HAVING COUNT(*) > 2。这里 WHERE 和 HAVING 的区别是关键:WHERE 在分组前过滤行,HAVING 在分组后过滤组。聚合函数不能写在 WHERE 里,只能写在 HAVING 里。

-- 查询选修课程数大于等于2的学生的学号、平均成绩和选课门数,按平均成绩降序 SELECT 学号, AVG(分数) AS 平均成绩, COUNT(*) AS 选课门数 FROM SC200905 GROUP BY 学号 HAVING COUNT(*) >= 2 ORDER BY 平均成绩 DESC;

AVG 会自动忽略 NULL 值,如果某个学生有一门课分数为 NULL,那门课不计入平均分但计入 COUNT(*)。如果想让 COUNT 也忽略 NULL,得写 COUNT(分数)。ORDER BY 默认升序,DESC 是降序,可以跟多个列,比如ORDER BY 平均成绩 DESC, 学号 ASC。

3.2 多表连接查询:INNER JOIN 和 OUTER JOIN 的结果差在哪

多表查询的核心是把两张或多张表按关联条件拼在一起。实验里要求比较内连接和外连接的结果,这是很多人绕不过去的坎。

查询选修了【数据库原理】的计算机系学生学号和姓名,需要三张表:Stu200905、SC200905、Cou200905。写法有两种,一种是 JOIN ON,一种是 WHERE 隐式连接。

-- 显式内连接 SELECT s.学号, s.姓名 FROM Stu200905 s JOIN SC200905 sc ON s.学号 = sc.学号 JOIN Cou200905 c ON sc.课程号 = c.课程号 WHERE c.课程名 = N'数据库原理' AND s.院系名称 = N'计算机系'; -- 隐式连接 SELECT s.学号, s.姓名 FROM Stu200905 s, SC200905 sc, Cou200905 c WHERE s.学号 = sc.学号 AND sc.课程号 = c.课程号 AND c.课程名 = N'数据库原理' AND s.院系名称 = N'计算机系';

两种写法结果一样,但显式 JOIN 更清晰,尤其是多张表的时候。N'数据库原理' 前面的 N 表示 Unicode 字符串,防止中文乱码,虽然 SQL Server 里不加 N 一般也能识别,但加上更保险。

查询所有学生的信息和他们选修的课程编号,包括没选课的学生,这时候必须用 LEFT JOIN。如果用 INNER JOIN,没选课的学生会被过滤掉。

SELECT s.学号, s.姓名, sc.课程号 FROM Stu200905 s LEFT JOIN SC200905 sc ON s.学号 = sc.学号;

LEFT JOIN 保留左表所有行,右表没匹配的填 NULL。RIGHT JOIN 反过来,FULL JOIN 两边都保留。实验里还要求查每一门课的间接先行课,就是先行课的先行课,这需要把课程表自己和自己连接,叫自连接。

SELECT c1.课程号, c2.先行课 AS 间接先行课 FROM Cou200905 c1 JOIN Cou200905 c2 ON c1.先行课 = c2.课程号 WHERE c2.先行课 IS NOT NULL;

自连接必须给表起两个别名,否则没法区分。这里 c1 是当前课,c2 是它的先行课,c2.先行课 就是间接先行课。WHERE 条件过滤掉没有间接先行课的记录。

3.3 聚合与排序:GROUP BY 和 ORDER BY 的执行顺序

很多人写查询时搞不清 GROUP BY 和 ORDER BY 谁先谁后。实际执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。所以 ORDER BY 里可以用 SELECT 里定义的别名,但 WHERE 里不能用。

实验里有一题是查计算机系学生选修课程数大于 2 的姓名、平均成绩和选课门数,按平均成绩降序。这里 WHERE 过滤院系,GROUP BY 按学生分组,HAVING 过滤课程数,ORDER BY 排序。注意姓名不在 GROUP BY 里,但可以出现在 SELECT 里,因为按学号分组后姓名是唯一的。如果按院系分组,SELECT 里就不能直接写姓名,除非用聚合函数。

提示:SQL Server 对 GROUP BY 的列要求比较严格,SELECT 里非聚合列必须出现在 GROUP BY 里,否则报错。MySQL 默认宽松模式可能不报错,但结果不确定。

4. 实验三:嵌套查询与组合查询,子查询和集合运算怎么选

4.1 嵌套查询:IN、EXISTS 和比较运算符的适用场景

实验三的题目要求用嵌套查询和组合查询两种方法实现同一需求,这是为了让你理解不同写法的等价性。嵌套查询就是把一个 SELECT 的结果作为另一个 SELECT 的条件。

统计选修了【数据库原理】课程的学生人数,可以用 IN 嵌套:

SELECT COUNT(*) FROM SC200905 WHERE 课程号 IN ( SELECT 课程号 FROM Cou200905 WHERE 课程名 = N'数据库原理' );

也可以用 EXISTS:

SELECT COUNT(*) FROM SC200905 sc WHERE EXISTS ( SELECT 1 FROM Cou200905 c WHERE c.课程号 = sc.课程号 AND c.课程名 = N'数据库原理' );

IN 适合子查询返回少量值的情况,EXISTS 适合子查询返回大量值或需要关联外层表的情况。EXISTS 只关心子查询有没有返回行,不关心返回什么,所以 SELECT 1 就够了。性能上,EXISTS 通常比 IN 快,因为 IN 要先执行子查询再拿结果去匹配,EXISTS 是逐行检查。

查询没有选修【数据库原理】课程的学生信息,用 NOT IN 或 NOT EXISTS。这里有个坑:如果子查询结果里有 NULL,NOT IN 会返回空结果,因为 NULL 和任何值比较都是 UNKNOWN。所以用 NOT IN 时要确保子查询列没有 NULL,或者改用 NOT EXISTS。

查询其他系中比计算机系学生年龄都小的学生信息,需要用到 ALL 或 MAX:

SELECT * FROM Stu200905 WHERE 院系名称 <> N'计算机系' AND 出生日期 > ALL ( SELECT 出生日期 FROM Stu200905 WHERE 院系名称 = N'计算机系' );

出生日期越大年龄越小,所以比所有计算机系学生年龄都小等价于出生日期大于所有计算机系学生的出生日期。ALL 表示比子查询结果里所有值都大,也可以用> (SELECT MAX(出生日期) ...)替代。

4.2 组合查询:UNION、INTERSECT 和 EXCEPT 的等价改写

组合查询是把两个 SELECT 的结果做集合运算。UNION 是并集,INTERSECT 是交集,EXCEPT 是差集。实验要求用组合查询和嵌套子查询两种方法实现同一需求,这是为了让你看到不同思路。

查询被 0602001 或 0602002 学生选修的课程号,用 UNION:

SELECT 课程号 FROM SC200905 WHERE 学号 = '0602001' UNION SELECT 课程号 FROM SC200905 WHERE 学号 = '0602002';

UNION 会自动去重,如果不想去重用 UNION ALL。用 IN 条件查询的等价写法:

SELECT DISTINCT 课程号 FROM SC200905 WHERE 学号 IN ('0602001', '0602002');

查询两个学生同时选修的课程号,用 INTERSECT:

SELECT 课程号 FROM SC200905 WHERE 学号 = '0602001' INTERSECT SELECT 课程号 FROM SC200905 WHERE 学号 = '0602002';

用 EXISTS 嵌套的等价写法:

SELECT 课程号 FROM SC200905 sc1 WHERE 学号 = '0602001' AND EXISTS ( SELECT 1 FROM SC200905 sc2 WHERE sc2.学号 = '0602002' AND sc2.课程号 = sc1.课程号 );

查询被 0602001 选修但没被 0602002 选修的课程号,用 EXCEPT:

SELECT 课程号 FROM SC200905 WHERE 学号 = '0602001' EXCEPT SELECT 课程号 FROM SC200905 WHERE 学号 = '0602002';

用 NOT EXISTS 的等价写法:

SELECT 课程号 FROM SC200905 sc1 WHERE 学号 = '0602001' AND NOT EXISTS ( SELECT 1 FROM SC200905 sc2 WHERE sc2.学号 = '0602002' AND sc2.课程号 = sc1.课程号 );

INTERSECT 和 EXCEPT 在 SQL Server 里支持,但 MySQL 不支持,MySQL 里要用 JOIN 或 EXISTS 改写。这是数据库移植时常见的坑。

4.3 子查询的层次嵌套与括号配对

实验要求里专门提到「语句的层次嵌套关系以及括号的配对使用问题」,这不是废话。嵌套查询写多了,括号很容易多一层少一层,尤其是子查询里再套子查询。

一个典型的三层嵌套:查选修了「数据库原理」且成绩大于该课平均成绩的学生学号。

SELECT 学号 FROM SC200905 WHERE 课程号 = (SELECT 课程号 FROM Cou200905 WHERE 课程名 = N'数据库原理') AND 分数 > ( SELECT AVG(分数) FROM SC200905 WHERE 课程号 = (SELECT 课程号 FROM Cou200905 WHERE 课程名 = N'数据库原理') );

这里最内层子查询查课程号,中间层算平均分,最外层过滤。括号从内到外配对,写的时候建议先写最内层,再一层层往外包。如果括号对不上,SSMS 会报语法错误,但错误位置不一定准确,可能指向括号附近。

注意:子查询返回多行时不能用 = 比较,要用 IN 或 ANY/ALL。如果子查询返回多列,也不能直接比较,得用 EXISTS 或 JOIN。

5. 避坑与排查:实验里最容易翻车的五个地方

5.1 中文乱码:N 前缀和排序规则

现象:插入中文数据后查询出来是问号,或者 WHERE 条件里中文匹配不上。

原因:数据库默认排序规则不是中文排序规则,或者字符串没加 N 前缀。SQL Server 里 varchar 存中文可能乱码,nvarchar 不会。

解决:建库时指定排序规则COLLATE Chinese_PRC_CI_AS,字符串常量加 N 前缀,比如N'计算机系'。列类型用 nvarchar 而不是 varchar。

5.2 外键冲突:插入顺序和删除顺序

现象:往选修表插数据时报「外键约束冲突」,或者删学生表记录时报「被引用」。

原因:插入时先插了选修表,被引用的学生或课程还不存在。删除时先删了学生表,选修表里还有引用。

解决:插入顺序按被引用表先插,引用表后插。删除顺序反过来。或者建外键时加 ON DELETE CASCADE 自动级联删除,但实验里一般不用。

5.3 NULL 值陷阱:NOT IN 和聚合函数

现象:WHERE 学号 NOT IN (SELECT 学号 FROM ...)返回空结果。

原因:子查询结果里有 NULL,NULL 和任何值比较都是 UNKNOWN,NOT IN 整个条件变成 UNKNOWN,过滤掉所有行。

解决:子查询里加WHERE 学号 IS NOT NULL,或者改用 NOT EXISTS。聚合函数 AVG、SUM、COUNT 会自动忽略 NULL,但 COUNT(*) 不忽略。

5.4 分组报错:SELECT 列不在 GROUP BY 里

现象:SELECT 姓名, AVG(分数) FROM ... GROUP BY 学号报错「列名无效」。

原因:SQL Server 要求 SELECT 里非聚合列必须出现在 GROUP BY 里。姓名不在 GROUP BY 里,所以报错。

解决:把姓名加到 GROUP BY 里,或者用聚合函数包起来,比如MAX(姓名)。如果按学号分组后姓名唯一,可以加GROUP BY 学号, 姓名。

5.5 连接条件漏写:笛卡尔积

现象:多表查询结果行数爆炸,比如三张表各 10 行,结果出来 1000 行。

原因:JOIN 或 WHERE 里漏了连接条件,导致笛卡尔积。

解决:检查每个 JOIN 后面有没有 ON,或者 WHERE 里有没有关联条件。N 张表连接至少需要 N-1 个连接条件。写完后先SELECT COUNT(*)看看行数是否合理。

6. 进阶技巧:把实验报告变成可复用的 SQL 模板

实验报告交完就扔太可惜了。我一般会把「学生选课」这个模型整理成一套可复用的 SQL 模板,以后遇到类似的业务场景直接改表名和字段名就能用。比如电商场景里,学生对应买家,课程对应商品,选修对应订单,查询逻辑几乎一样。

具体做法是建一个sql_templates目录,按查询类型分文件:single_table.sql、join_queries.sql、subqueries.sql、set_operations.sql。每个文件里把实验里的题目改写成参数化查询,用@dept_name、@course_name这种变量代替硬编码值。

-- 模板:查某院系选修某课程的学生 DECLARE @dept NVARCHAR(50) = N'计算机系'; DECLARE @course NVARCHAR(50) = N'数据库原理'; SELECT s.学号, s.姓名 FROM Stu200905 s JOIN SC200905 sc ON s.学号 = sc.学号 JOIN Cou200905 c ON sc.课程号 = c.课程号 WHERE s.院系名称 = @dept AND c.课程名 = @course;

这样改参数就能复用,不用每次重写 JOIN。另外,实验里没要求建索引,但实际工作中,外键列和经常出现在 WHERE 里的列都应该建索引。比如CREATE INDEX idx_sc_stu ON SC200905(学号)能加速按学号查选修记录。

验证查询是否正确,我习惯用「小数据量手工核对」:先插三五条数据,手动算出预期结果,再跑查询对比。如果结果一致,再插大量数据测性能。这个习惯是从一次翻车经历来的——当时写了个多表连接,小数据量看着对,上了十万行数据后发现漏了一个连接条件,结果全错。从那以后我每次写完复杂查询都强制走一遍小数据量核对,再跑 EXPLAIN 看执行计划。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询