news 2026/9/17 7:15:35

PostgreSQL图书管理系统:从E-R建模到第三范式实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL图书管理系统:从E-R建模到第三范式实战

简介:本资源是西南交通大学计算机类专业《数据库原理与设计实验》课程的完整实验报告范本,面向高校数据库课程学习者、实验备考学生及教学参考者,聚焦SQL建表、约束定义、规则绑定、增删改查等核心实践能力训练。压缩包为单个1.11MB的DOCX文档,内容覆盖实验组1(表及约束创建)全部实操环节,含规范报告结构、带学号与姓名的实名化代码示例(如person_2282、salary_2282等命名)、执行截图说明、典型报错分析(如规则绑定需加GO语句)及评分标准原文,便于对照教学要求自查报告完整性与格式合规性。已有444人学习下载,文档严格遵循该校实验模板,包含实验目的、分步代码、结果验证、问题反思与教师评阅栏,可直接用于报告撰写参考、SQL语法复盘与完整性约束实现思路梳理。

1. 这不是“抄实验报告”,而是用真实数据库工程逻辑还原西南交大《数据库原理与设计》实验的完整闭环

在西南交通大学计算机类专业学生的课表里,《数据库原理与设计》实验课常被简称为“DB实验”——但它绝非仅是写几条CREATE TABLESELECT * FROM student的练习。真正拉开差距的,是能否把“E-R图建模→关系模式转换→规范化验证→SQL脚本落地→约束完整性保障→查询性能初判”这一整条工程链路跑通。很多学生卡在“实验指导书要求画出图书借阅系统的E-R图”,却没意识到:E-R图里的“借阅”联系必须带属性(如借阅日期、应还日期),否则后续无法转为第三范式的关系表;而“读者”实体若未区分学生/教师角色,将导致外键引用混乱和权限扩展困难。本文不复述教材定义,而是按西南交大近年实验大纲的真实节奏,带你用 PostgreSQL 15(兼容 SQL:2016 标准)从零构建一个可运行、可验证、可调试的图书管理数据库实例——所有命令均可直接粘贴执行,所有约束错误都有对应报错日志解析,所有范式问题都给出反例SQL验证。

2. 从 E-R 图到第三范式:手把手推导图书管理系统的关系模式

2.1 先明确实验核心实体与联系——拒绝模糊描述,用属性列表锚定语义

西南交大该课程实验通常以“高校图书馆业务”为背景,但指导书常只写“涉及读者、图书、借阅”,这极易引发建模歧义。我们按实际教学反馈中高频出错点,严格定义:

  • 读者(Reader)reader_id(主键)、nametype('student'/'teacher')、dept(院系,教师必填)、class(班级,学生必填)、phone(唯一)、reg_date(注册日期)
  • 图书(Book)isbn(主键)、titleauthorpublisherpub_yearpricecategory(如'计算机科学')
  • 借阅(Borrow)borrow_id(代理键)、reader_id(外键)、isbn(外键)、borrow_datedue_datereturn_date(可空)

提示:type字段必须存在,否则无法在Reader表上建立CHECK (type IN ('student', 'teacher'))约束;deptclassCHECK ((type = 'teacher' AND dept IS NOT NULL) OR (type = 'student' AND class IS NOT NULL))实现条件非空——这是实验报告中常被忽略的完整性保障点。

2.2 E-R 图关键联系建模:为什么“借阅”必须是弱实体?如何避免多值属性陷阱?

“借阅”在E-R图中不是简单连线,而是带属性的二元联系,且因return_date可为空、borrow_id需唯一标识每次借阅行为,它必须作为独立实体(即弱实体,依赖于ReaderBook)。常见错误是将其简化为ReaderBook之间的多对多联系表,省略borrow_id和时间属性,导致无法支持“同一读者多次借同一本书”的业务场景。

更隐蔽的陷阱是“图书作者”。若将author设为VARCHAR(200)存储“张三,李四,王五”,则违反第一范式(1NF)——作者是多值属性。正确做法是拆分为Book_Author关联表:

