简介:这份资源是南华大学《数据库原理》课程的实验报告,面向大数据技术等专业学生以及正在学习数据库基础的学习者,用于完成课程实验、理解数据库设计与SQL应用。报告围绕认识DBMS、简单SQL查询、高级SQL查询、数据更新与实验总结展开,涵盖用SQL Server Management Studio图形界面和SQL语句创建“学生选课200905”“StuCou200905”数据库,建立学生、课程、选修三张表并定义主码、外码、默认值与表间关系,同时包含单表查询、多表连接查询及插入、更新、删除等操作练习。资源包共1个doc文件,约5.38MB,内容为完整实验报告文档,含实验题目、要求、代码与结果记录,便于对照复现实验过程、梳理SQL语法要点和查漏补缺。目前已有377人学习,适合作为课程实验参考与期末复习材料。
1. 数据库原理实验的知识地图:从建库到查询的完整链路
数据库原理这门课,实验部分看起来是四个独立任务,实际是一条完整的链路:先认识 DBMS 这个"黑匣子",再用 DDL 把现实世界的关系翻译成表结构,接着用 DML 往里面灌数据,最后用 SELECT 把数据按需求捞出来。很多人复习时把四个实验割裂开背,结果一到综合题就翻车——因为查询题里嵌套着建表时定下的主外码约束,约束没理清,JOIN 就写不对。
这份实验报告覆盖的正是这条链路:SQL Server Management Studio 的图形化建库建表、三张表(学生、课程、选修)的主外码设计、单表与多表查询、嵌套与组合查询。适合正在系统学数据库原理、准备实验考核或期末复习的人。梳理下来会发现,真正拉开差距的不是会不会写 SELECT,而是对"约束先行、查询后置"这个顺序的理解深度。下面按模块拆开讲,最后落到一份可执行的复习清单。
2. 建库建表:主码、外码与默认值的设计要点
2.1 三张表的字段设计与数据类型选择
实验一的核心是围绕"学生选课"场景建三张表:学生表、课程表、选修表。字段定义看似机械,但每个选择背后都有考点。学号、课程号这类编号用定长字符型(CHAR)比整数型更稳妥,因为编号可能带前导零;姓名用变长字符型(VARCHAR);出生日期用 DATE;分数用整数或小数型,取决于是否允许半分。
| 表名 | 关键字段 | 数据类型建议 | 约束 |
|---|---|---|---|
| 学生表 | 学号 | CHAR(10) | 主码,非空 |
| 学生表 | 姓名 | VARCHAR(20) | 非空 |
| 学生表 | 性别 | CHAR(2) | 默认值,可空 |
| 学生表 | 出生日期 | DATE | 可空 |
| 课程表 | 课程号 | CHAR(10) | 主码,非空 |
| 课程表 | 先行课 | CHAR(10) | 外码,参照课程号 |
| 课程表 | 学分 | INT | 默认值 |
| 选修表 | 学号 | CHAR(10) | 外码,参照学生表 |
| 选修表 | 课程号 | CHAR(10) | 外码,参照课程表 |
| 选修表 | 分数 | INT | 可空 |
这张表里最容易忽略的是课程表的"先行课"字段——它自己参照自己,是一个自参照外码。很多人在建表时忘了给它加外码约束,导致后面查"间接先行课"时数据里出现不存在的课程号,查询结果凭空多出脏行。
2.2 主码、外码与默认值的 SQL 写法
图形化界面建表和 SQL 语句建表要各练一遍,因为考试可能要求手写 DDL。下面这段是选修表的核心定义,重点看复合主码和外码的写法:
-- 选修表:学号+课程号 组成复合主码,两个字段分别外码参照 CREATE TABLE SC200905 ( 学号 CHAR(10) NOT NULL, 课程号 CHAR(10) NOT NULL, 分数 INT NULL, CONSTRAINT PK_SC PRIMARY KEY (学号, 课程号), CONSTRAINT FK_SC_Stu FOREIGN KEY (学号) REFERENCES Stu200905(学号), CONSTRAINT FK_SC_Cou FOREIGN KEY (课程号) REFERENCES Cou200905(课程号) );复合主码的含义是"同一个学生同一门课只能有一条记录",这直接决定了后面统计选课门数时不用去重。外码约束则保证了选修表里不会出现学生表里没有的学号。默认值的写法是在字段定义后加DEFAULT,比如性别字段写DEFAULT '男',学分字段写DEFAULT 4。参数上要注意:外码参照的必须是对方表的主码或候选码,类型和长度要完全一致,否则建表直接报错。
2.3 表间关系的三种类型怎么落到实验里
学生表和课程表通过选修表形成多对多关系,这是本实验的关系主线。理解这一点,后面所有多表查询都是在拆解这个多对多:查学生选了哪些课,是从学生表经选修表连到课程表;查某门课被谁选,方向反过来。一对多关系则体现在课程表的先行课自参照上——一门课最多有一个直接先行课,但一个先行课可以对应多门后续课。
提示:建表顺序必须是学生表、课程表在前,选修表在后,否则外码参照找不到目标表。删表顺序相反。
3. 简单 SQL 查询:单表筛选与多表连接的复习重点
3.1 单表查询的六类条件写法
实验二的单表查询十道题,实际覆盖六类条件:等值筛选(查某系学生)、模糊匹配(姓李)、空值判断(无先行课)、比较运算(不及格)、逻辑组合(选 C01 或 C02)、范围与排序(1999—2000 年出生、字典序前两名)。每一类都有固定套路,但坑集中在空值和聚合上。
查"没有先行课的课程名"必须用IS NULL,不能写= NULL,因为 NULL 参与任何比较都返回未知,结果永远是空集。这是血泪经验级别的易错点。查"选修两门以上课程"要先按学号分组再筛计数,顺序是GROUP BY在前、HAVING在后,把WHERE和HAVING搞混是另一个高频翻车点——WHERE筛行,HAVING筛组。
-- 查选修课程数大于等于2的学生:学号、平均成绩、选课门数,按平均成绩降序 SELECT 学号, AVG(分数) AS 平均成绩, COUNT(*) AS 选课门数 FROM SC200905 GROUP BY 学号 HAVING COUNT(*) >= 2 ORDER BY 平均成绩 DESC;这段里COUNT(*)统计的是组内行数,因为复合主码保证了每行对应一门课,所以行数就是门数。ORDER BY默认升序,降序必须显式写DESC。如果题目要求"平均成绩"参与排序但没在 SELECT 里起别名,某些环境会报错,起别名最稳。
3.2 内连接与外连接的结果差异
多表查询八道题里,区分内连接和外连接是重点。内连接只保留两表都匹配上的行,外连接保留一侧的全部行、另一侧用 NULL 填充。实验里"查询所有学生的信息和他们的选课课程号"必须用左外连接,因为没选课的学生也要出现在结果里,课程号显示为空。
-- 左外连接:所有学生都保留,没选课的课程号显示 NULL SELECT S.学号, S.姓名, S.性别, S.出生日期, S.院系名称, S.备注, SC.课程号 FROM Stu200905 S LEFT JOIN SC200905 SC ON S.学号 = SC.学号;如果把LEFT JOIN换成INNER JOIN,没选课的学生直接消失,结果行数变少,这是判卷时一眼能看出的错误。参数上注意ON后面写连接条件,不要写成WHERE,虽然某些写法结果相同,但外连接里把条件放WHERE会把外连接退化成内连接,这是最隐蔽的坑。
3.3 自连接查间接先行课
"查每门课的间接先行课"是本实验最绕的一题,需要课程表自己连自己两次:第一次找到直接先行课,第二次找到先行课的先行课。
-- 自连接:C1 是目标课,C2 是它的直接先行课,C3 是间接先行课 SELECT C1.课程号 AS 课程编号, C3.课程号 AS 间接先行课编号 FROM Cou200905 C1 JOIN Cou200905 C2 ON C1.先行课 = C2.课程号 JOIN Cou200905 C3 ON C2.先行课 = C3.课程号;逻辑是:C1 的先行课是 C2,C2 的先行课是 C3,那么 C3 就是 C1 的间接先行课。题目要求"有间接先行课才算",内连接天然满足——没有两级先行课的课程不会出现在结果里。这里给三张表起不同别名是关键,别名混用会导致条件指向错误,查出来的结果看似有值实则错位。
4. 高级 SQL 查询:嵌套子查询与组合查询的等价转换
4.1 嵌套查询的三种谓词
实验三的核心是同一需求用两种写法实现,逼你理解嵌套和组合的等价性。嵌套查询常用三个谓词:IN(属于子查询结果集)、EXISTS(子查询是否非空)、比较运算符配ALL/ANY(与子查询结果逐个比较)。
"查询其他系中比计算机系学生年龄都小的学生"这题,子查询先取出计算机系所有学生的出生日期,外层用< ALL比较——年龄都小意味着出生日期都晚,所以是大于所有计算机系学生的出生日期。方向搞反是这题最大的坑,写之前先在纸上画一下日期轴。
4.2 UNION、INTERSECT、EXCEPT 与子查询的对应
组合查询三个运算符分别对应三种集合语义,实验要求每种都用组合和嵌套两种方法写,这个对照关系值得背下来:
| 需求 | 组合查询写法 | 嵌套子查询写法 |
|---|---|---|
| 被 A 或 B 选修的课程 | UNION | IN |
| 被 A 和 B 同时选修的课程 | INTERSECT | EXISTS 两次 |
| 被 A 选修但 B 没选的课程 | EXCEPT | NOT EXISTS |
以"被 0602001 选修但没被 0602002 选修"为例,两种写法:
-- 写法一:EXCEPT 差集 SELECT 课程号 FROM SC200905 WHERE 学号 = '0602001' EXCEPT SELECT 课程号 FROM SC200905 WHERE 学号 = '0602002'; -- 写法二:NOT EXISTS 嵌套 SELECT DISTINCT 课程号 FROM SC200905 SC1 WHERE 学号 = '0602001' AND NOT EXISTS ( SELECT 1 FROM SC200905 SC2 WHERE SC2.学号 = '0602002' AND SC2.课程号 = SC1.课程号 );两种写法结果一致,但思路不同:差集是集合运算,直观;NOT EXISTS 是相关子查询,外层每行都要跑一次内层。考试常要求两种都写,复习时把这三组对照各手写一遍,比看十遍强。
4.3 子查询与连接的性能直觉
同样一个需求,用子查询和用连接写,结果可能相同但执行路径不同。相关子查询对外层每一行都要执行一次内层,数据量大时明显慢;连接和集合运算通常能被优化器处理得更好。实验阶段不要求调优,但要有这个直觉:能写成连接且语义等价的,优先连接;需要"存在性判断"的,EXISTS往往比IN更稳,因为IN遇到子查询结果含 NULL 时可能返回意外空集。
5. 易错点清单:这五处最容易在实验和考试里翻车
易错点一:NULL 的比较。为什么漏——直觉上以为= NULL能筛出空值,实际 SQL 三值逻辑里 NULL 不等于任何值。怎么补——所有空值判断统一用IS NULL/IS NOT NULL,写完条件后自查有没有出现= NULL。
易错点二:WHERE 与 HAVING 混用。为什么漏——两者都像"筛选",但作用对象不同。怎么补——记住口诀:筛行用 WHERE 且在 GROUP BY 之前,筛组用 HAVING 且在 GROUP BY 之后,聚合函数只能出现在 HAVING 里。
易错点三:外连接条件写进 WHERE。为什么漏——把连接条件当过滤条件写,语法不报错但语义变了。怎么补——外连接的附加条件放ON,对结果集的过滤才放WHERE,写完对比一下行数是否符合"保留全部一侧"的预期。
易错点四:自参照外码漏建。为什么漏——课程表的先行课参照自身,建表时容易只盯着跨表外码。怎么补——建完表用系统视图或图形界面检查所有外码约束,确认先行课字段有 REFERENCES 自身。
易错点五:组合查询列数和类型不匹配。为什么漏——UNION 两侧的 SELECT 列数必须相同、对应列类型兼容,少写一列直接报错。怎么补——写组合查询前先把两侧的列清单对齐,列顺序也要一致,否则结果列名取左侧但数据错位。
6. 复习清单:从建表到查询的查漏补缺路线
复习这份实验报告,建议按"约束 → 单表 → 多表 → 嵌套"四步走,每步配一个自测动作。第一步,合上报告,手写三张表的完整 DDL,重点检查复合主码、自参照外码、默认值三处,写完在环境里跑一遍看是否报错。第二步,把单表十题按条件类型归类,每类只写一题,重点确认 NULL 判断和 GROUP BY/HAVING 顺序。第三步,多表八题里挑出所有需要外连接的题,对比内连接写法的结果行数差异,理解"保留哪一侧"。第四步,组合查询三组对照各写两种实现,跑出结果后比对是否完全一致。
| 复习阶段 | 自测动作 | 通过标准 |
|---|---|---|
| 建表 | 手写三表 DDL 并执行 | 无报错,约束齐全 |
| 单表 | 每类条件各写一题 | NULL、分组顺序无误 |
| 多表 | 内外连接对比行数 | 能说清保留哪侧 |
| 嵌套 | 三组组合查询双写法 | 两种结果完全一致 |
查漏补缺的关键是"跑一遍再对照",而不是盯着代码看。很多错误只有执行后看结果集才能暴露,比如外连接退化成内连接、子查询方向写反,肉眼审查很难发现。建议准备一个小数据集,故意插入没选课的学生、没有先行课的课程、分数为 NULL 的记录,用这些边界数据去撞自己的查询,能撞出问题的地方就是没掌握的地方。
从那以后我每次复习 SQL 都强制走一遍"手写 DDL → 边界数据 → 双写法对照"的流程,不再靠读代码找安全感。希望这份梳理帮到你。
本文还有配套的精品资源,点击获取