ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

南华大学数据库原理实验报告:SQL Server建库到嵌套查询完整复现

南华大学数据库原理实验报告:SQL Server建库到嵌套查询完整复现 简介这份南华大学《数据库原理》实验报告面向大数据技术等专业学生用于课程实验的参考与复盘帮助读者系统掌握数据库管理系统操作与SQL语言应用。报告围绕SQL Server Management Studio展开涵盖数据库与数据表的创建、主码外码定义、默认值与空值约束设置以及学生、课程、选修三表间一对多与多对多关系的建立。实验内容从认识DBMS起步逐步深入到单表查询、多表连接查询、高级SQL查询与数据更新涉及条件筛选、排序、分组、JOIN及间接先行课等典型题目并强调实体完整性与参照完整性。资源包为1个doc文档大小约5.38MB结构完整、题目与代码对应清晰便于对照练习与查漏补缺。目前已有374人学习适合正在修读数据库原理课程或准备实验考核的学生参考使用。1. 南华大学数据库原理实验报告从建库到嵌套查询的完整复现路径如果你正在搜“南华大学 数据库原理 实验报告”大概率不是想抄一份交差而是想搞清楚这套实验到底在练什么、SQL Server 里怎么一步步跑通、以及为什么自己写的 SELECT 总是报错。这份报告来自 2022 学年春季学期大数据技术专业地点崇业楼 209教师肖建田覆盖了 DBMS 认识、简单 SQL 查询、高级 SQL 查询、数据更新四个实验模块。它最值钱的地方不是答案本身而是把“学生选课”这个经典场景从建库、建表、建关系到单表查询、多表连接、嵌套与组合查询串成了一条可复现的链路。适合正在上数据库原理课、需要交实验报告、或者想用 SQL Server 把 SELECT 语句真正练熟的人。2. 实验环境与建库建表SQL Server 里把三张表的关系立起来2.1 为什么选 SQL Server Management Studio 而不是命令行这套实验的起点是 SQL Server Management Studio也就是常说的 SSMS。很多新手会问既然最后都要用 SQL 语句建库建表为什么还要先用图形界面走一遍原因在于数据库原理课要你先建立“数据库—表—关系”的直观认知。图形界面里你能看到数据库节点、表节点、列属性、主键图标、外键连线这些视觉反馈对理解实体完整性和参照完整性非常关键。等你手动建完一遍再用 SQL 语句重建才知道每条 CREATE 语句背后对应的是哪个界面操作。实验里要求建两个库一个叫【学生选课200905】用管理工具建另一个叫 StuCou200905用 SQL 语句建。名字里的 200905 是学号后六位实际复现时换成你自己的学号即可。注意数据库名不要用中文加特殊符号混搭虽然 SQL Server 支持但后续在查询窗口切换数据库时容易选错。2.2 三张表的字段设计与约束定义实验的核心是三张表学生 200905、课程 200905、选修 200905。学生表存学号、姓名、性别、出生日期、院系名称、备注课程表存课程号、课程名、先行课、学分选修表存学号、课程号、分数。这里的关键不是把字段敲进去而是想清楚每个字段的数据类型、是否允许为空、默认值怎么给。表名字段类型建议约束学生 200905学号char(8)主键非空学生 200905姓名varchar(20)非空学生 200905性别char(2)默认值“男”学生 200905出生日期date允许空学生 200905院系名称varchar(30)允许空课程 200905课程号char(4)主键非空课程 200905课程名varchar(30)非空课程 200905先行课char(4)允许空外键指向课程号课程 200905学分decimal(3,1)默认值 2.0选修 200905学号char(8)外键指向学生表选修 200905课程号char(4)外键指向课程表选修 200905分数decimal(5,1)允许空性别默认值给“男”是实验里的常见设定实际业务中更合理的是不给默认值或者给“未知”。学分默认值 2.0 也是教学简化真实选课系统里学分由课程决定不该在选修表里设默认。但实验目的是让你练 DEFAULT 约束所以按题目走。2.3 用 SQL 语句重建 StuCou200905 的完整脚本图形界面建完【学生选课200905】后实验要求用 SQL 语句建 StuCou200905。下面这段脚本可以直接在 SSMS 新建查询里跑注意先切换到 master 或者你存放数据库的路径。-- 创建数据库 StuCou200905 CREATE DATABASE StuCou200905; GO USE StuCou200905; GO -- 创建学生表 CREATE TABLE Stu200905 ( 学号 CHAR(8) PRIMARY KEY, 姓名 VARCHAR(20) NOT NULL, 性别 CHAR(2) DEFAULT 男, 出生日期 DATE, 院系名称 VARCHAR(30), 备注 VARCHAR(100) ); GO -- 创建课程表先行课外键指向自身 CREATE TABLE Cou200905 ( 课程号 CHAR(4) PRIMARY KEY, 课程名 VARCHAR(30) NOT NULL, 先行课 CHAR(4), 学分 DECIMAL(3,1) DEFAULT 2.0, FOREIGN KEY (先行课) REFERENCES Cou200905(课程号) ); GO -- 创建选修表学号和课程号组成复合主键同时是外键 CREATE TABLE SC200905 ( 学号 CHAR(8), 课程号 CHAR(4), 分数 DECIMAL(5,1), PRIMARY KEY (学号, 课程号), FOREIGN KEY (学号) REFERENCES Stu200905(学号), FOREIGN KEY (课程号) REFERENCES Cou200905(课程号) ); GO这段脚本里课程表的先行课外键指向自身这是实验 2 里“间接先行课”查询的基础。选修表用学号和课程号做复合主键保证一个学生同一门课只能有一条选课记录。分数用 DECIMAL(5,1) 而不是 FLOAT避免浮点误差导致成绩比较时出现 59.9999 这种玄学问题。2.4 插入测试数据与验证表间关系建完表后要插数据实验报告里给了 Stu200905、Cou200905、SC200905 三组数据。插入顺序必须是学生、课程、选修因为外键约束会检查引用是否存在。如果先插选修表会直接报外键冲突。-- 插入学生数据 INSERT INTO Stu200905 VALUES (0602001, 李勇, 男, 1980-01-01, 计算机系, NULL), (0602002, 刘晨, 女, 1981-02-02, 计算机系, NULL), (0602003, 王敏, 女, 1982-03-03, 计算机系, NULL), (0602004, 张立, 男, 1983-04-04, 信息系, NULL); -- 插入课程数据 INSERT INTO Cou200905 VALUES (C01, 数据库原理, NULL, 4.0), (C02, 数据结构, C01, 3.0), (C03, 操作系统, C02, 3.0), (C04, 计算机网络, NULL, 2.0); -- 插入选修数据 INSERT INTO SC200905 VALUES (0602001, C01, 85.0), (0602001, C02, 90.0), (0602002, C01, 75.0), (0602002, C03, 58.0), (0602003, C01, 92.0), (0602003, C02, 88.0), (0602003, C04, 70.0);插完后用SELECT * FROM SC200905验证如果选修表里出现了学号或课程号在学生表、课程表里不存在的记录说明外键没生效。常见原因是建表时外键语句写错或者插入顺序反了。这时候不要急着删库先用EXEC sp_help SC200905看约束是否挂上。3. 简单 SQL 查询单表筛选与多表连接的写法拆解3.1 单表查询里的 WHERE 条件与排序陷阱实验 2 的单表查询有 10 道题覆盖了比较运算符、逻辑运算符、LIKE、BETWEEN、ORDER BY、GROUP BY 和聚合函数。这些题看起来简单但新手最容易在三个地方翻车日期格式、字符串匹配、分组后的排序。比如“查询计算机系在 1999—2000 年之间出生的学生姓名”SQL Server 里日期字面量要用单引号包起来写成1999-01-01 AND 2000-12-31。如果写成BETWEEN 1999 AND 2000SQL Server 会尝试把整数转成日期直接报错。再比如“查询姓李的学生中姓名按字典顺序排序的前两个”需要ORDER BY 姓名配合TOP 2但 TOP 必须和 ORDER BY 一起用才有确定结果否则返回哪两行是随机的。-- 查询计算机系全体学生信息 SELECT 学号, 姓名, 性别, 出生日期, 院系名称, 备注 FROM Stu200905 WHERE 院系名称 计算机系; -- 查询姓李的学生学号和姓名 SELECT 学号, 姓名 FROM Stu200905 WHERE 姓名 LIKE 李%; -- 查询没有先行课的课程名 SELECT 课程名 FROM Cou200905 WHERE 先行课 IS NULL; -- 查询考试成绩有不及格的学生的学号 SELECT DISTINCT 学号 FROM SC200905 WHERE 分数 60; -- 查询选修了 C01 或 C02 的学生学号及成绩 SELECT 学号, 课程号, 分数 FROM SC200905 WHERE 课程号 IN (C01, C02); -- 查询计算机系学生姓名及年龄 SELECT 姓名, YEAR(GETDATE()) - YEAR(出生日期) AS 年龄 FROM Stu200905 WHERE 院系名称 计算机系; -- 查询计算机系 1999-2000 年出生的学生姓名 SELECT 姓名 FROM Stu200905 WHERE 院系名称 计算机系 AND 出生日期 BETWEEN 1999-01-01 AND 2000-12-31; -- 查询姓李的学生中按字典顺序前两个 SELECT TOP 2 学号, 姓名 FROM Stu200905 WHERE 姓名 LIKE 李% ORDER BY 姓名; -- 查询选修两门以上课程的学生学号与课程数 SELECT 学号, COUNT(*) AS 课程数 FROM SC200905 GROUP BY 学号 HAVING COUNT(*) 2; -- 查询选修课程数大于等于 2 的学生学号、平均成绩和选课门数 SELECT 学号, AVG(分数) AS 平均成绩, COUNT(*) AS 选课门数 FROM SC200905 GROUP BY 学号 HAVING COUNT(*) 2 ORDER BY 平均成绩 DESC;这里HAVING和WHERE的区别是高频考点WHERE 在分组前过滤行HAVING 在分组后过滤组。上面最后两条题必须用 HAVING因为条件是针对 COUNT 聚合结果的。如果写成 WHERE COUNT(*) 2SQL Server 会直接报“聚合函数不能出现在 WHERE 子句中”。3.2 多表连接查询内连接、外连接与自连接的适用场景实验 2 的第二部分是多表连接8 道题里涉及了 INNER JOIN、LEFT JOIN 和自连接。很多学生到这里开始懵因为同样的“查询学生选课信息”用 WHERE 隐式连接和用 JOIN 显式连接结果一样但可读性差很多。我一般建议两张表用 JOIN三张表以上用 JOIN 链式写别在 WHERE 里堆逗号。“查询选修了数据库原理的计算机系学生学号和姓名”需要三张表学生表筛院系课程表筛课程名选修表做桥梁。写法有两种一种是 JOIN 链式一种是子查询。JOIN 链式在数据量大时通常更快因为优化器更容易选执行计划。-- 查询选修数据库原理的计算机系学生学号和姓名 SELECT S.学号, S.姓名 FROM Stu200905 S JOIN SC200905 SC ON S.学号 SC.学号 JOIN Cou200905 C ON SC.课程号 C.课程号 WHERE S.院系名称 计算机系 AND C.课程名 数据库原理; -- 查询每一门课的间接先行课 SELECT C1.课程号 AS 课程编号, C2.先行课 AS 间接先行课 FROM Cou200905 C1 JOIN Cou200905 C2 ON C1.先行课 C2.课程号 WHERE C2.先行课 IS NOT NULL; -- 查询学生学号、姓名、选修课程名称和成绩 SELECT S.学号, S.姓名, C.课程名, SC.分数 FROM Stu200905 S JOIN SC200905 SC ON S.学号 SC.学号 JOIN Cou200905 C ON SC.课程号 C.课程号; -- 查询选修了课程的学生姓名去重 SELECT DISTINCT S.姓名 FROM Stu200905 S JOIN SC200905 SC ON S.学号 SC.学号; -- 查询所有学生信息和选修课程编号左连接保留没选课的学生 SELECT S.学号, S.姓名, S.性别, S.出生日期, S.院系名称, S.备注, SC.课程号 FROM Stu200905 S LEFT JOIN SC200905 SC ON S.学号 SC.学号; -- 查询所有课程名字及被选修情况右连接保留没被选的课程 SELECT C.课程名, SC.学号, SC.课程号, SC.分数 FROM SC200905 SC RIGHT JOIN Cou200905 C ON SC.课程号 C.课程号; -- 列出学生所有可能的选修情况笛卡尔积 SELECT S.学号, S.姓名, C.课程号, C.课程名 FROM Stu200905 S CROSS JOIN Cou200905 C; -- 查找计算机系选修课程数大于 2 的学生姓名、平均成绩和选课门数 SELECT S.姓名, AVG(SC.分数) AS 平均成绩, COUNT(*) AS 选课门数 FROM Stu200905 S JOIN SC200905 SC ON S.学号 SC.学号 WHERE S.院系名称 计算机系 GROUP BY S.姓名 HAVING COUNT(*) 2 ORDER BY 平均成绩 DESC;自连接那道“间接先行课”是实验里最容易写错的。C1 和 C2 都是课程表C1.先行课 C2.课程号 表示 C1 的先行课是 C2而 C2 自己还有先行课所以 C2.先行课 就是 C1 的间接先行课。如果 C2.先行课 是 NULL说明 C2 没有先行课C1 也就没有间接先行课所以最后要加WHERE C2.先行课 IS NOT NULL。注意LEFT JOIN 和 RIGHT JOIN 在 SQL Server 里可以互换但建议统一用 LEFT JOIN把要保留全部行的表放左边可读性更好。CROSS JOIN 返回的是笛卡尔积学生表 4 行乘课程表 4 行等于 16 行实验里用来演示“所有可能选修情况”实际业务中慎用。4. 高级 SQL 查询嵌套子查询与 UNION/INTERSECT/EXCEPT 组合4.1 嵌套查询的执行顺序与相关子查询实验 3 的 6 道题全部围绕嵌套查询和组合查询。嵌套查询分两类不相关子查询和相关子查询。不相关子查询先执行内层把结果传给外层相关子查询内层依赖外层的值每处理一行外层就执行一次内层。理解这个区别才能判断什么时候该用 IN什么时候该用 EXISTS。“统计选修了数据库原理课程的学生人数”是不相关子查询的典型先在课程表里找到数据库原理的课程号再在选修表里数这个课程号出现了几次。-- 统计选修数据库原理课程的学生人数 SELECT COUNT(*) AS 学生人数 FROM SC200905 WHERE 课程号 IN ( SELECT 课程号 FROM Cou200905 WHERE 课程名 数据库原理 ); -- 查询没有选修数据库原理课程的学生信息 SELECT 学号, 姓名, 性别, 出生日期, 院系名称, 备注 FROM Stu200905 WHERE 学号 NOT IN ( SELECT SC.学号 FROM SC200905 SC JOIN Cou200905 C ON SC.课程号 C.课程号 WHERE C.课程名 数据库原理 ); -- 查询其他系中比计算机系学生年龄都小的学生信息 SELECT 学号, 姓名, 性别, 出生日期, 院系名称, 备注 FROM Stu200905 WHERE 院系名称 计算机系 AND 出生日期 ( SELECT MAX(出生日期) FROM Stu200905 WHERE 院系名称 计算机系 );第三题“比计算机系学生年龄都小”等价于“出生日期大于计算机系最晚的出生日期”所以内层用 MAX(出生日期)。如果写成 ALL (SELECT 出生日期 FROM ...)也可以但 MAX 更直观。这里有个坑如果计算机系没有学生子查询返回 NULL外层比较结果也是 NULL查询返回空集。实验数据里计算机系有学生所以不会触发但真实场景要加判空。4.2 UNION、INTERSECT、EXCEPT 与 IN/EXISTS 的等价改写实验 3 的后三题要求用两种方法实现同一查询组合查询和嵌套子查询。这是为了让你理解 UNION 对应 OR、INTERSECT 对应 AND、EXCEPT 对应 NOT。以“查询被 0602001 或 0602002 选修的课程号”为例UNION 写法是把两个学生的选课课程号分别查出来再合并去重IN 写法是把两个学号放进 IN 列表。-- 方法一UNION 组合查询 SELECT 课程号 FROM SC200905 WHERE 学号 0602001 UNION SELECT 课程号 FROM SC200905 WHERE 学号 0602002; -- 方法二IN 条件查询 SELECT DISTINCT 课程号 FROM SC200905 WHERE 学号 IN (0602001, 0602002); -- 查询 0602001 和 0602002 同时选修的课程号 -- 方法一INTERSECT SELECT 课程号 FROM SC200905 WHERE 学号 0602001 INTERSECT SELECT 课程号 FROM SC200905 WHERE 学号 0602002; -- 方法二EXISTS 嵌套子查询 SELECT DISTINCT SC1.课程号 FROM SC200905 SC1 WHERE SC1.学号 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 DISTINCT SC1.课程号 FROM SC200905 SC1 WHERE SC1.学号 0602001 AND NOT EXISTS ( SELECT 1 FROM SC200905 SC2 WHERE SC2.学号 0602002 AND SC2.课程号 SC1.课程号 );INTERSECT 和 EXCEPT 在 SQL Server 里是集合运算符会自动去重。EXISTS 和 NOT EXISTS 不关心子查询返回什么列只关心有没有行所以写SELECT 1就够了。性能上EXISTS 通常比 IN 更适合相关子查询因为找到第一行就返回不用把整个子查询结果集拉出来。但 SQL Server 优化器现在很聪明很多情况下 IN 和 EXISTS 会生成相同执行计划不用过度纠结。提示UNION 默认去重如果确定两个结果集没有重复或者不需要去重用 UNION ALL 更快。INTERSECT 和 EXCEPT 没有 ALL 版本SQL Server 里不支持 INTERSECT ALL。5. 避坑与排查SQL Server 实验里最容易翻车的五个地方5.1 数据库名和表名带中文导致查询窗口选错库现象在 SSMS 里新建查询执行SELECT * FROM Stu200905报“对象名无效”但表明明建了。原因查询窗口当前数据库不是 StuCou200905而是 master 或者【学生选课200905】。解决在查询窗口左上角下拉框手动选 StuCou200905或者脚本开头加USE StuCou200905; GO。中文库名在 SSMS 里显示正常但切换时容易看花眼建议建库时就用英文名。5.2 外键约束导致插入数据顺序报错现象先插选修表数据报“INSERT 语句与 FOREIGN KEY 约束冲突”。原因选修表的学号和课程号分别引用学生表和课程表被引用的行还不存在。解决按学生表、课程表、选修表的顺序插入。如果已经插乱了先DELETE FROM SC200905清空选修表再按顺序重插。注意 DELETE 不会重置自增列但这套实验没有自增列不影响。5.3 日期比较写成字符串导致隐式转换失败现象WHERE 出生日期 BETWEEN 1999 AND 2000报“从字符串转换日期和/或时间时失败”。原因SQL Server 把 1999 当字符串尝试转成日期时缺少月份和日期。解决写完整日期1999-01-01 AND 2000-12-31或者用YEAR(出生日期) BETWEEN 1999 AND 2000。后者虽然能跑但会导致索引失效数据量大时慢 SQL 优化会找上门。5.4 GROUP BY 后 SELECT 列不在聚合函数里现象SELECT 学号, 姓名, AVG(分数) FROM ... GROUP BY 学号报“列名无效”。原因姓名不在 GROUP BY 里也不是聚合函数。解决把姓名加到 GROUP BY或者用子查询先聚合再连接学生表取姓名。实验里“查询选修课程数大于等于 2 的学生学号、平均成绩和选课门数”只要求学号所以不涉及姓名但最后一道多表题要求姓名就需要 JOIN 学生表后按姓名分组或者按学号分组再关联。5.5 NOT IN 遇到 NULL 值返回空结果现象WHERE 学号 NOT IN (SELECT 学号 FROM SC200905 WHERE 课程号 C01)返回空集但明明有学生没选 C01。原因子查询结果里如果有 NULLNOT IN 会变成学号 NULL AND ...任何值和 NULL 比较都是 UNKNOWN整个条件不成立。解决子查询加WHERE 学号 IS NOT NULL或者改用 NOT EXISTS。这套实验数据里学号是主键不会为 NULL但真实场景里外键列可能允许空血泪经验是 NOT IN 能不用就不用。6. 从实验报告到真实项目把查询结果导出并验证执行计划实验报告交完不是终点。如果你想把这份报告里的 SQL 真正变成自己的技能我建议做两件事一是把每个查询的执行计划打开看一遍二是把结果导出成 CSV 做交叉验证。打开执行计划的方法在 SSMS 里点“查询”菜单选“包括实际执行计划”然后跑查询。你会看到每个步骤的开销百分比。比如“查询选修数据库原理的计算机系学生”这条如果学生表数据量大但院系名称没索引会出现表扫描开销集中在 Stu200905 上。这时候可以加一个非聚集索引CREATE NONCLUSTERED INDEX IX_Stu200905_院系名称 ON Stu200905(院系名称) INCLUDE (姓名);加完再跑一次执行计划表扫描会变成索引查找逻辑读次数下降。但注意实验环境数据量小加索引前后可能都是 0 秒看不出差别。真实项目里慢 SQL 优化就是靠这个对比来判断的。导出结果用 SSMS 自带的“结果另存为 CSV”就行或者用sqlcmd命令行sqlcmd -S localhost -d StuCou200905 -Q SELECT * FROM SC200905 -o sc200905.csv -s , -W-S指定服务器-d指定数据库-Q是查询语句-o输出文件-s指定列分隔符-W去掉尾部空格。导出后可以用 Excel 或者 Python 的 pandas 读进来验证 COUNT、AVG 这些聚合结果和 SQL 里跑出来的是否一致。我一般会拿“选修课程数大于等于 2 的学生”这条做交叉验证因为涉及 GROUP BY 和 HAVING最容易出现“SQL 跑出来 3 行Excel 筛选出来 4 行”这种对不上的情况一查往往是 NULL 值或者重复行在作怪。从那以后我每次写完嵌套查询都强制走一遍执行计划确认没有意外的表扫描或者笛卡尔积。这份南华大学的实验报告虽然只是课程作业但把建库、建表、单表查询、多表连接、嵌套与组合查询串成了一条完整的练习链路照着跑一遍SQL 的基本功就扎实了。希望帮到你。本文还有配套的精品资源点击获取
返回列表