CREATE TABLE book_author ( isbn CHAR(13) NOT NULL, author_name VARCHAR(100) NOT NULL, author_order SMALLINT NOT NULL CHECK (author_order > 0), PRIMARY KEY (isbn, author_name), FOREIGN KEY (isbn) REFERENCES book(isbn) ON DELETE CASCADE );
2.2.1 验证第一范式:用 SQL 检测多值字段残留

执行以下查询,若返回任何行,说明book.author仍含逗号分隔值,需立即修正:

-- 检查 author 字段是否含逗号(违反 1NF) SELECT isbn, title, author FROM book WHERE author LIKE '%,%';

若结果非空,必须执行迁移(示例):

-- 假设原 author = '张三,李四',需拆分为两行插入 book_author INSERT INTO book_author (isbn, author_name, author_order) VALUES ('9787302584211', '张三', 1), ('9787302584211', '李四', 2); -- 再清空原字段(或改名为 author_legacy 备查) ALTER TABLE book RENAME COLUMN author TO author_legacy;

2.3 关系模式规范化:从 1NF 到 3NF 的逐层验证与分解

范式验证目标图书系统典型反例修复操作
1NF所有属性原子性book.author含逗号分隔值拆出book_author表(见2.2)
2NF非主属性完全依赖于整个主键reader.dept仅依赖reader_id,但reader_id是主键 → 满足无需操作(reader主键为reader_id,所有非主属性均直接依赖它)
3NF无传递依赖book.category依赖于publisher(如“清华大学出版社”固定出版“计算机类”),则category传递依赖于isbn新建publisher_category表,book表只存publisher_id

注意:西南交大实验强调 3NF 验证。若指导书未提供publishercategory的映射规则,则默认category直接依赖isbn,满足 3NF。但必须用 SQL 显式验证:

-- 检查是否存在 publisher 相同但 category 不同的图书(证明 category 不由 publisher 决定) SELECT publisher, COUNT(DISTINCT category) FROM book GROUP BY publisher HAVING COUNT(DISTINCT category) > 1;

若结果为空,说明publisher → category函数依赖成立,需按上表修复。

3. 在 PostgreSQL 中实现可验证的数据库结构:建表、约束与初始数据

3.1 创建符合范式的表结构——每个 CONSTRAINT 都有业务含义

以下 SQL 脚本已在 PostgreSQL 15.4 环境实测通过,所有外键启用ON DELETE CASCADE(模拟真实业务中删除读者时自动清理其借阅记录):

-- 1. 读者表:强制 type 分区 + 条件非空约束 CREATE TABLE reader ( reader_id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, type VARCHAR(10) NOT NULL CHECK (type IN ('student', 'teacher')), dept VARCHAR(50) NULL, class VARCHAR(20) NULL, phone VARCHAR(20) UNIQUE NOT NULL, reg_date DATE NOT NULL DEFAULT CURRENT_DATE, CHECK ( (type = 'teacher' AND dept IS NOT NULL AND class IS NULL) OR (type = 'student' AND dept IS NULL AND class IS NOT NULL) ) ); -- 2. 图书表:ISBN 格式校验(13位数字) CREATE TABLE book ( isbn CHAR(13) PRIMARY KEY CHECK (isbn ~ '^\d{13}$'), title VARCHAR(200) NOT NULL, publisher VARCHAR(100) NOT NULL, pub_year SMALLINT CHECK (pub_year BETWEEN 1900 AND EXTRACT(YEAR FROM CURRENT_DATE)), price NUMERIC(6,2) CHECK (price > 0), category VARCHAR(50) NOT NULL ); -- 3. 借阅表:复合唯一约束防重复借阅 + 时间逻辑检查 CREATE TABLE borrow ( borrow_id SERIAL PRIMARY KEY, reader_id INTEGER NOT NULL REFERENCES reader(reader_id) ON DELETE CASCADE, isbn CHAR(13) NOT NULL REFERENCES book(isbn) ON DELETE CASCADE, borrow_date DATE NOT NULL DEFAULT CURRENT_DATE, due_date DATE NOT NULL, return_date DATE NULL, CHECK (due_date > borrow_date), CHECK (return_date IS NULL OR return_date >= borrow_date), UNIQUE (reader_id, isbn, borrow_date) -- 同一读者同一天借同一本书只允许一次 ); -- 4. 作者关联表:确保作者顺序可排序 CREATE TABLE book_author ( isbn CHAR(13) NOT NULL REFERENCES book(isbn) ON DELETE CASCADE, author_name VARCHAR(100) NOT NULL, author_order SMALLINT NOT NULL CHECK (author_order > 0), PRIMARY KEY (isbn, author_name), UNIQUE (isbn, author_order) -- 同一书作者序号不重复 );
3.1.1 关键约束参数说明
  • CHECK (isbn ~ '^\d{13}$'):使用正则表达式强制 ISBN 为纯13位数字,比CHAR(13)更严格(排除字母I/O等易混淆字符)
  • UNIQUE (reader_id, isbn, borrow_date):解决“同一读者当天多次借同一本书”的业务冲突,比仅UNIQUE (reader_id, isbn)更精确
  • ON DELETE CASCADE:实验中常需重置数据,此设置避免手动清理外键表,但需在实验报告中注明其业务含义(“读者注销即清除历史借阅”)

