从零构建学生选课系统ER图设计与数据库落地全指南开篇为什么你的ER图总被导师打回重画记得第一次接触数据库课程设计时我花了整整三天绘制的完美ER图被导师用红笔圈出十几个问题。那些被标记为冗余关系、缺失属性的批注让我意识到ER图不是简单的图形拼接而是对业务本质的抽象表达。本文将用一个完整的选课系统案例带你避开那些教科书不会告诉你的实战陷阱。选课系统看似简单却包含了数据库设计中最典型的实体关系学生与课程的多对多关联、教师与课程的一对多分配、成绩与选课记录的依赖关系。这些场景正是检验ER图设计能力的试金石。我们将从需求分析开始逐步完成概念模型到物理模型的转换最终生成可执行的SQL建表语句。1. 需求分析识别核心实体与业务规则设计ER图的第一步不是画图而是理解业务。假设我们正在为一个大学设计选课系统通过与教务人员访谈梳理出以下核心需求学生管理记录学号、姓名、所属院系等基本信息课程管理包含课程编号、名称、学分、开课学期等属性教师管理需要存储教职工号、姓名、职称等信息选课规则每门课程由一位教师负责助教不算责任教师学生可选修多门课程每门课程可被多名学生选修选课需要记录成绩且成绩只能由授课教师录入课程有容量限制当报名人数达到上限后不可再选1.1 实体属性提取根据需求我们初步识别出四个主要实体及其关键属性实体必需属性可选属性学生学号(PK)、姓名、入学年份联系方式、邮箱课程课程编号(PK)、名称、学分课程描述、先修要求教师工号(PK)、姓名、所属院系职称、研究方向选课记录无需通过关系属性体现注意在需求阶段就要区分需要存储的数据和运行时计算数据。例如当前选课人数是统计值不应作为实体属性。2. 关系建模破解多对多关系的本质2.1 关系类型判定技巧使用1对多测试法判断关系类型教师-课程一位教师可以教授多门课程1→n一门课程只能由一位教师负责1←1→一对多关系学生-课程一个学生可选修多门课程1→n一门课程可被多名学生选修n←1→多对多关系特殊案例如果系统需要记录学生对课程的评分非成绩则一个学生可对多门课程评分1→n一门课程可被多名学生评分n←1但每个学生对每门课程只能评分一次→带约束的多对多关系2.2 关系属性挖掘最容易遗漏的是关系本身的属性。在我们的案例中选课关系必须包含成绩属性根据业务需求可能还需要选课时间记录操作时间戳选课状态待审核/已确认/已退选成绩录入时间审计需求erDiagram STUDENT ||--o{ COURSE_SELECTION : selects TEACHER ||--o{ COURSE : teaches COURSE ||--o{ COURSE_SELECTION : has STUDENT { string student_id PK string name int enrollment_year } TEACHER { string teacher_id PK string name string department } COURSE { string course_id PK string title int credits } COURSE_SELECTION { string student_id FK string course_id FK float grade datetime select_time }常见错误将课程容量错误地作为课程属性。实际上容量可能随学期变化应该放在开课安排实体中。3. ER图到关系模式的转换实战3.1 转换规则应用按照以下步骤进行转换实体转表CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, enrollment_year INT, department VARCHAR(50) ); CREATE TABLE course ( course_id VARCHAR(10) PRIMARY KEY, title VARCHAR(100) NOT NULL, credits INT CHECK (credits 0) );处理一对多关系教师-课程CREATE TABLE teacher ( teacher_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, department VARCHAR(50) ); -- 在多的一方添加外键 ALTER TABLE course ADD COLUMN teacher_id VARCHAR(20); ALTER TABLE course ADD CONSTRAINT fk_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id);处理多对多关系学生-课程CREATE TABLE course_selection ( student_id VARCHAR(20), course_id VARCHAR(10), grade DECIMAL(5,2) CHECK (grade BETWEEN 0 AND 100), select_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );3.2 高级设计技巧问题场景需要记录课程在不同学期的开课情况同一课程可能每年开设解决方案引入开课安排实体作为中间层CREATE TABLE course_offering ( offering_id SERIAL PRIMARY KEY, course_id VARCHAR(10), semester VARCHAR(20), max_capacity INT, FOREIGN KEY (course_id) REFERENCES course(course_id) ); -- 修改选课表关联 ALTER TABLE course_selection ADD COLUMN offering_id INT, ADD CONSTRAINT fk_offering FOREIGN KEY (offering_id) REFERENCES course_offering(offering_id);这种设计解决了同一课程多学期开设的区分不同学期可能有不同的容量限制教师可能每学期变化通过在course_offering中添加teacher_id字段4. 性能优化与完整性保障4.1 索引策略针对查询热点建立索引-- 高频查询按学号查选课 CREATE INDEX idx_selection_student ON course_selection(student_id); -- 高频查询按课程查选课学生 CREATE INDEX idx_selection_course ON course_selection(course_id); -- 组合查询学生学期查询 CREATE INDEX idx_selection_student_semester ON course_selection(student_id, offering_id);4.2 约束强化添加业务规则约束-- 确保同一学生同一课程只能选一次通过主键已保证 -- 限制选课总学分不超过30 ALTER TABLE student ADD COLUMN total_credits INT DEFAULT 0; CREATE OR REPLACE FUNCTION check_credits() RETURNS TRIGGER AS $$ BEGIN IF (SELECT SUM(c.credits) FROM course_selection cs JOIN course c ON cs.course_id c.course_id WHERE cs.student_id NEW.student_id) 30 THEN RAISE EXCEPTION Credit limit exceeded; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_credit_limit BEFORE INSERT ON course_selection FOR EACH ROW EXECUTE FUNCTION check_credits();4.3 反范式化设计在严格遵循范式的基础上针对性能瓶颈适当反范式化-- 在课程表中缓存当前选课人数需要触发器维护 ALTER TABLE course_offering ADD COLUMN current_enrollment INT DEFAULT 0; CREATE OR REPLACE FUNCTION update_enrollment() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP INSERT) THEN UPDATE course_offering SET current_enrollment current_enrollment 1 WHERE offering_id NEW.offering_id; ELSIF (TG_OP DELETE) THEN UPDATE course_offering SET current_enrollment current_enrollment - 1 WHERE offering_id OLD.offering_id; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_enrollment_change AFTER INSERT OR DELETE ON course_selection FOR EACH ROW EXECUTE FUNCTION update_enrollment();5. 设计验证与常见陷阱5.1 验证方法业务场景测试模拟学生A选择课程X成功同一学生重复选择同一课程应失败选课人数超过容量应失败教师尝试给自己授课的课程打分应成功非授课教师尝试打分应失败SQL验证示例-- 测试选课冲突 BEGIN; INSERT INTO course_selection VALUES (S001, CS101, NULL, NOW(), 1); INSERT INTO course_selection VALUES (S001, CS101, NULL, NOW(), 1); -- 应报主键冲突 ROLLBACK;5.2 典型设计错误过度中心化设计-- 错误示范把所有关系放在中心表 CREATE TABLE central_record ( record_id SERIAL PRIMARY KEY, student_id VARCHAR(20), course_id VARCHAR(10), teacher_id VARCHAR(20), -- 各种混合属性... );问题导致数据冗余和更新异常忽略历史数据-- 错误示范直接修改成绩而不留痕 UPDATE course_selection SET grade 85 WHERE student_id S001 AND course_id CS101;改进应设计成绩变更审计表错误的主键选择-- 错误示范用姓名作为主键 CREATE TABLE student ( name VARCHAR(50) PRIMARY KEY, -- 可能重名 ... );原则主键应永远是不可变、无业务含义的代理键或自然键6. 扩展设计权限与业务流程整合6.1 角色权限设计CREATE TABLE system_role ( role_id SERIAL PRIMARY KEY, role_name VARCHAR(20) UNIQUE ); INSERT INTO system_role VALUES (1, student), (2, teacher), (3, admin); CREATE TABLE user_role ( user_id VARCHAR(20), role_id INT, PRIMARY KEY (user_id, role_id) ); -- 学生只能查看自己的成绩 CREATE POLICY select_own_grades ON course_selection FOR SELECT USING (student_id current_user_id()); -- 教师只能修改自己授课课程的成绩 CREATE POLICY update_own_course_grades ON course_selection FOR UPDATE USING ( EXISTS ( SELECT 1 FROM course c WHERE c.course_id course_selection.course_id AND c.teacher_id current_user_id() ) );6.2 选课业务流程整合-- 选课状态机 CREATE TYPE selection_status AS ENUM ( PENDING, CONFIRMED, WITHDRAWN, GRADED ); ALTER TABLE course_selection ADD COLUMN status selection_status NOT NULL DEFAULT PENDING; -- 选课业务规则 CREATE OR REPLACE FUNCTION process_selection() RETURNS TRIGGER AS $$ BEGIN -- 检查课程容量 IF (SELECT current_enrollment max_capacity FROM course_offering WHERE offering_id NEW.offering_id) THEN RAISE EXCEPTION Course is full; END IF; -- 检查时间冲突简化示例 IF EXISTS ( SELECT 1 FROM course_selection cs JOIN course_offering co ON cs.offering_id co.offering_id WHERE cs.student_id NEW.student_id AND co.semester (SELECT semester FROM course_offering WHERE offering_id NEW.offering_id) -- 实际应检查具体上课时间冲突 ) THEN RAISE EXCEPTION Schedule conflict; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql;7. 数据仓库设计为分析扩展7.1 星型模式设计-- 事实表 CREATE TABLE fact_course_grade ( grade_key SERIAL PRIMARY KEY, student_key INT, course_key INT, teacher_key INT, semester_key INT, grade DECIMAL(5,2), credit_earned INT ); -- 维度表 CREATE TABLE dim_student ( student_key SERIAL PRIMARY KEY, student_id VARCHAR(20), name VARCHAR(50), department VARCHAR(50), enrollment_year INT ); -- 定期ETL过程 INSERT INTO fact_course_grade SELECT nextval(fact_course_grade_grade_key_seq), s.student_key, c.course_key, t.teacher_key, sm.semester_key, cs.grade, c.credits FROM course_selection cs JOIN student s ON cs.student_id s.student_id JOIN course_offering co ON cs.offering_id co.offering_id JOIN course c ON co.course_id c.course_id JOIN teacher t ON c.teacher_id t.teacher_id JOIN dim_semester sm ON co.semester sm.semester_code WHERE cs.grade IS NOT NULL;7.2 物化视图优化-- 学生GPA实时计算 CREATE MATERIALIZED VIEW mv_student_gpa AS SELECT s.student_id, s.name, ROUND( SUM(c.credits * cs.grade) / NULLIF(SUM(c.credits), 0), 2 ) AS gpa FROM student s JOIN course_selection cs ON s.student_id cs.student_id JOIN course c ON cs.course_id c.course_id WHERE cs.grade IS NOT NULL GROUP BY s.student_id, s.name; -- 定期刷新 REFRESH MATERIALIZED VIEW mv_student_gpa;8. 文档与团队协作规范8.1 数据字典示例### 学生表(student) | 字段名 | 类型 | 必填 | 描述 | 示例值 | |-----------------|--------------|------|--------------------|-------------| | student_id | VARCHAR(20) | 是 | 学号主键 | 20230001 | | name | VARCHAR(50) | 是 | 学生姓名 | 张三 | | enrollment_year | INT | 是 | 入学年份 | 2023 | | department | VARCHAR(50) | 否 | 所属院系 | 计算机系 | ### 选课关系表(course_selection) | 字段名 | 类型 | 必填 | 描述 | 示例值 | |--------------|----------------|------|--------------------------|-----------------| | student_id | VARCHAR(20) | 是 | 学生外键 | 20230001 | | course_id | VARCHAR(10) | 是 | 课程外键 | CS101 | | grade | DECIMAL(5,2) | 否 | 成绩(0-100) | 85.50 | | select_time | TIMESTAMP | 是 | 选课时间 | 2023-09-01 10:00:00 |8.2 版本控制建议数据库变更脚本/migrations ├── 20230901_initial_schema.sql ├── 20230915_add_offering_table.sql └── 20231001_create_audit_log.sqlER图维护工具使用PlantUML或dbdiagram.io等版本友好工具将ER图定义代码与数据库脚本同步更新startuml entity Student { * student_id : varchar(20) -- * name : varchar(50) enrollment_year : integer department : varchar(50) } entity Course { * course_id : varchar(10) -- * title : varchar(100) * credits : integer teacher_id : varchar(20) } Student }o--o{ Course : selects Teacher ||--o{ Course : teaches enduml
别再乱画ER图了!从学生选课系统实战,搞懂实体关系与数据库表设计
发布时间:2026/5/31 8:09:05
从零构建学生选课系统ER图设计与数据库落地全指南开篇为什么你的ER图总被导师打回重画记得第一次接触数据库课程设计时我花了整整三天绘制的完美ER图被导师用红笔圈出十几个问题。那些被标记为冗余关系、缺失属性的批注让我意识到ER图不是简单的图形拼接而是对业务本质的抽象表达。本文将用一个完整的选课系统案例带你避开那些教科书不会告诉你的实战陷阱。选课系统看似简单却包含了数据库设计中最典型的实体关系学生与课程的多对多关联、教师与课程的一对多分配、成绩与选课记录的依赖关系。这些场景正是检验ER图设计能力的试金石。我们将从需求分析开始逐步完成概念模型到物理模型的转换最终生成可执行的SQL建表语句。1. 需求分析识别核心实体与业务规则设计ER图的第一步不是画图而是理解业务。假设我们正在为一个大学设计选课系统通过与教务人员访谈梳理出以下核心需求学生管理记录学号、姓名、所属院系等基本信息课程管理包含课程编号、名称、学分、开课学期等属性教师管理需要存储教职工号、姓名、职称等信息选课规则每门课程由一位教师负责助教不算责任教师学生可选修多门课程每门课程可被多名学生选修选课需要记录成绩且成绩只能由授课教师录入课程有容量限制当报名人数达到上限后不可再选1.1 实体属性提取根据需求我们初步识别出四个主要实体及其关键属性实体必需属性可选属性学生学号(PK)、姓名、入学年份联系方式、邮箱课程课程编号(PK)、名称、学分课程描述、先修要求教师工号(PK)、姓名、所属院系职称、研究方向选课记录无需通过关系属性体现注意在需求阶段就要区分需要存储的数据和运行时计算数据。例如当前选课人数是统计值不应作为实体属性。2. 关系建模破解多对多关系的本质2.1 关系类型判定技巧使用1对多测试法判断关系类型教师-课程一位教师可以教授多门课程1→n一门课程只能由一位教师负责1←1→一对多关系学生-课程一个学生可选修多门课程1→n一门课程可被多名学生选修n←1→多对多关系特殊案例如果系统需要记录学生对课程的评分非成绩则一个学生可对多门课程评分1→n一门课程可被多名学生评分n←1但每个学生对每门课程只能评分一次→带约束的多对多关系2.2 关系属性挖掘最容易遗漏的是关系本身的属性。在我们的案例中选课关系必须包含成绩属性根据业务需求可能还需要选课时间记录操作时间戳选课状态待审核/已确认/已退选成绩录入时间审计需求erDiagram STUDENT ||--o{ COURSE_SELECTION : selects TEACHER ||--o{ COURSE : teaches COURSE ||--o{ COURSE_SELECTION : has STUDENT { string student_id PK string name int enrollment_year } TEACHER { string teacher_id PK string name string department } COURSE { string course_id PK string title int credits } COURSE_SELECTION { string student_id FK string course_id FK float grade datetime select_time }常见错误将课程容量错误地作为课程属性。实际上容量可能随学期变化应该放在开课安排实体中。3. ER图到关系模式的转换实战3.1 转换规则应用按照以下步骤进行转换实体转表CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, enrollment_year INT, department VARCHAR(50) ); CREATE TABLE course ( course_id VARCHAR(10) PRIMARY KEY, title VARCHAR(100) NOT NULL, credits INT CHECK (credits 0) );处理一对多关系教师-课程CREATE TABLE teacher ( teacher_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, department VARCHAR(50) ); -- 在多的一方添加外键 ALTER TABLE course ADD COLUMN teacher_id VARCHAR(20); ALTER TABLE course ADD CONSTRAINT fk_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id);处理多对多关系学生-课程CREATE TABLE course_selection ( student_id VARCHAR(20), course_id VARCHAR(10), grade DECIMAL(5,2) CHECK (grade BETWEEN 0 AND 100), select_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );3.2 高级设计技巧问题场景需要记录课程在不同学期的开课情况同一课程可能每年开设解决方案引入开课安排实体作为中间层CREATE TABLE course_offering ( offering_id SERIAL PRIMARY KEY, course_id VARCHAR(10), semester VARCHAR(20), max_capacity INT, FOREIGN KEY (course_id) REFERENCES course(course_id) ); -- 修改选课表关联 ALTER TABLE course_selection ADD COLUMN offering_id INT, ADD CONSTRAINT fk_offering FOREIGN KEY (offering_id) REFERENCES course_offering(offering_id);这种设计解决了同一课程多学期开设的区分不同学期可能有不同的容量限制教师可能每学期变化通过在course_offering中添加teacher_id字段4. 性能优化与完整性保障4.1 索引策略针对查询热点建立索引-- 高频查询按学号查选课 CREATE INDEX idx_selection_student ON course_selection(student_id); -- 高频查询按课程查选课学生 CREATE INDEX idx_selection_course ON course_selection(course_id); -- 组合查询学生学期查询 CREATE INDEX idx_selection_student_semester ON course_selection(student_id, offering_id);4.2 约束强化添加业务规则约束-- 确保同一学生同一课程只能选一次通过主键已保证 -- 限制选课总学分不超过30 ALTER TABLE student ADD COLUMN total_credits INT DEFAULT 0; CREATE OR REPLACE FUNCTION check_credits() RETURNS TRIGGER AS $$ BEGIN IF (SELECT SUM(c.credits) FROM course_selection cs JOIN course c ON cs.course_id c.course_id WHERE cs.student_id NEW.student_id) 30 THEN RAISE EXCEPTION Credit limit exceeded; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_credit_limit BEFORE INSERT ON course_selection FOR EACH ROW EXECUTE FUNCTION check_credits();4.3 反范式化设计在严格遵循范式的基础上针对性能瓶颈适当反范式化-- 在课程表中缓存当前选课人数需要触发器维护 ALTER TABLE course_offering ADD COLUMN current_enrollment INT DEFAULT 0; CREATE OR REPLACE FUNCTION update_enrollment() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP INSERT) THEN UPDATE course_offering SET current_enrollment current_enrollment 1 WHERE offering_id NEW.offering_id; ELSIF (TG_OP DELETE) THEN UPDATE course_offering SET current_enrollment current_enrollment - 1 WHERE offering_id OLD.offering_id; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_enrollment_change AFTER INSERT OR DELETE ON course_selection FOR EACH ROW EXECUTE FUNCTION update_enrollment();5. 设计验证与常见陷阱5.1 验证方法业务场景测试模拟学生A选择课程X成功同一学生重复选择同一课程应失败选课人数超过容量应失败教师尝试给自己授课的课程打分应成功非授课教师尝试打分应失败SQL验证示例-- 测试选课冲突 BEGIN; INSERT INTO course_selection VALUES (S001, CS101, NULL, NOW(), 1); INSERT INTO course_selection VALUES (S001, CS101, NULL, NOW(), 1); -- 应报主键冲突 ROLLBACK;5.2 典型设计错误过度中心化设计-- 错误示范把所有关系放在中心表 CREATE TABLE central_record ( record_id SERIAL PRIMARY KEY, student_id VARCHAR(20), course_id VARCHAR(10), teacher_id VARCHAR(20), -- 各种混合属性... );问题导致数据冗余和更新异常忽略历史数据-- 错误示范直接修改成绩而不留痕 UPDATE course_selection SET grade 85 WHERE student_id S001 AND course_id CS101;改进应设计成绩变更审计表错误的主键选择-- 错误示范用姓名作为主键 CREATE TABLE student ( name VARCHAR(50) PRIMARY KEY, -- 可能重名 ... );原则主键应永远是不可变、无业务含义的代理键或自然键6. 扩展设计权限与业务流程整合6.1 角色权限设计CREATE TABLE system_role ( role_id SERIAL PRIMARY KEY, role_name VARCHAR(20) UNIQUE ); INSERT INTO system_role VALUES (1, student), (2, teacher), (3, admin); CREATE TABLE user_role ( user_id VARCHAR(20), role_id INT, PRIMARY KEY (user_id, role_id) ); -- 学生只能查看自己的成绩 CREATE POLICY select_own_grades ON course_selection FOR SELECT USING (student_id current_user_id()); -- 教师只能修改自己授课课程的成绩 CREATE POLICY update_own_course_grades ON course_selection FOR UPDATE USING ( EXISTS ( SELECT 1 FROM course c WHERE c.course_id course_selection.course_id AND c.teacher_id current_user_id() ) );6.2 选课业务流程整合-- 选课状态机 CREATE TYPE selection_status AS ENUM ( PENDING, CONFIRMED, WITHDRAWN, GRADED ); ALTER TABLE course_selection ADD COLUMN status selection_status NOT NULL DEFAULT PENDING; -- 选课业务规则 CREATE OR REPLACE FUNCTION process_selection() RETURNS TRIGGER AS $$ BEGIN -- 检查课程容量 IF (SELECT current_enrollment max_capacity FROM course_offering WHERE offering_id NEW.offering_id) THEN RAISE EXCEPTION Course is full; END IF; -- 检查时间冲突简化示例 IF EXISTS ( SELECT 1 FROM course_selection cs JOIN course_offering co ON cs.offering_id co.offering_id WHERE cs.student_id NEW.student_id AND co.semester (SELECT semester FROM course_offering WHERE offering_id NEW.offering_id) -- 实际应检查具体上课时间冲突 ) THEN RAISE EXCEPTION Schedule conflict; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql;7. 数据仓库设计为分析扩展7.1 星型模式设计-- 事实表 CREATE TABLE fact_course_grade ( grade_key SERIAL PRIMARY KEY, student_key INT, course_key INT, teacher_key INT, semester_key INT, grade DECIMAL(5,2), credit_earned INT ); -- 维度表 CREATE TABLE dim_student ( student_key SERIAL PRIMARY KEY, student_id VARCHAR(20), name VARCHAR(50), department VARCHAR(50), enrollment_year INT ); -- 定期ETL过程 INSERT INTO fact_course_grade SELECT nextval(fact_course_grade_grade_key_seq), s.student_key, c.course_key, t.teacher_key, sm.semester_key, cs.grade, c.credits FROM course_selection cs JOIN student s ON cs.student_id s.student_id JOIN course_offering co ON cs.offering_id co.offering_id JOIN course c ON co.course_id c.course_id JOIN teacher t ON c.teacher_id t.teacher_id JOIN dim_semester sm ON co.semester sm.semester_code WHERE cs.grade IS NOT NULL;7.2 物化视图优化-- 学生GPA实时计算 CREATE MATERIALIZED VIEW mv_student_gpa AS SELECT s.student_id, s.name, ROUND( SUM(c.credits * cs.grade) / NULLIF(SUM(c.credits), 0), 2 ) AS gpa FROM student s JOIN course_selection cs ON s.student_id cs.student_id JOIN course c ON cs.course_id c.course_id WHERE cs.grade IS NOT NULL GROUP BY s.student_id, s.name; -- 定期刷新 REFRESH MATERIALIZED VIEW mv_student_gpa;8. 文档与团队协作规范8.1 数据字典示例### 学生表(student) | 字段名 | 类型 | 必填 | 描述 | 示例值 | |-----------------|--------------|------|--------------------|-------------| | student_id | VARCHAR(20) | 是 | 学号主键 | 20230001 | | name | VARCHAR(50) | 是 | 学生姓名 | 张三 | | enrollment_year | INT | 是 | 入学年份 | 2023 | | department | VARCHAR(50) | 否 | 所属院系 | 计算机系 | ### 选课关系表(course_selection) | 字段名 | 类型 | 必填 | 描述 | 示例值 | |--------------|----------------|------|--------------------------|-----------------| | student_id | VARCHAR(20) | 是 | 学生外键 | 20230001 | | course_id | VARCHAR(10) | 是 | 课程外键 | CS101 | | grade | DECIMAL(5,2) | 否 | 成绩(0-100) | 85.50 | | select_time | TIMESTAMP | 是 | 选课时间 | 2023-09-01 10:00:00 |8.2 版本控制建议数据库变更脚本/migrations ├── 20230901_initial_schema.sql ├── 20230915_add_offering_table.sql └── 20231001_create_audit_log.sqlER图维护工具使用PlantUML或dbdiagram.io等版本友好工具将ER图定义代码与数据库脚本同步更新startuml entity Student { * student_id : varchar(20) -- * name : varchar(50) enrollment_year : integer department : varchar(50) } entity Course { * course_id : varchar(10) -- * title : varchar(100) * credits : integer teacher_id : varchar(20) } Student }o--o{ Course : selects Teacher ||--o{ Course : teaches enduml