数据库ER图建模与关系模式转换:从实体关系到MySQL建表完整实践
发布时间:2026/10/9 18:45:28 锦皓数字建站

简介面向数据库初学者的ER图习题集以PDF形式收录了17个典型场景的概念模型设计题覆盖商业库存与销售、汽车运输、银行储蓄、体育锦标赛、超市连锁、大学教务、住院管理、证券业务等真实业务适合高校学生、考研备考者及需要练习数据库概念模型设计的人群。压缩包内为单个PDF文件大小仅83KB轻量便携可随时打开对照练习。目前已有2311人浏览学习。内容在每道题基础上给出实体、属性与联系解析例如仓库-商店-商品间的库存/销售/供应三元联系、车队-车辆-司机的聘用/拥有/使用关系等帮助读者抓住多元联系与基数判断的关键思路。通过完成这些习题可以系统提升ER图绘制能力为后续关系模式转换与数据库设计打下扎实基础。1. 为什么一份 ER 图习题集值得认真对待从这道题说开去无论是期末备考、面试刷题还是真正上手设计业务表结构ER 图都是绕不开的第一道关口。很多开发者一上来就写建表 SQL表建完才发现多对多关系漏了中间表、属性挂错了实体、外键方向反了最后只能靠ALTER TABLE反复补救。数据库 ER 图习题.pdf 这类资料之所以有用不是因为图本身难画而是它逼着你面对一个最容易被忽视的问题把一段业务描述翻译成结构正确的数据模型你的翻译规则到底稳不稳。我见过不少能把 SQL 写得飞快的开发者一给业务场景让他画 ER 图就露馅画出来的模型要么关系基数看错要么属性归属混乱要么弱实体和普通实体完全分不清。这些恰恰是习题集真正训练的东西。本文会带你从最核心的概念入手把 ER 图怎么读、怎么做、怎么验证、怎么落成表结构一次梳理清楚中间附上参数、示例和踩坑记录。适合正在复习数据库课程的学生、准备面试的求职者以及想补建模基本功的在职开发者。这套东西不难但值得认真过一遍。2. 用 ER 图正确建立实体与关系三个建模核心与两种画法2.1 ER 图的三要素实体、属性、联系先把边界划干净任何一张合规的 ER 图本质上只表达三件事实体是什么、属性有哪些、实体之间怎么联系。这三件事的边界如果划不干净后面全部白搭。先说实体。实体是现实中可区分的事物学生、课程、订单、商品、班级都是实体。判断一个名词是不是实体的朴素标准是它是否需要被单独记录和追踪。比如「学生的姓名」不是实体它是学生的属性但「学生」本身是实体因为我们要存多个学生的多条信息。再比如「订单中的商品」到底算实体还是属性如果商品有自己的独立信息价格、库存、分类且需要被多个订单引用它就是实体如果只是附属于某个订单的一段描述文字那它可以是属性。这个判断直接决定模型的结构。属性相对简单它是实体的特征描述。但属性也有两个容易出问题的细节点一个是主键属性要标出来一个是复合属性如「家庭住址」由省、市、街道组成要不要拆开。大多数习题不需要拆复合属性如果你发现某个属性在业务里需要被独立查询或统计比如按城市统计用户数量那就该拆否则不必强行分解拆多了反而让图变得冗余。联系是 ER 图的核心难点也是习题集考察的重点。联系表达的是两个或多个实体之间的业务关联比如「学生选修课程」是一个联系「教师教授课程」是一个联系。联系本身也可以有属性比如「选修」联系可以带「成绩」属性——这个成绩既不属于学生也不属于课程它只有在学生和课程发生关系时才存在所以挂在联系上。这里我先给一个自检小技巧画任何一条联系线之前先问三个问题——这个联系在业务上是否真实存在它是一对一、一对多还是多对多这个联系自己是否携带属性三个问题都能回答这条线才画得明白。2.2 从一段业务描述中抽实体抓名词、查动词、筛冗余做习题时拿到一段业务描述第一件事不是画图而是把描述里的名词和动词分别圈出来。名词候选实体动词候选联系。这是最朴素也最可靠的做法。我一般分三步走。第一步通读题目把所有名词按出现顺序列出来比如「学校、系、教师、学生、课程、教室、成绩、班级」。第二步逐个判断这个名词是否需要独立存储信息「成绩」作为名词出现了但它不是实体它是学生和课程之间选修联系上的属性「教室」如果题目只提了「上课地点」这个词且不需要记录教室容量、位置等独立信息那它大概率就是个属性。第三步用动词验证联系学生「选修」课程、教师「讲授」课程、系「管辖」班级这些动词对应的主谓搭配必须能成立如果主语和宾语有一方不是实体这个联系就不成立。我踩过的典型误区是「看到名词就实体的条件反射」。A 同学做练习时把「学生姓名」单拎出来作为一个实体理由是它存储的信息不少。结果自检时发现姓名表和学生表之间是一对一关系然后还得用外键关联纯属自我制造工作量。判断一个名词是实体还是属性有一个更硬的标准它需不需要以行记录的形式被独立存储和检索。姓名是学生表的一列不是一张表。同样的逻辑适用于地址、电话、邮箱这类描述性名词。另外一个容易漏掉的是「多值属性」的存在。比如一个学生有多个手机号这属于多值属性。在 ER 图上正规画法是用双线椭圆表示多值属性但如果手机号需要被单独查询比如按手机号定位到学生那就应该把手机号拆成一张单独的联系或实体表。习题里如果明确说了「一个学生可以有多个联系电话」你需要决定是按多值属性画还是在转换阶段拆成子表——我的建议是直接拆成子表因为大多数习题后续会要求转关系模式多值属性在关系模式里是要单独成表的一步到位更省事。2.3 关系基数的判定方法从业务语义反推而不是从句子结构硬猜关系基数1:1、1:N、M:N判定错是所有 ER 图错误里最致命的一类因为它直接决定最终的建表结构。判定方法是固定套路对每一对实体分别从两个方向问「一个 A 最多对应几个 B」和「一个 B 最多对应几个 A」两个答案合起来就是基数。举个例子「班级—学生」一个班级对应多个学生一个学生只属于一个班级所以班级和学生是 1:N班级是一端学生是多端。再比如「学生—课程选修」一个学生可以选修多门课程一门课程可以被多个学生选修所以是 M:N。这个方向要特别注意很多人从文字表述的先后顺序去猜把「学生选修课程」画成 1:N理由是一句中文读下来像是一对多这是完全错误的。必须双向验证。M:N 关系在 ER 图上表示为实体间的菱形连线而它的处理方式是习题集中最重要的考点之一必须拆成中间表否则两个实体的表结构无法干净落地。到时候在第三张章会用完整示例演示。一对一关系比前两者少见但更容易出错。典型场景比如「系—系主任」一个系只有一个系主任一个系主任只管一个系。这种关系在落到关系模式时需要选择一个方向放外键——到底在系表里放主任编号还是在主任表里放系编号两种做法都可行但选择依据是查询方向。如果业务上经常从系查到主任就在系表放主任编号作为外键如果经常从主任查到系就反过来。选择标准我会在 3.3 里展开说。这里必须提醒一个常见误判不要把同一张表内部的自引用关系升级为 M:N。比如「员工—员工」之间的上下级关系一个员工有一个上级一个员工有多个下属这是 1:N 的自引用不是 M:N不需要中间表只需要在员工表里加一列manager_id指向自己的主键。习题里但凡出现「员工」「领导」「上级」这类描述先检查是不是自引用关系再用基数判定法验证。自引用方向反了也会出大问题。3. 把 ER 图转成关系模式这是习题集里最值钱的一步3.1 转换五规则实体成表、属性成列、关系决定外键绝大多数数据库习题在画完 ER 图之后都会要求「将 ER 图转换为关系模式」这个转换过程有固定规则不讲技巧、不需灵感你只需要按规则走就能拿全分。规则一每个强实体转换成一张表实体名就是表名实体属性就是表的列。规则二实体的主键就是表的主键。规则三1:N 联系在 N 端多端表中添加一个外键列引用 1 端表的主键。规则四M:N 联系新建一张独立表表里至少包含两个实体的主键作为外键(A 主键, B 主键) 联合作为新表的主键联系自身的属性也放进这张表。规则五1:1 联系任选一端的表添加另一端的主键作为外键也可以干脆合成一张表视业务而定。这套规则看似简单真正执行时最容易出问题的不是规则本身而是「关系属性到底放哪」。举个例子说明完整流程业务描述是「一个学生可选修多门课程一门课程可被多名学生选修学生选修课程产生成绩」。实体是学生和课程联系是选修M:N。那么转换结果是-- 学生表强实体直接转换 CREATE TABLE student ( student_id CHAR(10) PRIMARY KEY, -- 学号作为主键 student_name VARCHAR(50) NOT NULL, gender CHAR(1), enroll_year SMALLINT ); -- 课程表强实体直接转换 CREATE TABLE course ( course_id CHAR(8) PRIMARY KEY, -- 课程编号 course_name VARCHAR(80) NOT NULL, credit DECIMAL(3,1) -- 学分允许 3.5 这种小数值 ); -- 选修表M:N 联系拆出的中间表 CREATE TABLE takes ( student_id CHAR(10), course_id CHAR(8), grade DECIMAL(4,2), -- 成绩联系属性放中间表 PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );代码逻辑和参数说明如下takes表是 M:N 联系的核心产物它的主键由两个外键共同组成这保证了同一条学生-课程记录不会重复插入。grade字段是联系自身的属性它不属于 student 也不属于 course必须放在中间表上。DECIMAL(4,2)允许最大 99.99 的成绩值如果题目要求百分制整数那用TINYINT UNSIGNED更合适——这是你需要根据题目要求调的地方。CHAR(10)用于学号是因为学号通常定长且不会参与计算如果用VARCHAR(10)也不是错误但定长字符在 MySQL 里做等值检索时性能略优你需要有自己的选择依据。3.2 多对多联系必须拆中间表为什么不能直接在两个实体表里互加对方主键这个问题我在批改练习时见过无数次也是在初学阶段最容易犯的设计硬伤把 M:N 关系直接在两个实体表里互加外键列。错误做法是有诱惑力的——直觉上学生表里加一个「已选课程 ID」的字段课程表里加一个「选课学生 ID」的字段看起来就能表达关系了。但稍微推演一下就会发现死路一个学生选了三门课course_id字段里要存三个值这违反了第一范式如果把三个值用逗号拼接成一个字符串那你将会需要在应用层split字符串来做任何关联查询完全失去 SQL 的关联能力外键约束也会失效。这是黑匣子一样的隐患当时没事一查数据全乱。拆成中间表之后一个学生选多少门课都对应中间表的多条记录每一行都是原子值外键约束、级联删除、JOIN 查询全部正常工作。判断一个关系是否需要中间表就一句话从两端看都是一对多那这个关系就必然是多对多别想着省一张表。另外要注意中间表主键的选择。上面示例用双外键联合做主键这是默认做法。但如果中间表自己还有多层含义比如同一学生同一课程有多次重修记录那么双外键联合主键就不够用了需要加一个自增列作为代理主键双外键作为普通外键并加联合唯一索引。这两种写法的差异是一个常见的进阶考点-- 带重修记录的中间表允许同一学生同一课程出现多行 CREATE TABLE takes_with_retake ( takes_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_id CHAR(10) NOT NULL, course_id CHAR(8) NOT NULL, grade DECIMAL(4,2), retake_count TINYINT DEFAULT 0, UNIQUE KEY uk_stu_course_retake (student_id, course_id, retake_count), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );参数说明AUTO_INCREMENT代理主键让每一条重修记录有独立标识联合唯一索引uk_stu_course_retake只是约束了「同一学生同一课程同一次重修」不重复但不能防止不同重修次数插入多行——这正是我们想要的。TINYINT选它是因为重修次数理论上不会超过 127没必要用INT占用 4 字节。这里要记住一个点不要盲目给中间表加代理主键能不加就不加联合主键本身已经能完成大多数场景的完整性约束加代理主键会让索引体积变大、写入多一次索引维护。加的说清楚为什么加不加的也说清楚为什么不加这才算把这块吃透了。3.3 一对一的三种落表方式与选择依据外键放哪端答案在查询里一对一关系的落表方式有三种各有适用场景。第一种是合并成一张表适用情况是两端实体在业务上总是同时访问比如「用户」和「用户扩展资料」合并之后查询少一次 JOIN。第二种和第三种分别是把外键放在 A 表或 B 表选择依据是「哪端在业务里先从自己出发去查对方」。举个具体习题例子「每个系有一名系主任每名系主任只管理一个系」。实体是系department和教师teacher关系是「系主任任职」1:1。两个落表方向-- 方案一外键放系表适合经常从系信息出发查主任是谁 CREATE TABLE department ( dept_id CHAR(4) PRIMARY KEY, dept_name VARCHAR(50) NOT NULL, chair_teacher_id CHAR(6), UNIQUE KEY uk_chair (chair_teacher_id), FOREIGN KEY (chair_teacher_id) REFERENCES teacher(teacher_id) ); -- 方案二外键放教师表适合经常从人出发查他在哪个系当主任 CREATE TABLE teacher ( teacher_id CHAR(6) PRIMARY KEY, teacher_name VARCHAR(50) NOT NULL, manage_dept_id CHAR(4), UNIQUE KEY uk_dept (manage_dept_id), FOREIGN KEY (manage_dept_id) REFERENCES department(dept_id) );注意两种方案都加了UNIQUE约束——这才是 1:1 关系落表最关键的一步。不加UNIQUE的话外键列可以重复关系就退化成 1:N 甚至 M:N 了外键约束只保证引用存在不保证唯一。你可以把 UNIQUE 约束看成是数据库在强制执行「一对一」的语义。丢了它整个 1:1 设计名存实亡。选择哪一个方案我的经验是看高频查询的方向系统里最常见的页面是「系信息页显示主任名字」那就在 department 表放外键最常见的是「教师详情页显示他当主任的系」那就放 teacher 表。没有唯一正确答案但必须有决策依据。习题考试里默认选哪端都行你只需要在答案里写一句「外键设置在 X 端理由是从 X 端查询 Y 更为频繁」这就是满分答案的样子。关于合表做法要谨慎。合表虽然查询最快但会把两类内聚度不同的属性搅在一起。如果未来业务上「用户核心表」和「用户扩展表」被不同的服务访问强行合表会让服务之间的数据权限难以划分。模块划分时一张表只属于单一领域服务的原则在多数情况下比「少一次 JOIN」更重要。4. 做 ER 图习题的避坑指南这些错误我批改时见到最多4.1 把「不需要记录的数据」硬建模成实体导致表数量失控现象提交的答案里学生表旁边还立了张「学生手机号表」课程表旁边立了一张「课程教材表」最后实体数量比题目描述的名词数量还多关系模式里 70% 是两列的小表。原因没有先做「是否需要独立存储和检索」的判断看到关键词就想当然。比如题目只说「课程有教材名称」那教材就只是 course 表里的textbook_name一列不需要单独一张表。解决建任何实体之前问一句这个对象有没有除了名称之外的独立信息需要记录如果没有它就是属性。拿不准的时候可以反向测试如果把「教材」当成属性塞进 course 表查询「所有使用某本教材的课程」会不会受影响不会的话就不要拆实体。做习题时可以用这条规则筛掉至少三分之一的多余实体。4.2 M:N 识别失败漏拆中间表或者中间表主键设计成单列现象学生和课程多对多但只有两张表没有中间表或者有中间表了但主键只有一个student_id导致同一学生同一课程无法存在多条记录成绩项被覆盖。原因基数判定没做双向验证或者是做中间表时「顺手」把其中一个外键设成了主键没有意识到 M:N 关系里必须两个外键联合才能保证记录的粒度。解决画图阶段就双向问「一个 A 对应几个 B」和「一个 B 对应几个 A」任一端出现「多个」就把逻辑标在图上。转关系模式时凡是在图上标记了 M:N 的菱形直接套中间表模板联合主键 两个外键 联系属性列。自检写一道查错语句如果你在中间表定义里没有看到PRIMARY KEY (外键1, 外键2)那大概率就是错了。4.3 弱实体识别错误把弱实体当成普通实体生成孤儿记录现象题目描述「一个职工有多个家属家属依赖职工而存在」答案里家属表完全独立存在有自己独立的主键跟职工表只是普通外键关系。业务上家属离开了职工就没有存在意义这种数据应该由职工记录的删除级联清除而不是独立存活。原因没有明确「弱实体」和「普通实体」的分界线。弱实体必须依赖另一个强实体而存在它自己没有足够的主键属性来独立标识一条记录。家属表如果没有职工 ID单靠家属姓名这种属性根本无法区分不同职工的重名家属。解决识别弱实体看两点——业务上离开父实体是否还有存在意义以及是否无法独自构成主键。如果答案是「没意义」和「无法独自构成主键」那就是弱实体。落表时把弱实体的主键定义为「父表外键 判别符」的联合主键而且外键必须加ON DELETE CASCADECREATE TABLE dependent ( employee_id CHAR(8), dependent_name VARCHAR(50), relation VARCHAR(20), birth_date DATE, PRIMARY KEY (employee_id, dependent_name), FOREIGN KEY (employee_id) REFERENCES employee(employee_id) ON DELETE CASCADE );这里PRIMARY KEY (employee_id, dependent_name)正是弱实体「部分键partial key」的体现同一个员工名下不能有重名的家属不同员工之间重名无所谓。ON DELETE CASCADE保证员工离职后家属信息自动清理不产生孤儿数据。忘记 CASCADE 是另外一半人容易犯的延伸错误——弱实体的外键不带级联删除这个弱实体约束就不完整。4.4 属性归属错误把联系属性挂在实体上导致语义错位现象先看两版建表。错误版把成绩做成学生表的一列grade或者做成课程表的一列。正确版是放在中间表。原因没有理解「成绩」这个值依赖于「学生-课程」这个配对它在业务里天然属于联系而不是任何单端实体。把它放在学生表就会发生一个学生选了五门课学生表里只能存五个成绩值一个字段装不下又回到第一范式的那条岔路上。解决判定属性归属的万能方法造一个英文句子「A 的 B 是在与 C 的关系中产生的」如果句子成立这个 B 属性属于 A 与 C 的联系。举个例子「学生的成绩是在与课程的关系中产生的」成立所以成绩属于学生和课程的选修联系放进中间表。再试「学生的性别是在与课程的关系中产生的」不成立所以性别是学生实体的自身属性。这个造句法虽然土但在做习题时快且准我一直在用。4.5 复查顺序一套五步自检清单做完一张 ER 图或一套关系模式按固定顺序复查五遍能拦截大部分低级错误。第一步检查每个实体是否有主键主键是否最小不要有多余列。第二步逐条联系线验证基数双向问「一个 X 对应几个 Y」。第三步检查 M:N 联系是否有对应中间表中间表主键是否为联合形式。第四步检查外键引用列与对应的被引用主键列类型是否完全一致——包括数据类型和长度CHAR(10)的学号不能引用CHAR(8)的列MySQL 在有一部分类型不一致时会直接建表失败其余情况会留下隐患。第五步做语义终检拿着建好的表名和列名对着题目原文逐句念一遍每一句业务描述必须能在表结构里找到位置。这五步做完习题的正确率会有非常明显的提升。一套检查顺序固定下来比漫无目的的看图高效太多。5. 用工具验证练习结果从 ER 图到 MySQL 建表语句的落地5.1 MySQL Workbench 的 EER 图建模从画图到导出 SQL 的最小操作手绘 ER 图可以训练建模思维但它验证不了表结构是否合理。我把练习的最后一关固定为「画图工具出图 自动导出建表语句」让工具帮我抓漏。MySQL Workbench 是这一步的主力工具免费且对习题场景足够用。常见做法是在 Workbench 中新建一个 EER Model然后按下面步骤操作# 没有安装 MySQL Workbench 的话在 Ubuntu/Debian 上可这样装 sudo snap install mysql-workbench-community打开后选择 File → New Model双击Add Diagram进入画布。右侧面板拖入 Table 节点双击表名进入编辑区设置表名、添加列名和数据类型、勾选 PK/ NN / UQ / AI 约束。设置外键的正确姿势是双击关系连线——选中两个表的关联列Workbench 的 Foreign Key 面板会自动生成引用关系不需要手写 SQL。画完图之后最关键的一步是让工具替你做复查Database → Forward Engineer选择导出到 SQL 文件而不是直连数据库得到一个完整建表脚本。把脚本里的表结构和你手写的答案对照工具生成的 SQL 会强制使用它自己的命名规范比如FK_table1_table2对照时你把注意力集中在列名、类型、约束上不要在命名前缀上花时间。这步能立刻暴露的问题包括外键忘加工具生成时对应关系连线缺失、数据类型在列定义时选错比如主键选了INT但业务上需要BIGINT、联合主键没有在表定义里正确勾选多列。Workbench 这类的图形工具本身不会判断你的模型正确与否但它能把你的设计固化成精确的 SQL 文本错误反而更容易暴露。5.2 从导出的 SQL 逆向检查 ER 图通过正向和逆向比对确认设计一致性只做正向导出得到的 SQL 很可能带着你自己建模时的错误一路输出所以必须做第二步把导出的 SQL 重新逆向导入为 ER 图看看工具理解出来的模型是否与你的本意一致。MySQL Workbench 的做法是File → Import → Reverse Engineer MySQL Create Script选到刚导出的文件工具会重新生成一个 ER 图。然后对比两件事实体数量是否一致每个联系的类型是否一致。如果工具逆向出来的 M:N 联系在你原图里显示的是 1:N说明你在画图时联系定义有偏差——很常见的具体表现是你原本想画学生和课程的多对多但 Workbench 的建模面板里只定义了一个外键方向另一次外键没有被识别导致 M:N 退化成 1:N。这种问题看图画未必看得出来逆向对比一轮马上暴露。这里值得留意一个通用工具使用技巧正向导出和逆向导入形成的闭环本质上是「设计意图」和「机器理解」之间的一致性问题。做习题时你希望的是「机器理解 题目要求」所以正向导出后逆向导入这个动作千万别省。第一遍做时可能要多花二十分钟但每次都能发现一个小错误几次之后所有常见坑都被模型记住了。5.3 PowerDesigner 与 draw.io 的选型参考谁的颗粒度适合你不是所有人都喜欢用 MySQL Workbench。习题练习环境下我推荐三个工具按场景选。第一个是 PowerDesigner老牌建模工具功能重量级支持概念数据模型CDM、逻辑数据模型LDM、物理数据模型PDM三层分离适合做大型业务系统的数据架构设计。它能把概念模型自动映射为物理模型生产级项目里使用广泛。缺点是启动慢、界面复杂做几道练习题有点杀鸡用牛刀但如果你工作环境里已经在用它拿它练手完全没问题。第二个是 draw.io也叫 diagrams.net免费、轻量、纯网页可用。它适合快速画 ER 图来辅助思考画完可以导出 PDF 或者图片放进答案。但它的绘制方式本质是自由画布表和联系之间没有真正的「模型」语义层也就是说你画出来的菱形连线和矩形方块图无法被工具理解成外键关系也无法直接导出建表语句。如果你的需求只是把图做出来给人看draw.io 足够如果你需要验证表结构还是回到 Workbench 这类带模型语义的工具。第三个是 Navicat Data Modeler 这类数据库客户端附带的建模扩展优点是和实际数据库连接打通——你可以在库里建好表然后反向生成模型方便做「练习答案 vs 实际 LaunchDB 表」的对比。但它不是免费的且功能覆盖不如 Workbench 全面学生党一般不需要特意购买。我的选型经验是做练习阶段用 Workbench 足够它恰好横跨「画图」和「生成 SQL」两个能力当题目复杂度上升比如要处理几十张表的分层设计再考虑 PowerDesigner 的概念模型与物理模型分离能力。5.4 让工具讲题把习题描述转成 SQL 再转回 ER 图你会看到什么一个模拟场景题目描述「医院有多个科室每个科室有多名医生一名医生只能属于一个科室一名医生可以负责多个病人一个病人可以被多名医生治疗每名医生对每个病人的治疗记录包含治疗日期和诊断结果。」现在把这个描述手画成 ER 图然后按 5.1 和 5.2 的流程用工具建模并导出 SQL。你会得到如下表结构CREATE TABLE department ( dept_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, dept_name VARCHAR(50) NOT NULL UNIQUE ); CREATE TABLE doctor ( doctor_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, doctor_name VARCHAR(50) NOT NULL, dept_id INT UNSIGNED NOT NULL, FOREIGN KEY (dept_id) REFERENCES department(dept_id) ); CREATE TABLE patient ( patient_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, patient_name VARCHAR(50) NOT NULL, birth_date DATE ); CREATE TABLE treatment ( doctor_id INT UNSIGNED, patient_id INT UNSIGNED, treatment_date DATE, diagnosis VARCHAR(255), PRIMARY KEY (doctor_id, patient_id, treatment_date), FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id), FOREIGN KEY (patient_id) REFERENCES patient(patient_id) );参数说明有两点值得注意。第一treatment表主键是三列联合而不是两列因为描述里提到「每名医生对每个病人的治疗记录」——如果同一天、同一医生、同一病人只存在一次诊断三列联合主键刚好满足但如果有「同一天内同一医生给同一病人看两次病」的业务那必须再加一个就诊序号字段进主键或者换成自增代理主键。第二department.dept_name加UNIQUE是因为科室名称在业务上自然不可重复doctor.dept_id没有加UNIQUE因为一个科室有多个医生这个外键是可重复的它的粒度和 department 的主键粒度不同约束自然不同。用工具导出完这段 SQL 后你对照手写的答案会发现一个常见隐藏错误很多人画医生和科室时在 doctor 表里设置了dept_id但忘了加NOT NULL。如果某个医生可能暂时没有分配科室那NOT NULL不应该加如果题目隐含「每个医生都属于且必属于一个科室」那必须加NOT NULL。这就是题目语义对字段约束的影响工具本身不会帮你判断这个是该不该为空的问题需要你在画图阶段就意识到它属于「参与度约束」total participation。6. 进阶把 ER 图习题当校验集的技巧一题三做让答案之间互相验证练习册的价值不在做题本身而在「一题三做」——同一道题用三种不同产出物去表达让它们互相校验。养成这个习惯之后你做的不再是题而是一套对数据模型的反复确认过程。第一遍做拿到题目纯手绘 ER 图不允许打开任何工具用笔或任何画图应用把实体、属性、联系画出来标清基数。这一步训练的是建模直觉也是考试时的真实状态。第二遍做不回头看图直接依据题目原文写关系模式——强实体成表、联系定外键、1:1 选方向、M:N 拆中间表每一步都要能说清依据。第三遍做拿第二遍写出的关系模式建 SQL 表然后在本地或工具里反向生成 ER 图与第一遍的手绘图逐项对比。多个实体之间数量不一致的、联系类型对不上的、属性列归属错位的在这一轮都会露出马脚。这一步我管整个方向叫「把习题当作一套校准数据集」手绘图是主观版本SQL 是客观版本两者对齐之后你对「从业务描述到表结构」这条链路的理解就完成了一次闭环验证。很多人练 ER 图只练到「把图画完对一下答案」这其实只完成了一半训练。正如增删改查不是数据库的全部ER 图也不是只画不建。画完图能干活干完活能回查这才算真正掌握了建模能力。我自己早年做练习时图省事直接跳过手绘开着工具一边看题一边拖表结果考试时离开工具完全画不出连贯的图。后来被某公司的笔试题按在地上摩擦了一次才老老实实回到「三做」这套笨办法。练了不到两周画图速度和准确率都上来了。现在每次拿到新的业务需求我仍然会在纸上过一遍实体和关系线再开工具建模中间省掉的那一步推演会在后面某个改表结构的需求里连本带利找回来——这大概就是那一次踩坑换来最大的教训。毕业后发现真正值得投入时间的不是收集多少习题而是用一套固定的方法反复练透每个概念。希望这套流程和踩坑记录能帮你少绕几段弯路。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。