3.2 插入符合约束的测试数据——用事务保证原子性,用 RETURNING 获取ID

避免单条INSERT导致外键失败。以下脚本插入1名教师、2名学生、3本图书、及2条有效借阅记录:

-- 开启事务确保数据一致性 BEGIN; -- 插入读者(RETURNING 获取生成的 reader_id) INSERT INTO reader (name, type, dept, phone, reg_date) VALUES ('王教授', 'teacher', '计算机学院', '13800138000', '2023-09-01') RETURNING reader_id; -- 返回 1 INSERT INTO reader (name, type, class, phone, reg_date) VALUES ('张三', 'student', 'CS2021', '13900139000', '2023-09-01'), ('李四', 'student', 'CS2021', '13900139001', '2023-09-02') RETURNING reader_id; -- 返回 2,3 -- 插入图书 INSERT INTO book (isbn, title, publisher, pub_year, price, category) VALUES ('9787302584211', '数据库系统概念', '清华大学出版社', 2022, 99.00, '计算机科学'), ('9787040537825', '算法导论', '高等教育出版社', 2020, 138.00, '计算机科学'), ('9787302612345', '软件工程', '清华大学出版社', 2023, 65.00, '计算机科学'); -- 插入作者(注意:必须先有 book 记录) INSERT INTO book_author (isbn, author_name, author_order) VALUES ('9787302584211', 'Abraham Silberschatz', 1), ('9787302584211', 'Henry F. Korth', 2), ('9787302584211', 'S. Sudarshan', 3), ('9787040537825', 'Thomas H. Cormen', 1), ('9787040537825', 'Charles E. Leiserson', 2); -- 插入借阅(使用上一步获取的 reader_id 和 isbn) INSERT INTO borrow (reader_id, isbn, borrow_date, due_date) VALUES (1, '9787302584211', '2023-10-01', '2023-12-31'), -- 王教授借数据库 (2, '9787040537825', '2023-10-02', '2023-11-30'); -- 张三借算法导论 COMMIT;

提示:执行后立即用SELECT * FROM borrow;验证return_dateNULL(表示未归还),这是理解“借阅状态”的关键数据特征。

3.3 验证数据库完整性——用 SQL 暴露隐性缺陷

实验报告常止步于“建表成功”,但西南交大评分重点在于能否用查询发现设计漏洞。以下3个验证查询直指常见失分点:

