简介这份《数据库ER图习题.pdf》面向数据库课程学习者与备考学生聚焦概念模型设计这一核心考点帮助读者通过大量真实业务场景练习实体、属性与联系的抽象方法。资源共1个PDF文件压缩包约83KB内容以题目与场景描述为主便于打印或电子阅读。目前已有2310人学习下载说明其在数据库学习群体中具有一定参考价值。习题覆盖仓库、商店、商品、车队、司机、银行储蓄、体育锦标赛、超市、大学、货运、人事、旅游、住院管理、电脑销售、证券等十余类场景每道题均给出实体集属性及联系类型读者可据此动手绘制ER图再对照联系基数与属性归属进行自查。通过集中训练能够加深对一对一、一对多、多对多联系以及弱实体、多值属性等概念的理解提升将需求描述转化为数据库概念模型的能力适合作为期末复习或课程设计的配套练习材料。1. 数据库ER图习题.pdf从画图到建表的完整训练路径很多人第一次拿到「数据库ER图习题.pdf」这类材料时会下意识把它当成一份画图练习——把矩形、菱形、椭圆连起来就完事。但真正做过数据库设计的人都知道ER图习题的核心价值不在图形本身而在于它逼你把「业务规则」翻译成「表结构约束」的完整思维链路。一份典型的ER图习题会给出一个业务场景描述比如学生选课、图书借阅、订单管理然后要求你识别实体、确定属性、画出联系并标注基数最后往往还要转成关系模式。这个过程覆盖了数据库课程设计中最容易翻车的几个环节弱实体识别、多值属性处理、三元联系拆解、基数判断。如果你正在准备数据库考试、课程设计或者刚入行需要补上概念设计这一环这份习题就是最小可用的训练集。接下来我会按「怎么读题→怎么画→怎么转表→怎么验证」的顺序把每一步拆到能直接动手的程度。2. 读题与实体识别从一段业务描述里捞出所有该建表的东西2.1 实体、属性、联系的判定标准拿到一道ER图习题第一步不是动笔画而是逐句拆解业务描述。实体是系统中需要独立记录信息的对象通常对应一个名词且这个名词有自己的一组属性。属性是描述实体的特征联系是实体之间的交互。判断一个名词是不是实体有个实操标准如果它需要被单独管理、有生命周期、会被其他对象引用那它就是实体如果它只是某个实体的描述性信息那就是属性。举个例子题目说「一个部门有多名员工每个员工属于一个部门员工有工号、姓名、入职日期部门有部门编号和部门名称」。这里「部门」和「员工」都是实体因为它们各自有独立属性且需要单独管理。「属于」是联系基数是部门1对员工N。但如果说「员工有家庭住址住址包括省、市、街道」那住址就是属性而不是实体——除非题目要求住址可以被多个员工共享或者需要单独维护地址库。常见翻车点是把多值属性当成实体。比如「员工有多个电话号码」电话号码本身没有独立生命周期它只是员工的一个多值属性。在ER图里用双椭圆表示转关系模式时单独建一张电话表主键是工号电话号码。这个判断如果做错后面整张图都会歪。2.2 基数与参与约束的标注方法基数标注是ER图习题里扣分最多的地方。基数描述的是一个实体通过某个联系能关联到多少个另一端的实体。常见的有1:1、1:N、M:N三种。参与约束分两种全部参与用双线表示和部分参与用单线表示。全部参与意味着这个实体中的每个实例都必须参与该联系部分参与则允许不参与。判断基数时我一般会造两个测试句。对于实体A和B之间的联系R问自己「一个A能对应几个B」和「一个B能对应几个A」。比如「学生选课」一个学生能选多门课一门课能被多个学生选所以是M:N。「课程有先修课」一门课只能有一个先修课假设一个先修课可以被多门课共享所以是1:N。参与约束的判断更微妙。题目如果说「每个员工必须属于一个部门」那员工对「属于」这个联系是全部参与画双线。如果说「每个部门可以没有员工」那部门是部分参与画单线。这个细节在转关系模式时直接影响外键是否允许为空。提示读题时把「必须」「至少」「可以」「允许」这些词圈出来它们直接对应参与约束。2.3 用一张检查表避免漏实体做完第一遍识别后用下面这张表逐项核对能挡掉大部分低级错误检查项判断问题常见遗漏独立生命周期这个对象是否需要单独建表把订单明细当成订单的属性多值属性同一个实体的某属性是否有多个值电话号码、地址、技能标签派生属性某属性是否可由其他属性算出年龄由出生日期算出不单独存储弱实体某实体的存在是否依赖于另一实体订单明细依赖于订单三元联系三个实体是否同时参与一个联系供应商-零件-项目这张表不是让你机械套用而是帮你形成条件反射。练过十几道题之后你读题时脑子里会自动跳出这些判断。3. 画图与转关系模式从Chen模型到表结构的六步操作3.1 用标准符号画出可检查的ER图ER图的符号体系不复杂矩形是实体椭圆是属性菱形是联系双椭圆是多值属性虚椭圆是派生属性双矩形是弱实体双菱形是弱实体对应的联系。画图工具用draw.io、Visio或者纸笔都行关键是布局要清晰连线不要交叉太多。画图时我习惯先画实体再画联系最后补属性。实体放在四角或两侧联系放中间。基数标注在连线上用1、N、M表示。参与约束用线条样式区分双线表示全部参与单线表示部分参与。下面是一个学生选课场景的ER图结构描述你可以照着在draw.io里复现实体学生学号姓名专业 实体课程课程号课程名学分 联系选课学生-课程M:N属性成绩 基数学生1..N选课课程1..N选课 参与约束学生全部参与每个学生至少选一门课程部分参与课程可以没人选这个结构转成关系模式时选课联系会变成一张独立的表主键是学号课程号成绩作为该表的属性。3.2 把ER图转成关系模式的规则与代码转关系模式有一套成熟的规则我把它整理成可执行的步骤第一步每个强实体转一张表实体的属性变成列实体的主键变成表的主键。第二步1:1联系可以合并到任意一端通常合并到全部参与的那一端把另一端的主键作为外键加进来。第三步1:N联系把1端的主键作为外键加到N端。第四步M:N联系必须单独建表主键是两端主键的组合联系的属性变成该表的列。第五步多值属性单独建表主键是原实体主键加上多值属性本身。第六步弱实体转表时主键是弱实体自己的部分键加上所属强实体的主键。用Python写一个简单的转换脚本把上面的规则固化下来方便你验证自己的手工转换结果# ER图转关系模式的简化脚本 # 输入实体和联系的描述字典 # 输出建表语句列表 def er_to_schema(entities, relationships): tables [] # 第一步强实体转表 for name, attrs in entities.items(): pk attrs[pk] cols , .join([f{a} VARCHAR(50) for a in attrs[columns]]) tables.append(fCREATE TABLE {name} ({cols}, PRIMARY KEY ({pk}));) # 第二步到第四步处理联系 for rel in relationships: if rel[type] 1:N: # 把1端主键加到N端 one_pk entities[rel[one]][pk] tables.append(fALTER TABLE {rel[many]} ADD COLUMN {one_pk} VARCHAR(50);) tables.append(fALTER TABLE {rel[many]} ADD FOREIGN KEY ({one_pk}) REFERENCES {rel[one]}({one_pk});) elif rel[type] M:N: # 单独建表 left_pk entities[rel[left]][pk] right_pk entities[rel[right]][pk] extra rel.get(attrs, []) extra_cols , .join([f{a} VARCHAR(50) for a in extra]) col_def f{left_pk} VARCHAR(50), {right_pk} VARCHAR(50) if extra_cols: col_def f, {extra_cols} tables.append(fCREATE TABLE {rel[name]} ({col_def}, PRIMARY KEY ({left_pk}, {right_pk}));) return tables # 示例学生选课 entities { Student: {pk: sno, columns: [sno, sname, major]}, Course: {pk: cno, columns: [cno, cname, credit]} } relationships [ {name: Enroll, type: M:N, left: Student, right: Course, attrs: [grade]} ] for sql in er_to_schema(entities, relationships): print(sql)这段代码的逻辑很直白强实体直接建表1:N联系用ALTER TABLE加外键M:N联系单独建表并把两端主键组合成复合主键。参数方面entities字典里每个实体需要指定主键列和所有列relationships列表里每个联系需要指定类型和涉及的实体。实际做题时你不需要写代码但用这段脚本跑一遍你的答案能快速发现漏掉的表或外键。3.3 用SQL建表语句反向验证ER图转完关系模式后把建表语句实际跑一遍是检验ER图是否正确的硬标准。我一般会在SQLite或MySQL里建一个临时库把生成的DDL执行一遍然后插入几条测试数据看看约束是否合理。-- 学生选课场景的完整建表语句 CREATE TABLE Student ( sno VARCHAR(20) PRIMARY KEY, sname VARCHAR(50) NOT NULL, major VARCHAR(50) ); CREATE TABLE Course ( cno VARCHAR(20) PRIMARY KEY, cname VARCHAR(50) NOT NULL, credit INT CHECK (credit 0) ); CREATE TABLE Enroll ( sno VARCHAR(20), cno VARCHAR(20), grade DECIMAL(5,2), PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES Student(sno), FOREIGN KEY (cno) REFERENCES Course(cno) ); -- 测试数据 INSERT INTO Student VALUES (S001, 张三, 计算机); INSERT INTO Course VALUES (C001, 数据库, 3); INSERT INTO Enroll VALUES (S001, C001, 85.5);执行这段SQL时如果外键约束报错说明你的ER图里某个联系的方向或基数标错了。比如Enroll表插入时如果sno在Student表里不存在外键会拒绝插入这正好验证了「学生必须存在才能选课」这个参与约束。参数上CHECK (credit 0)这种约束在ER图里通常不会画出来但实际建表时应该加上这是从概念设计到物理设计的自然延伸。注意ER图习题的答案不唯一但建表语句能跑通、数据能插入、约束能生效是判断答案是否合理的底线。4. 避坑与排查ER图习题里最容易翻车的五个地方4.1 弱实体主键漏掉所属实体主键现象订单明细表的主键只写了明细编号插入两条不同订单的相同明细编号时主键冲突。原因弱实体的主键必须包含所属强实体的主键。订单明细的存在依赖于订单单独一个明细编号在不同订单下可以重复所以主键应该是订单号明细编号。解决画ER图时弱实体用双矩形对应的联系用双菱形转表时主键写成复合主键。建表语句里PRIMARY KEY (order_no, detail_no)两个列都不能少。4.2 多值属性直接塞进实体表现象员工表里出现phone1, phone2, phone3这样的列或者用逗号分隔的电话号码字符串。原因把多值属性当成了普通属性。多值属性在ER图里应该用双椭圆表示转关系模式时必须单独建表。解决建一张EmployeePhone表主键是工号电话号码外键引用员工表。这样电话号码数量不受限制查询也方便。如果题目明确说最多三个电话那可以用三个列但这是物理设计的妥协概念设计阶段仍然应该按多值属性处理。4.3 三元联系拆成三个二元联系现象供应商、零件、项目三个实体之间的供应联系被拆成供应商-零件、零件-项目、供应商-项目三个二元联系。原因没有识别出三元联系。三元联系表示三个实体同时参与一个事件拆成二元联系会丢失语义。比如「供应商S向项目P供应零件C」这个事实拆成三个二元联系后无法表达「哪个供应商向哪个项目供应了哪个零件」。解决三元联系用菱形连接三个实体转关系模式时单独建一张表主键是三个实体主键的组合。如果三元联系上有属性比如供应数量也放在这张表里。4.4 基数方向标反现象把「一个部门有多名员工」标成了员工1对部门N。原因读题时没有分清「一」和「多」分别对应哪一端。基数标注的是「一个A对应几个B」不是「一个B对应几个A」。解决造测试句。「一个部门有几名员工多名。一名员工属于几个部门一个。」所以部门是1端员工是N端。在连线上靠近部门的一端标1靠近员工的一端标N。这个习惯能挡掉90%的基数错误。4.5 派生属性单独建列现象学生表里同时有出生日期和年龄两个列插入数据时年龄需要手动算。原因没有识别派生属性。派生属性可以由其他属性计算得出在ER图里用虚椭圆表示转关系模式时不应该单独建列。解决只保留出生日期年龄在查询时用YEAR(CURDATE()) - YEAR(birthdate)计算。如果性能要求高可以用物化视图或触发器维护但概念设计阶段不建列。5. 进阶验证用反例数据和查询语句检验ER图的完整性5.1 构造边界数据测试约束ER图转成表之后光看建表语句还不够得用边界数据去撞约束。我一般会准备三类测试数据正常数据、边界数据、非法数据。正常数据验证基本功能边界数据验证约束的边界非法数据验证约束是否能拦住。以学生选课为例正常数据是学生选了课有成绩。边界数据是一个学生选了0门课如果参与约束是部分参与、一门课被0个学生选。非法数据是选课表里插入一个不存在的学号或者成绩超出0-100范围。如果非法数据能插进去说明你的约束漏了。-- 测试非法数据学号不存在 INSERT INTO Enroll VALUES (S999, C001, 90); -- 预期外键约束报错插入失败 -- 测试边界数据成绩为NULL INSERT INTO Enroll VALUES (S001, C002, NULL); -- 如果题目允许成绩暂缺这条应该成功 -- 测试边界数据选课数为0的学生 SELECT s.sno, COUNT(e.cno) FROM Student s LEFT JOIN Enroll e ON s.sno e.sno GROUP BY s.sno HAVING COUNT(e.cno) 0; -- 如果参与约束是全部参与这个查询应该返回空这三条语句跑一遍你就能知道自己的ER图在参与约束和域约束上有没有漏洞。参数上LEFT JOIN用来找出没有选课的学生HAVING COUNT 0筛选出选课数为零的记录。如果题目要求学生必须选课这个查询返回任何行都说明你的约束没生效。5.2 用查询反推联系是否合理另一个验证角度是从查询需求反推ER图。假设题目要求「查询每个学生的选课总学分」如果你的ER图里选课联系没有记录成绩或者课程表里没有学分这个查询就写不出来。反过来如果所有题目要求的查询都能在你的表结构上写出来说明ER图基本完整。我习惯把习题里隐含的查询需求列出来逐条写SQL验证。比如-- 查询每个学生的选课总学分 SELECT s.sname, SUM(c.credit) AS total_credit FROM Student s JOIN Enroll e ON s.sno e.sno JOIN Course c ON e.cno c.cno GROUP BY s.sno, s.sname; -- 查询没有学生选的课程 SELECT c.cname FROM Course c LEFT JOIN Enroll e ON c.cno e.cno WHERE e.sno IS NULL; -- 查询选了超过3门课的学生 SELECT s.sname, COUNT(e.cno) AS course_count FROM Student s JOIN Enroll e ON s.sno e.sno GROUP BY s.sno, s.sname HAVING COUNT(e.cno) 3;这三条查询覆盖了聚合、外连接、分组筛选如果都能跑通且结果符合预期说明你的ER图在实体、联系、属性三个层面都没有大问题。如果某条查询写不出来比如课程表里没有学分列那就回去检查ER图里课程实体的属性是否完整。5.3 一个我常用的检查习惯做完一道ER图习题后我会把答案放一晚上第二天不看原图只看建表语句和查询需求试着在纸上重新画一遍ER图。如果画出来的图和昨天的一致说明思路是清晰的如果不一致说明有些联系或属性当时是蒙的需要回去重新读题。这个习惯帮我抓出了不少「当时觉得对、其实没想清楚」的地方。另外我习惯把每道题的ER图和建表语句存在一个文件夹里按业务场景命名。过一段时间回头翻能明显看出自己在基数判断和弱实体处理上的进步。ER图习题的价值不在于做对一道题而在于形成一套从业务描述到表结构的条件反射。这套反射练成了后面做真实的数据库设计时读需求文档的速度会快很多。希望帮到你。本文还有配套的精品资源点击获取