别再死记硬背了!用这5个真实业务场景(选课/图书/医院),手把手教你画E-R图和设计数据库表
实战数据库设计5个真实业务场景下的E-R图与表结构解析在数据库设计的教学过程中最常听到的抱怨就是学了一堆理论遇到真实项目还是无从下手。确实单纯记忆E-R图的菱形、矩形符号或是背诵范式的定义并不能真正培养出解决实际问题的能力。本文将打破传统教学模式选取教育、医疗、图书管理等五个典型业务场景带您从零开始完成完整的数据库设计流程。1. 选课管理系统从需求到实现的完整设计大学选课系统是理解多对多关系的经典案例。让我们从一个真实的需求出发某高校需要开发新系统要求记录学生选课情况、成绩并能统计各班级选课人数。系统需支持学生查询已修学分教师录入成绩管理员生成报表等功能。核心实体识别学生学号主键、姓名、性别、年龄班级班号主键、班名、所属专业课程课程号主键、课程名、学分、开课学期关键业务规则一个班级包含多个学生每个学生只属于一个班级1:n学生可选修多门课程每门课程可被多个学生选修m:n成绩只存在于学生与课程建立联系后联系属性-- 最终表结构SQL实现 CREATE TABLE 班级 ( 班号 CHAR(6) PRIMARY KEY, 班名 VARCHAR(20) NOT NULL, 专业 VARCHAR(30) ); CREATE TABLE 学生 ( 学号 CHAR(10) PRIMARY KEY, 姓名 VARCHAR(10) NOT NULL, 性别 CHAR(1) CHECK(性别 IN (男,女)), 年龄 SMALLINT, 班号 CHAR(6) REFERENCES 班级(班号) ); CREATE TABLE 课程 ( 课程号 CHAR(8) PRIMARY KEY, 课程名 VARCHAR(30) NOT NULL, 学分 SMALLINT DEFAULT 2, 开课学期 SMALLINT ); CREATE TABLE 选课 ( 学号 CHAR(10) REFERENCES 学生(学号), 课程号 CHAR(8) REFERENCES 课程(课程号), 成绩 DECIMAL(5,2), 选课时间 DATE, PRIMARY KEY (学号, 课程号) );注意成绩应该作为选课表的属性而非学生表因为只有选课后才会产生成绩。学分则属于课程固有属性。2. 医院病房管理系统复杂业务关系的处理技巧医疗系统的数据模型往往涉及复杂的业务规则。某三甲医院需要建立住院管理系统需管理科室、病房、医生和病人的多级关系。特殊要求包括每个病人有唯一主管医生医生只能属于一个科室病房需按科室分配。设计过程中的关键决策点科室-病房关系采用1:n设计病房表包含科室外键医生分配医生表中设置科室外键病人表设置医生外键病房分配病人表需要同时记录病房号和床位号复合外键E-R图转换关系模型表名主键外键特殊约束科室科名无科电话需符合格式校验病房病房号科名→科室.科名床位号需与病房号联合唯一医生工作证号科名→科室.科名职称需在预设范围内病人病历号工作证号→医生.工作证号病房号床位号→病房.病房号床位号-- 病房表的特殊设计 CREATE TABLE 病房 ( 病房号 VARCHAR(10), 床位号 VARCHAR(5), 科名 VARCHAR(20) REFERENCES 科室(科名), PRIMARY KEY (病房号, 床位号), UNIQUE (病房号, 床位号, 科名) -- 确保病房属于指定科室 );3. 图书借阅系统时间维度与历史记录设计图书馆管理系统需要特别关注时间维度的处理。以某市图书馆为例系统需记录每本书的借还历史计算逾期罚款并限制读者借阅数量。关键业务规则包括同一本书可能被反复借阅需要完整历史记录还书日期可能为空表示未还不同类型读者借阅上限不同。历史数据处理的三种方案对比状态标记法简单但不完整CREATE TABLE 借阅 ( 读者编号 VARCHAR(10), 图书编号 VARCHAR(20), 借期 DATE NOT NULL, 还期 DATE NULL, 状态 CHAR(1) DEFAULT 0 -- 0在借 1已还 );版本号法适合频繁更新CREATE TABLE 借阅历史 ( record_id SERIAL PRIMARY KEY, 读者编号 VARCHAR(10), 图书编号 VARCHAR(20), 操作时间 TIMESTAMP, 操作类型 CHAR(1), -- B借 R还 经办人 VARCHAR(10) );触发器辅助法自动维护历史CREATE TABLE 借阅日志 ( log_id SERIAL PRIMARY KEY, 原记录ID INT, 操作类型 VARCHAR(10), 操作时间 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 操作人 VARCHAR(20) );提示对于需要完整审计追踪的系统建议采用方案3虽然实现复杂但数据最可靠。4. 电商订单系统主从表设计与事务处理电商平台的订单系统是典型的一对多关系案例。一个订单包含多个商品每个商品条目有独立的价格、数量考虑促销价可能变化。设计时需要特别注意数据一致性和事务完整性。订单核心表结构CREATE TABLE 订单 ( 订单号 VARCHAR(20) PRIMARY KEY, 用户ID VARCHAR(20) NOT NULL, 下单时间 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 总金额 DECIMAL(10,2) CHECK(总金额 0), 状态 VARCHAR(10) CHECK(状态 IN (待支付,已支付,已发货,已完成,已取消)), 收货地址 TEXT ); CREATE TABLE 订单明细 ( 明细ID SERIAL PRIMARY KEY, 订单号 VARCHAR(20) REFERENCES 订单(订单号), 商品ID VARCHAR(15) NOT NULL, 购买价 DECIMAL(10,2) NOT NULL, 数量 INT CHECK(数量 0), 优惠金额 DECIMAL(10,2) DEFAULT 0, UNIQUE (订单号, 商品ID) -- 防止同一商品重复添加 );常见问题处理方案价格不一致问题记录购买时的快照价格而非实时引用商品表价格库存扣减时机支付成功后异步扣减采用预扣库存机制订单取消处理需要事务处理订单状态和库存回滚5. 仓储管理系统多对多关系的复杂案例仓库管理系统涉及零件、供应商和工程项目的复杂多对多关系。特殊挑战包括同一零件可能来自不同供应商且价格不同工程项目需要记录每种零件的需求总量需要跟踪每次采购的交货情况。核心表设计技巧三元关系处理引入供应关系表记录供应商-零件-工程的三方关系历史价格追踪在采购表中记录每次交易价格而非仅在零件表中维护库存事务设计采用入库单、出库单模式便于审计追踪CREATE TABLE 供应关系 ( 供应商ID VARCHAR(10), 零件ID VARCHAR(15), 工程ID VARCHAR(12), 报价 DECIMAL(10,2), 最小起订量 INT, 交货周期 INT, PRIMARY KEY (供应商ID, 零件ID, 工程ID) ); CREATE TABLE 采购单 ( 采购单号 VARCHAR(20) PRIMARY KEY, 供应商ID VARCHAR(10), 工程ID VARCHAR(12), 创建时间 TIMESTAMP, 预期到货 DATE, 状态 VARCHAR(10) ); CREATE TABLE 采购明细 ( 明细ID SERIAL PRIMARY KEY, 采购单号 VARCHAR(20) REFERENCES 采购单(采购单号), 零件ID VARCHAR(15), 数量 INT, 实际单价 DECIMAL(10,2), 到货状态 VARCHAR(10) );在实际项目中数据库设计从来不是一蹴而就的过程。我曾参与一个图书管理系统的重构最初设计忽略了读者可能续借的情况导致后来不得不增加续借次数字段并修改相关查询逻辑。经验告诉我们好的设计应该预留适当的扩展空间同时保持核心数据结构的稳定。