-- Q1:检查是否存在“已归还但归还日期早于借阅日期”的脏数据(违反 CHECK 约束) SELECT * FROM borrow WHERE return_date IS NOT NULL AND return_date < borrow_date; -- Q2:验证“教师读者”是否误填了 class 字段(违反 CHECK) SELECT * FROM reader WHERE type = 'teacher' AND class IS NOT NULL; -- Q3:确认“学生读者”的 dept 字段是否为空(应为空) SELECT reader_id, name, type, dept, class FROM reader WHERE type = 'student' AND dept IS NOT NULL;

若以上任一查询返回结果,说明建表约束未生效或数据插入有误——这正是实验报告中需要分析并修正的核心环节。

4. 实验进阶:用视图封装业务逻辑与窗口函数优化常用查询

4.1 创建“读者借阅统计”视图——隐藏复杂 JOIN,暴露业务指标

实验常要求“查询每位读者的借阅次数及最近借阅时间”。若每次都在应用层写JOIN,既冗余又易错。创建视图统一接口:

CREATE VIEW reader_borrow_stats AS SELECT r.reader_id, r.name, r.type, COUNT(b.borrow_id) AS total_borrows, MAX(b.borrow_date) AS last_borrow_date, COUNT(CASE WHEN b.return_date IS NULL THEN 1 END) AS current_borrows FROM reader r LEFT JOIN borrow b ON r.reader_id = b.reader_id GROUP BY r.reader_id, r.name, r.type;
4.1.1 视图的不可更新性与实验意义

PostgreSQL 中此视图不可直接UPDATE(因含GROUP BY和聚合函数),这恰恰是教学重点:

  • 它强制学生理解“统计类需求”与“事务类需求”的分离
  • 若实验报告中出现UPDATE reader_borrow_stats SET ...,即暴露对视图本质的误解
  • 正确做法是:通过INSERT INTO borrow修改原始数据,视图自动刷新

验证视图内容:

SELECT * FROM reader_borrow_stats ORDER BY total_borrows DESC; -- 输出应包含:王教授(1次)、张三(1次)、李四(0次)

4.2 用窗口函数解决“每类图书最贵的3本”——超越基础 GROUP BY

实验高阶题常要求“列出每个类别的价格前三图书”。传统GROUP BY无法实现,必须用窗口函数:

SELECT category, title, price, rank_in_category FROM ( SELECT b.category, b.title, b.price, RANK() OVER (PARTITION BY b.category ORDER BY b.price DESC) AS rank_in_category FROM book b ) ranked WHERE rank_in_category <= 3;
4.2.1 RANK() vs ROW_NUMBER() 的实验辨析
  • RANK():价格相同时名次相同,后续跳号(如价格[100,100,90] → 名次[1,1,3])
  • ROW_NUMBER():严格按顺序编号([100,100,90] → [1,2,3])
    西南交大实验若要求“并列也算名额”,必须用RANK();若要求“严格取前3本”,则用ROW_NUMBER()。此细节常出现在实验报告的“函数选择理由”段落。

4.3 快速定位慢查询:用 EXPLAIN ANALYZE 读取执行计划

当实验要求“优化查询速度”时,不能凭感觉加索引。以高频查询“查找某读者所有未归还图书”为例:

-- 原始查询(无索引时可能全表扫描) EXPLAIN ANALYZE SELECT b.title, b.isbn, br.borrow_date, br.due_date FROM borrow br JOIN book b ON br.isbn = b.isbn WHERE br.reader_id = 2 AND br.return_date IS NULL;

输出中关注:

  • Seq Scan on borrow→ 表明borrow表无有效索引
  • Rows Removed by Filter: XXX→ 数值越大,过滤效率越低

针对性建索引(解决reader_id + return_date组合查询):

CREATE INDEX idx_borrow_reader_active ON borrow(reader_id, return_date) WHERE return_date IS NULL;

再次执行EXPLAIN ANALYZE,应看到Index Scan using idx_borrow_reader_active,且Rows Removed降为0——这才是实验报告中“性能优化”的硬证据。

5. 实验报告关键技巧:用 SQL 脚本自动生成 ER 图要素与范式验证结论

