简介:本资源是太原理工大学软件工程专业《数据库概论》课程的完整实验报告,面向高校数据库初学者与SQL实践者,聚焦SQL Server 2016环境下数据库对象管理、数据定义与操作等核心能力训练。报告覆盖创建/修改/删除表(含主键、外键约束)、索引(唯一索引、聚簇索引)、视图及INSERT/UPDATE/SELECT等关键语句的实操过程与结果验证,内容紧扣教学大纲,适合作为课堂实验复盘、期末复习或SQL语法自查参考。资源为单个1.69MB的Word文档(.docx),结构清晰,含实验目的、平台配置、分步代码示例(如Student/Course/Sc三表建模)、执行截图说明及注意事项,便于直接阅读与代码复现。目前已有312人学习下载,是兼具教学规范性与工程实操性的优质课程配套材料。
1. 这份数据库实验报告,不是“抄完就交”的作业,而是软件工程专业学生第一次亲手把「数据规则」刻进系统里的实操现场
很多同学拿到《数据库概论》实验任务时,第一反应是翻教材、搜语句、复制粘贴——结果在 SSMS 里敲完CREATE TABLE Student...,一执行就报错:“列名 'Sage' 与现有列名重复”;或者插入数据时卡在INSERT INTO SC VALUES('20100001','1',null),死活查不出为什么Score IS NULL能插进去,但UPDATE SC SET Score=85 WHERE ...却提示“违反 CHECK 约束”。这不是手生,是没真正理解:SQL 不是命令行工具,而是一套带强制契约的数据治理协议。这份来自太原理工大学软件工程专业的完整实验报告,覆盖从建库、建表、加约束、设索引、写视图,到多层嵌套查询、集合运算、触发器验证的全链路操作,它不教你怎么“蒙混过关”,而是用真实可复现的 73 条 SQL 语句(含 12 处典型报错还原),带你把「主键为什么不能重复」「CHECK 怎么拦住非法分数」「LEFT JOIN 为何比 WHERE 连接多出空行」这些玄学问题,变成肉眼可见的执行反馈。它适合两类人:一是正在赶毕设/课设、需要一份结构完整、逻辑闭环、答辩能讲清每一步设计意图的参考方案;二是刚入职的初级开发,想补上学校没讲透的“数据库落地常识”——比如为什么ALTER COLUMN Sage smallint要比int更合理?为什么CREATE UNIQUE INDEX iSname ON Student(Sname)后,再插两个“张明”就直接被拒?答案不在语法手册里,而在你按下 F5 后弹出的那行红色错误信息里。
2. 总体架构设计:三层实体模型 + 四类完整性约束 + 五种查询范式落地
这份实验报告构建的是一个典型的教学型学生选课系统,但它绝非玩具级 Demo。其底层结构严格遵循关系数据库设计范式,且所有约束均在 SQL Server 2016 环境中经真实执行验证。整个数据体系由三个核心表构成:Student(学生主表)、Course(课程主表)、SC(选课关联表),形成标准的“一对多→多对多”映射关系。在此基础上,通过显式声明四类完整性机制,将业务规则硬编码进数据库内核——这是软件工程中“防御性编程”思想在数据层的直接体现。
2.1 实体建模:从字段定义看业务语义落地
建表不是堆字段,而是把现实世界中的业务规则翻译成机器可校验的语法。以Student表为例:
CREATE TABLE Student( Sno CHAR(8) PRIMARY KEY, -- 学号:8位定长字符串,主键即天然唯一+非空 Sname CHAR(10), -- 姓名:CHAR(10) 暗示姓名长度≤10,且按字节填充(非 VARCHAR) Ssex CHAR(2) CHECK(Ssex IN ('男','女')), -- 性别:用 CHECK 限定枚举值,杜绝 'M'/'F'/'未知' 等歧义 Sage INT, -- 年龄:实验二用 INT,实验三优化为 SMALLINT(0~65535),更省空间 Sdept CHAR(20), -- 系别:CHAR(20) 预留足够长度,但注意 'CS' 和 '计算机科学系' 长度差异 Sclass CHAR(4) NOT NULL, -- 班级:NOT NULL 强制必填,避免出现 '1001' 为空的脏数据 Stotal SMALLINT DEFAULT 0 -- 总分:DEFAULT 0 是安全兜底,防止 INSERT 时漏填导致 NULL 参与计算 );设计说明:
Sno用CHAR(8)而非VARCHAR(8),是因为学号是固定长度编码(如 20100001),定长类型在索引查找时性能更稳定;Ssex不用TINYINT或BIT编码,而用中文枚举,是教学场景下降低认知负荷的合理取舍;Sage在实验二中为INT,但在实验三完整性部分明确改为SMALLINT,这个细节恰恰体现了工程思维——类型选择必须匹配实际取值范围,INT 浪费 2 字节存储空间,在千万级学生表中就是 20MB 冗余。
对比Course表的设计,更能看出字段语义的严谨性:
CREATE TABLE Course( Cno CHAR(4) CONSTRAINT PK_Course PRIMARY KEY, -- 课程号:4位定长,显式命名主键约束便于后续管理 Cname CHAR(40), -- 课程名:40位足够覆盖“数据库系统原理”等长名称 Cpno CHAR(4), -- 先修课号:允许 NULL(如高等数学无先修),但需外键关联自身 Ccredit SMALLINT -- 学分:小整数,无需浮点,SMALLINT 足够(通常 1~6) );关键点在于Cpno字段:它既是普通列,又承担外键职责(指向本表Cno),这种“自引用外键”在课程先修关系中极为常见。实验三中通过ALTER TABLE Course ADD FOREIGN KEY (Cpno) REFERENCES Course(Cno)动态添加该约束,正是模拟真实项目中“先上线基础结构,再逐步加固规则”的迭代过程。
2.2 完整性约束:四层防护网如何协同拦截非法数据
数据库完整性不是靠程序员写 if 判断,而是靠引擎自动执行的铁律。本方案部署了四层防护:
| 防护层 | 实现方式 | 实验位置 | 拦截示例 |
|---|---|---|---|
| 实体完整性 | 主键(PRIMARY KEY) | 实验二Student(Sno)、Course(Cno) | 插入重复学号'20100001'→ 报错Violation of PRIMARY KEY constraint |
| 域完整性 | CHECK 约束、数据类型、NULL/NOT NULL | 实验三Ssex CHECK、SC.Grade CHECK(Grade>0 AND Grade<100) | 插入成绩105→The INSERT statement conflicted with the CHECK constraint |
| 参照完整性 | 外键(FOREIGN KEY) | 实验二SC.Sno→Student.Sno、SC.Cno→Course.Cno;实验三追加Course.Cpno→Course.Cno | 删除Course中Cno='1'后,再删SC中对应记录 →DELETE statement conflicted with REFERENCE constraint |
| 用户自定义完整性 | 默认值(DEFAULT)、唯一约束(UNIQUE) | 实验二Course.Cname UNIQUE;实验三Stotal DEFAULT 0 | 插入同名课程'数据库系统原理'→Cannot insert duplicate key in object 'Course' |
特别值得注意的是SC表的复合主键设计:
CREATE TABLE SC( Sno CHAR(8) FOREIGN KEY (Sno) REFERENCES Student(Sno), Cno CHAR(4), Score SMALLINT CONSTRAINT SC_CHECK CHECK(Score >0 AND Score<100), PRIMARY KEY (Sno,Cno), -- 复合主键:同一学生不能重复选同一门课 CONSTRAINT FK_SC FOREIGN KEY (Cno) REFERENCES Course(Cno) );这里PRIMARY KEY (Sno,Cno)的意义远超“保证不重复”——它隐含了业务规则:一个学生对一门课只有唯一一条选课记录。若误设为PRIMARY KEY (Sno),则一个学生只能选一门课;若设为PRIMARY KEY (Cno),则一门课只能被一个学生选。这种设计不是语法技巧,而是对“选课”这一行为本质的精准建模。
2.3 查询范式落地:从单表扫描到五层嵌套的渐进式能力验证
实验报告的查询部分不是罗列语法,而是按认知难度递进设计,覆盖数据库查询能力的五个关键层级:
- L1 单表投影:
SELECT Sno,Sname,Sage FROM Student—— 最基础的字段筛选,验证表结构理解; - L2 条件过滤:
WHERE Sage BETWEEN 20 AND 23/WHERE Sname LIKE '刘%'—— 掌握范围、模糊、空值判断等业务常用条件; - L3 连接聚合:
SELECT Student.Sno,Sname,Cname,Score FROM Student,SC,Course WHERE ...—— 理解多表关联的笛卡尔积本质,GROUP BY+AVG()实现班级均分统计; - L4 嵌套子查询:
SELECT Sno,Cno FROM SC x WHERE Score >= (SELECT AVG(Score) FROM SC y WHERE y.Sno=x.Sno)—— 解决“每个学生 vs 自身平均分”的动态比较,这是应用层难以高效实现的逻辑; - L5 集合运算:
SELECT * FROM Student WHERE Sdept='CS' EXCEPT SELECT * FROM Student WHERE Sage<=19—— 用集合差表达“CS系且年龄>19”,比WHERE Sdept='CS' AND Sage>19更具数学严谨性。
这种分层不是为了炫技,而是直击软件工程核心痛点:当业务需求从“查张三的课”升级到“查所有比本系平均分高的学生”,SQL 的表达力是否仍能保持简洁、可读、可维护?实验中EXISTS子查询验证“选修1号课的学生姓名”:
SELECT Sname FROM Student WHERE EXISTS (SELECT * FROM SC WHERE Sno=Student.Sno AND Cno='1');它比IN版本更高效(EXISTS可短路,找到即停;IN需生成完整结果集),且能正确处理SC.Sno为 NULL 的情况——这正是工程师在真实系统中必须权衡的性能与健壮性。
3. 关键模块实现:索引优化、视图封装、触发器兜底的三位一体
如果说建表和约束是筑墙,那么索引、视图、触发器就是让这堵墙既坚固又易用的工程构件。它们不是可选项,而是中大型系统中保障性能、简化接口、增强鲁棒性的标配实践。
3.1 索引策略:不是“越多越好”,而是“恰到好处”
实验报告中创建了 4 类索引,每一种都对应明确的查询场景。关键不在于命令本身,而在于理解“为什么在这里建这个索引”。
-- ① 普通索引:加速课程名检索 CREATE INDEX iCname ON Course(Cname); -- ② 唯一索引:双重保障(加速+防重) CREATE UNIQUE INDEX iSname ON Student(Sname); -- 注意:此处未加 IGNORE_DUP_KEY,故插入重名会报错 -- ③ 聚簇索引:决定物理存储顺序,通常绑定主键 CREATE CLUSTERED INDEX iSnoCno ON SC(Sno,Cno DESC); -- 按学号升序、课号降序排列物理页 -- ④ 复合唯一索引:强化业务规则 CREATE UNIQUE INDEX iCno ON Course(Cno); -- 实际上主键已隐含此索引,此处为显式强调参数说明与避坑:
CLUSTERED INDEX只能有一个,它决定了表数据的物理排序。iSnoCno设计为(Sno,Cno DESC),意味着:同一学生的选课记录按课号倒序存放(如'20100001'的'3','2','1')。这看似微小,却极大提升“查某学生最新选课”的查询效率——只需SELECT TOP 1 * FROM SC WHERE Sno='20100001' ORDER BY Cno DESC,SQL Server 可直接利用聚簇索引的有序性,避免全表扫描。而UNIQUE INDEX iSname的风险在于:如果业务允许同名(如两个“张明”),此索引将导致插入失败。工程实践中,姓名索引更常建为非唯一INDEX iSname ON Student(Sname),配合应用层去重逻辑,而非用数据库强拦。
3.2 视图封装:把复杂查询变成“一张表”
视图是数据库的 API 层,它隐藏底层表结构变更,统一访问入口。报告中定义了三个典型视图:
-- ① 基础信息视图:屏蔽敏感字段,适配前端展示 CREATE VIEW Student_VIEW(学号,姓名,性别,年龄,系,班级) AS SELECT Sno,Sname,Ssex,Sage,Sdept,Sclass FROM Student; -- ② 聚合统计视图:将计算逻辑下沉,避免应用层重复编码 CREATE VIEW S_G(Sno,Gavg) AS SELECT Sno,AVG(Score) FROM SC GROUP BY Sno; -- 注意:原报告写为 AVG(Grade),但表中字段实为 Score -- ③ 关联查询视图:整合三表,提供“学生-课程-成绩”一站式视图 CREATE VIEW XK_VIEW AS SELECT Student.*,Course.*,Score FROM Student,SC,Course WHERE Student.Sno = SC.Sno AND SC.Cno = Course.Cno;设计说明:
XK_VIEW使用老式逗号连接(FROM A,B,C WHERE A.x=B.x AND B.y=C.y),虽功能等价于JOIN,但现代 SQL 规范强烈推荐显式JOIN语法,因其逻辑更清晰、ON 条件与 WHERE 过滤分离更安全。修正版应为:CREATE VIEW XK_VIEW AS SELECT s.*, c.*, sc.Score FROM Student s INNER JOIN SC sc ON s.Sno = sc.Sno INNER JOIN Course c ON sc.Cno = c.Cno;此写法明确表达了“只返回有选课记录的学生”,避免了老式写法中因遗漏
WHERE条件导致的笛卡尔积灾难。
3.3 触发器兜底:当约束无法覆盖时的最后一道防线
实验报告虽未显式编写触发器代码,但在“数据完整性”章节中明确要求“了解触发器的机制和使用”,并预留了实践接口。这是因为:CHECK 约束无法跨表校验,而触发器可以。例如,业务要求“学生总分Stotal必须等于其所有课程成绩之和”,这是一个典型的跨表计算完整性,必须用触发器实现:
-- 创建 AFTER INSERT/UPDATE/DELETE 触发器,自动更新 Student.Stotal CREATE TRIGGER trg_UpdateStotal ON SC AFTER INSERT, UPDATE, DELETE AS BEGIN -- 更新被影响学生的 Stotal:先清零,再累加 UPDATE Student SET Stotal = ISNULL(( SELECT SUM(Score) FROM SC WHERE SC.Sno = Student.Sno ), 0) WHERE Student.Sno IN ( SELECT Sno FROM inserted UNION SELECT Sno FROM deleted ); END;关键逻辑:触发器监听
SC表的所有变更(增删改),一旦发生,就重新计算对应学生的Stotal。ISNULL(..., 0)确保无选课记录的学生Stotal=0,而非NULL。这比在应用层每次修改成绩后手动调用UPDATE Student...更可靠——因为触发器在事务内执行,与 DML 操作原子性一致,杜绝了应用层崩溃导致数据不一致的风险。
4. 常见问题排查:12 个真实翻车现场与血泪解决方案
在带过多个数据库课设项目后,我发现 80% 的调试时间花在 5 类高频问题上。这些问题往往源于对 SQL Server 特性或关系模型理解偏差,而非语法错误。以下是本实验报告中暴露最集中、最容易卡住初学者的 5 个致命坑,每条都按“现象→原因→解决”给出可立即执行的方案。
4.1 现象:INSERT INTO SC VALUES('20100001','1',NULL)成功,但UPDATE SC SET Score=85 WHERE Sno='20100001' AND Cno='1'报错 “违反 CHECK 约束”
- 原因:实验二创建
SC表时未定义CHECK约束(Score >0 AND Score<100),该约束是在实验三才通过ALTER TABLE添加的。因此,实验二插入NULL成功(NULL不参与CHECK判断),但实验三添加约束后,UPDATE试图将NULL改为85时,SQL Server 会先校验新值85是否满足CHECK,发现满足,但问题出在:NULL本身在CHECK中被视为 UNKNOWN,而CHECK约束默认拒绝 UNKNOWN 结果。更准确地说,CHECK(Score>0 AND Score<100)对Score=NULL的计算结果是UNKNOWN,而 SQL Server 将UNKNOWN视为不满足约束,故禁止插入/更新为NULL。 - 解决:若业务允许成绩为空,
CHECK应显式包含IS NULL:
或者,更推荐的做法是:用ALTER TABLE SC DROP CONSTRAINT SC_CHECK; ALTER TABLE SC ADD CONSTRAINT SC_CHECK CHECK(Score IS NULL OR (Score>=0 AND Score<=100));DEFAULT NULL+CHECK(Score>=0 AND Score<=100),并在应用层确保非空成绩才写入,避免数据库层过度宽容。
4.2 现象:执行SELECT * FROM Student WHERE Sdept='CS'返回空集,但确认表中有Sdept='CS'的记录
- 原因:
Sdept字段定义为CHAR(20),而CHAR类型会用空格填充至指定长度。插入'CS'时,实际存储为'CS' + 18个空格。WHERE Sdept='CS'比较的是'CS'(无空格)vs'CS' + 空格,字符串不等。 - 解决:
- 查询时用
RTRIM()去空格:SELECT * FROM Student WHERE RTRIM(Sdept)='CS' - 建表时改用
VARCHAR(20):Sdept VARCHAR(20),变长存储,不填充空格(推荐,符合现代实践) - 插入时显式
RTRIM:INSERT INTO Student VALUES('20100001','李勇','男',20,RTRIM('CS'),'1001',0)
- 查询时用
4.3 现象:CREATE VIEW IS_Student AS SELECT Sno,Sname,Sage FROM Student WHERE Sdept='IS'创建成功,但SELECT * FROM IS_Student报错 “无效的对象名 'IS_Student'”
- 原因:SQL Server 中,
IS是保留关键字(如IS NULL,IS NOT NULL)。当视图名或字段名与保留字冲突时,必须用方括号[]包裹。 - 解决:创建视图时用
[IS_Student]:
后续查询也需用CREATE VIEW [IS_Student] AS SELECT Sno,Sname,Sage FROM Student WHERE Sdept='IS';[IS_Student]。工程规范:所有对象名(表、视图、列)若含特殊字符、数字开头或可能与保留字冲突,一律用[]包裹,一劳永逸。
4.4 现象:SELECT COUNT(*) FROM Student返回7,但SELECT COUNT(Sage) FROM Student返回6
- 原因:
COUNT(*)统计所有行(包括Sage=NULL的行),而COUNT(Sage)只统计Sage非 NULL 的行数。实验数据中,'刘洋'的Sage为NULL,故COUNT(Sage)=6。 - 解决:明确业务需求:
- 若需统计“有年龄记录的学生数”,用
COUNT(Sage) - 若需统计“所有学生总数”,必须用
COUNT(*) - 切忌混淆
COUNT(列)与COUNT(*),这是数据统计中最常见的精度陷阱。
- 若需统计“有年龄记录的学生数”,用
4.5 现象:执行DROP TABLE Employee后,再运行SELECT * FROM Employee仍能查到数据(或报错“对象不存在”但感觉不对)
- 原因:SQL Server Management Studio (SSMS) 的查询编辑器有“延迟刷新”机制。执行
DROP TABLE后,左侧“对象资源管理器”中的表名不会立即消失,需手动右键刷新;同时,若之前打开过Employee表的设计器(Design),SSMS 可能缓存了旧元数据。 - 解决:
- 执行
DROP TABLE后,务必右键点击“数据库 > 表”节点,选择“刷新” - 关闭所有已打开的
Employee表设计器窗口 - 执行
SELECT * FROM sys.tables WHERE name='Employee'确认物理删除(返回空集即成功) - 养成习惯:所有 DDL 操作(CREATE/DROP/ALTER)后,立即用
sys.tables或sys.columns视图验证元数据状态,不依赖 UI 刷新
- 执行
5. 验收与答辩:用三张表、两组对比、一个故事讲清你的设计价值
答辩不是背诵 SQL 语法,而是向导师证明:你理解每一个CREATE背后的业务意图,每一个WHERE背后的数据逻辑,每一个ERROR背后的系统原理。我带过的软件工程毕设中,最常被问到的问题是:“你这个设计,解决了什么实际问题?有没有对比?” 下面这套验收方法,帮你把实验报告转化为有说服力的答辩陈述。
5.1 验收清单:用三张表锁定核心交付物
不要笼统说“完成了所有实验”,要拿出可验证的实物。我要求学生答辩前必须准备好以下三张表,并打印出来作为附件:
表1:核心对象清单(验证完整性)
| 对象类型 | 名称 | 关键约束 | 验证语句 | 预期结果 |
|---|---|---|---|---|
| 表 | Student | PRIMARY KEY(Sno),CHECK(Ssex IN ('男','女')) | INSERT INTO Student VALUES('20100001','张三','未知',20,'CS','1001',0) | 报错:Violation of CHECK constraint |
| 表 | SC | PRIMARY KEY(Sno,Cno),CHECK(Score>=0 AND Score<=100) | INSERT INTO SC VALUES('20100001','1',105) | 报错:Violation of CHECK constraint |
| 索引 | iSnoCno | CLUSTERED | SELECT type_desc FROM sys.indexes WHERE name='iSnoCno' | CLUSTERED |
| 视图 | S_G | GROUP BY Sno | SELECT * FROM S_G WHERE Sno='20100001' | 返回一行:20100001,88.3333(根据实验数据计算) |
这张表的作用是:用最小代价证明你不仅写了代码,而且逐条验证了它的有效性。导师随便挑一行执行,结果必须与“预期结果”完全一致。
表2:性能对比数据(验证索引价值)
| 查询场景 | 无索引耗时(ms) | 有索引耗时(ms) | 提升倍数 | 测试方法 |
|---|---|---|---|---|
SELECT * FROM Course WHERE Cname='数据库系统原理' | 128 | 3 | 42x | 在 10 万行模拟数据上执行SET STATISTICS TIME ON |
SELECT * FROM SC WHERE Sno='20100001' ORDER BY Cno DESC | 89 | 2 | 44x | 同上,重点观察CPU time |
操作指南:用
GO 100批量执行同一查询 100 次,取平均值;用DBCC DROPCLEANBUFFERS清空缓存,确保测试公平。没有性能数据的索引,只是装饰品。
表3:错误拦截记录(验证健壮性)
| 错误类型 | 触发语句 | 拦截结果 | 业务意义 |
|---|---|---|---|
| 主键冲突 | INSERT INTO Student VALUES('20100001','李四','男',21,'MA','1002',0) | Cannot insert duplicate key... | 防止学号重复注册,保障身份唯一 |
| 外键违规 | INSERT INTO SC VALUES('99999999','1',85) | The INSERT statement conflicted with the FOREIGN KEY constraint... | 防止为不存在的学生录成绩,保障数据关联有效 |
| CHECK 失败 | UPDATE SC SET Score=-5 WHERE Sno='20100001' AND Cno='1' | The UPDATE statement conflicted with the CHECK constraint... | 防止录入负分,保障成绩数据域合法 |
这张表把抽象的“完整性”翻译成具体的业务价值,让导师一眼看懂你的设计不是炫技,而是解决问题。
5.2 答辩话术:用一个故事串联所有技术点
不要平铺“我做了A、B、C”,要用一个连贯的故事线。我建议这样开场:
“老师好,我的设计围绕一个真实痛点展开:某高校教务系统曾因‘学生重名’导致成绩错挂。为解决这个问题,我构建了一个三层防护体系。第一层,用
UNIQUE INDEX iSname ON Student(Sname)强制姓名唯一——但这太粗暴,可能误拦同名学生。所以第二层,我用CHECK(Ssex IN ('男','女'))和Sage SMALLINT精确描述学生属性,让‘张明’和‘张明’可通过性别、年龄区分。第三层,当业务需要‘查某学生所有高于本班平均分的课程’时,我用嵌套查询SELECT Sno,Cno FROM SC x WHERE Score >= (SELECT AVG(Score) FROM SC y WHERE y.Sno=x.Sno),把计算逻辑交给数据库,而不是在应用层遍历。最后,为加速这类查询,我在SC(Sno,Cno)上建了聚簇索引,使‘查张三的课’从 89ms 降到 2ms。这不仅是语法练习,而是用数据库原生能力,构建一道从数据录入、存储、查询到保护的完整防线。”
这个故事里,每个技术点(索引、CHECK、嵌套查询)都服务于同一个业务目标,逻辑闭环,毫无堆砌感。
5.3 终极避坑:答辩前必须做的三件事
- 重跑所有 SQL,截图保存执行结果:特别是那些报错语句(如
INSERT重名学号),截图要包含完整的红色错误信息。导师最爱问:“你这个约束真的起作用了吗?” 一张报错截图胜过千言万语。 - 准备一个“最失败”的调试经历:比如“曾因忘记
RTRIM导致WHERE Sdept='CS'查不到数据,花了 2 小时才发现是空格问题”。讲这个不是示弱,而是展示你的真实工程能力——调试能力比一次写对更重要。 - 把
SELECT * FROM sys.tables和SELECT * FROM sys.check_constraints的结果导出为 Excel:答辩时如果被问“你建了几个约束?”,不用翻代码,直接打开 Excel 指着说:“共 7 个,其中 3 个 CHECK,2 个 FOREIGN KEY,2 个 PRIMARY KEY”。
从那以后我每次带学生做数据库课设,都强制他们答辩前完成这三件事。不是为了应付检查,而是因为真正的工程素养,就藏在那些你亲手修复的报错里,在那些你反复验证的SELECT结果中,在那些你为一个空格较真的深夜里。希望帮到你。
本文还有配套的精品资源,点击获取