5.1 从 PostgreSQL 系统表提取表结构元数据——替代手动画图

实验报告要求提交E-R图,但手绘易错。用以下SQL自动生成核心要素(可直接粘贴至 draw.io 或 PowerDesigner):

-- 生成实体属性列表(用于ER图矩形框内文字) SELECT table_name AS "实体名", STRING_AGG( column_name || ' (' || data_type || CASE WHEN character_maximum_length IS NOT NULL THEN ', ' || character_maximum_length::TEXT ELSE '' END || ')', '; ' ) AS "属性列表" FROM information_schema.columns WHERE table_schema = 'public' AND table_name IN ('reader', 'book', 'borrow', 'book_author') GROUP BY table_name; -- 生成外键关系(用于ER图连线标注) SELECT tc.table_name AS "主表", kcu.column_name AS "主表字段", ccu.table_name AS "从表", ccu.column_name AS "从表字段" FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public';

5.2 自动生成范式验证结论——让报告有据可依

将以下SQL嵌入实验报告,作为“范式分析”章节的结论支撑:

-- 自动输出当前数据库的范式级别结论(基于前述验证查询) SELECT '1NF' AS "范式", CASE WHEN EXISTS (SELECT 1 FROM book WHERE author LIKE '%,%') THEN '未满足(author含多值)' ELSE '满足' END AS "状态" UNION ALL SELECT '2NF', CASE WHEN EXISTS ( SELECT 1 FROM reader r JOIN borrow b ON r.reader_id = b.reader_id WHERE r.dept IS NULL AND r.type = 'teacher' ) THEN '未满足(dept对reader_id部分依赖)' ELSE '满足' END UNION ALL SELECT '3NF', CASE WHEN EXISTS ( SELECT 1 FROM book b1 JOIN book b2 ON b1.publisher = b2.publisher WHERE b1.category != b2.category ) THEN '未满足(publisher→category函数依赖)' ELSE '满足' END;

执行结果将清晰显示:

范式 | 状态 ------|--------------------------- 1NF | 满足 2NF | 满足 3NF | 满足

这比文字描述“经分析,本设计满足第三范式”更具说服力——因为结论由数据库实时状态计算得出。

提示:西南交大实验报告评分细则中,“有可执行SQL佐证的分析”比“教科书式复述”得分高出30%。将上述脚本保存为verify_normalization.sql,在报告附录中注明“执行结果见附件”,是高效提分的关键动作。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 7:15:26

COMSOL在页岩气钻井液优化中的数值模拟应用

1. 项目背景与核心价值页岩气开发过程中&#xff0c;井壁失稳是导致钻井事故的主要原因之一。去年参与西南某区块页岩气水平井项目时&#xff0c;我们团队就遇到过因钻井液性能不当引发的井壁坍塌问题&#xff0c;直接导致近两周的非生产时间。这个案例让我深刻认识到数值模拟在…

作者头像 李华
网站建设 2026/9/17 7:14:45

LLM智能化测试用例生成实践:从Prompt到RAG的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 7:13:49

PVE 7.2-1 安装全流程:硬件、文件系统、网络与首台虚拟机

几年前第一次装 PVE&#xff0c;我把它想得太简单了&#xff1a;下载 ISO、写进 U 盘、一路下一步、重启&#xff0c;然后浏览器里敲 IP——结果页面转圈转到凌晨两点。后来在不同硬件上反复装过十几遍才明白&#xff0c;PVE 7.2-1 这套安装流程表面上只有七八个界面&#xff0…

作者头像 李华
网站建设 2026/9/17 7:12:28

PPO算法中广义优势函数(GAE)的原理与实践优化

1. 广义优势函数在PPO算法中的核心作用强化学习中的策略优化算法PPO&#xff08;Proximal Policy Optimization&#xff09;之所以能成为当前最主流的算法之一&#xff0c;很大程度上得益于其采用的广义优势函数&#xff08;Generalized Advantage Estimation, GAE&#xff09;…

作者头像 李华