简介:数据库大作业以“超市管理系统”为场景,完整呈现数据库课程设计从需求分析到详细实现的全过程,适合高校计算机相关专业学生完成数据库课程设计或期末项目参考。文档共18页,含系统定义、需求分析、系统设计、详细设计、心得与成员分工等模块,覆盖商品、顾客、采购、销售、库存等多类数据表及E-R图设计,并给出Navicat建表、数据录入和典型SQL查询示例。从员工表、商品表到购买记录表、出/入库表,逻辑结构完整,能帮助读者理解关系模式与业务流程的数据映射。资源为1个docx文件,压缩包约663KB,内容结构清晰,便于直接查阅或按需修改套用。目前已有1806人浏览学习,对需要快速搭建超市管理数据库方案或理清设计思路的学习者具有实用参考价值。
1. 把“数据库大作业.docx”当成一份欠规范的数据库交付物来改
接手过不少数据库课程设计、企业内部的建表评审和接口文档,你会发现一个共性:真正值钱的东西往往不在.sql文件里,而在那份 Word 文档里——ER 图、字段注释、约束说明、存储过程逻辑全写在 docx 里。但这份文档通常又乱得让人头疼:有修订痕迹、有图片粘贴的 ER 图、有直接从 Navicat 拷出来的建表语句、还有手打的“备注”列。问题是,评审老师或项目负责人要的是一份“能对得上代码”的文档,而不是一篇既不像设计说明书、又不像操作手册的 Word 拼盘。本文就沿着“数据库大作业.docx”这个交付物,把 docx 文档的表结构设计与 MySQL 实例、可执行 SQL、增删改查验证串成一条线,覆盖从文档体检、ER 图解析、建库建表到文档批量修订的完整路径。
这篇内容不是教你写大作业,而是教你处理所有以 docx 为载体的数据库交付物:怎么从 Word 里抽字段定义、怎么把文档里的设计落成可跑的 MySQL 库、怎么让文档和数据库“互相校对”、以及怎么用 Python 批量精修 docx 的样式。适合正在带课设的在校生、需要把旧文档翻新成交付物的工程师,以及那些被“文档与代码不一致”坑过的人。我默认你的环境是 Windows / macOS 上的 Python 3.8+,数据库用 MySQL 8.0。
2. 用 python-docx 解析 docx:先给数据库文档做一次“结构体检”
2.1 为什么不用 Pandoc 而用 python-docx 处理数据库大作业
处理 docx 的常见工具是 Pandoc——一条命令就能导出 Markdown。但它只适合“把文字抽出来”,不适合“把表格结构、修订标记、段落的字体层级”保留下来,而数据库大作业的正文恰恰是表格:字段名、类型、长度、是否为空、默认值、备注,全在 Word 表格里。表头样式稍不一致,Pandoc 导出来的 Markdown 就是一堆无对齐的管道符,后期整理成本远高于直接用代码解析。
我一般会用python-docx做三层解析:
- 文档级:提取所有段落和表格,按出现顺序编号,输出一份“正文结构清单”;
- 表格级:定位“字段名 / 数据类型 / 约束 / 备注”这类表头,把 Word 表格直接转成 Python 的
list[dict],用于后续比对数据库元数据; - 修订级:把 docx 文档的
word/document.xml解开,统计修订插入和删除的内容,确认这份文档有没有“未接受修订”的残留。
这样做的好处是,后面不管是生成建表 SQL 还是校验数据库字段,都是对同一份结构化数据操作,而不是反复打开 Word 复制粘贴。
2.2 最小可用的 docx 文档体检脚本
下面这段脚本提取 docx 里的正文段落、表格数量和表格内容,并输出修订记录条数。
from docx import Document from docx.table import Table from docx.oxml.ns import qn import re doc = Document("数据库大作业.docx") # 1. 段落统计 paras = [p.text.strip() for p in doc.paragraphs if p.text.strip()] print(f"正文段落数: {len(paras)}") # 2. 表格数量与行列数 tables: list[Table] = doc.tables print(f"表格数量: {len(tables)}") for i, tb in enumerate(tables[:5]): print(f" 第{i + 1}个表: {len(tb.rows)}行 x {len(tb.columns)}列") # 3. 修订记录统计(需解压 xml) from zipfile import ZipFile with ZipFile("数据库大作业.docx") as z: xml = z.read("word/document.xml").decode("utf-8") ins = len(re.findall(r'<w:ins ', xml)) dele = len(re.findall(r'<w:del ', xml)) print(f"修订插入次数: {ins}, 修订删除次数: {dele}")参数与行为说明:
doc.paragraphs只取非空文本段落,用来判断文档总篇幅和章节覆盖情况;如果段落数非常少但表格特别多,说明这是一份“表驱动”文档,重点解析对象应该是表格。doc.tables是文档正文中的所有表格。这里只打印前 5 个的尺寸,避免刷屏;实际交付前应该遍历全部。- 修改记录的正则匹配基于
document.xml:<w:ins表示插入修订,<w:del表示删除修订。结果大于 0 时,必须让作者在 Word 里“接受全部修订”后再定稿,否则打印出来的 PDF 和在线预览会残留标黄或划线内容,这在数据库课程设计评审时非常扎眼。
2.3 把 Word 表格转成 Python 字典:这是唯一的文档事实来源
接下来把表格区域转换成结构化数据。这里的关键不是“读取表格”,而是“识别表头在哪一行”。数据库大作业的表格第一行往往长这样:学号、姓名、课程号、成绩、备注,但也有可能第一行是“学生信息表”这种大标题。我一般用“这行单元格是否包含字段名/列名/名称/类型/数据类型之一”判断表头行。
from docx import Document doc = Document("数据库大作业.docx") HEADER_KW = ("字段名", "列名", "名称", "类型", "数据类型", "备注", "约束") rows = [] for tb in doc.tables: header_idx = None for i, row in enumerate(tb.rows[:3]): # 表头基本出现在前3行 cells = [c.text.strip() for c in row.cells] if any(k in " ".join(cells) for k in HEADER_KW): header_idx = i break if header_idx is None: continue header = [c.text.strip() for c in tb.rows[header_idx].cells] for row in tb.rows[header_idx + 1:]: values = [c.text.strip() for c in row.cells] rows.append(dict(zip(header, values))) print(f"共识别到 {len(rows)} 条字段定义") for r in rows[:3]: print(r)逻辑与适用边界:
- 使用
tb.rows[:3]限定表头查找范围,是因为多数跨页表格在每一页都会重复表头行,从中取第一个匹配行即可,误判率低。 dict(zip(header, values))要求表头行和后续行的单元格数量一致。如果你发现某行的values长度小于header,多半是 Word 表格单元格合并了竖向列,这时需要按“合并单元格时值重复”的策略补位。- 这个脚本的输出就是后续建表和元数据对账的输入,所以建议把
rows直接json.dump保存成中间文件,避免反复解析 docx。
做完这一层,文档里的表结构就从 Word 格式变成了可计算的 Python 对象。之后要生成 SQL、比对字段、做修订检查,全都围着rows转,不再需要碰 Word 原件。
提示:python-docx 不支持读取
.doc老格式,如果你的文件实际是.doc但扩展名是.docx,解压 zip 时会直接报BadZipFile。快速验证方式:把文件复制一份改成.zip,能打开就说明是真正的 docx。
3. 从 ER 图和字段表到 MySQL 建库脚本
3.1 数据库大作业里的设计到底要怎么“落库”
很多数据库大作业的问题不是没有设计,而是设计停留在文档里。一屋子表结构用 Word 表格画得很漂亮,字段的“数据类型”列写着varchar(20)、int、date,可真正要用 MySQL 跑起来时,发现字符集没定、主键没加、外键没建、自增列没写auto_increment。所以读完 docx 表格后,下一件事就是把文档里的表定义落成可执行的 SQL。
这里有个原则要先立住:优先信文档,还是优先信代码?如果这是一份还没建库的课程设计,以文档为准;如果数据库已经跑了一段时间,以数据库元数据为准,并反向修订文档。本文场景是前者,所以我按“文档字段表 -> DDL -> 种子数据 -> 增删改查验证”的路径来搭。如果文档里缺字段类型,可以参考同表的其他字段推测,不要直接跳过。缺默认值不影响建表,但缺主键一定有问题。
3.2 根据数据字典手工设计库表结构
假定文档里有一个“学生信息表”,字段定义是:学号、姓名、性别、出生日期、入学年份、系别、备注。我一般会把 DDL 写成下面这样:
DROP TABLE IF EXISTS student; CREATE TABLE student ( student_id CHAR(10) NOT NULL COMMENT '学号,主键', name VARCHAR(50) NOT NULL COMMENT '姓名', gender ENUM('M','F') NOT NULL DEFAULT 'M' COMMENT '性别 M男 F女', birth_date DATE NULL COMMENT '出生日期', enroll_year YEAR NOT NULL COMMENT '入学年份', department VARCHAR(100) NULL COMMENT '系别', remark VARCHAR(255) NULL COMMENT '备注,预留扩展', PRIMARY KEY (student_id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '学生信息表';建表参数说明:
CHAR(10)用于定长学号,教研室里常见学号就是 8 到 10 位数字,定长存储省空间,而且等值查询时不需要计算长度。ENUM('M','F')处理性别字段是常见做法,但注意不要在 ENUM 里塞“未知”这种业务含义,真要加状态值就拆一个单独字段gender_status。YEAR只适合“入学年份”这种粒度到年的数据,不要用来存生日或时间戳。
3.3 增删改查基础 SQL 与大作业中的事务控制
建完表后,至少要用文档中描述的业务场景把增删改查走一遍。这里以向 student 表插入记录、查询某系别学生、修改备注、删除退学学生为例:
INSERT INTO student (student_id, name, gender, birth_date, enroll_year, department) VALUES ('2024001001', '张琳', 'F', '2005-06-12', 2024, '计算机系'), ('2024001002', '陈昊', 'M', '2005-01-30', 2024, '计算机系'); -- 普通查询 SELECT student_id, name, department FROM student WHERE enroll_year = 2024 AND gender = 'F'; -- 修改某个学生的备注 UPDATE student SET remark = '转专业进入,需补修数据库基础' WHERE student_id = '2024001001'; -- 删除学籍异动学生 DELETE FROM student WHERE student_id = '2024001003';事务控制方面,注意UPDATE和DELETE在数据库课程设计中常被要求在事务里执行。默认 MySQL 是自动提交模式,多条语句要么都成功要么都回滚,必须手动包事务:
START TRANSACTION; UPDATE student SET department = '软件工程系' WHERE student_id = '2024001002'; INSERT INTO student_log (student_id, action, op_time) VALUES ('2024001002', '转系', NOW()); COMMIT;为什么这里要单独提事务?因为数据库大作业评审时常见扣分点就是“新增了日志表但业务操作没有和日志写入绑定成同一个事务”。一旦第二步INSERT失败,前面的UPDATE应该回滚,而不是留在原地造成转系成功但日志缺失。
3.4 外键、索引与范式:别让文档里的关系模型名存实亡
课程设计里如果文档画了 ER 图,而数据库没有外键和索引,评审老师一眼就能看出来。我建议建表时按这三条落地:
- 外键只在“子表无论如何不会先于主表被删”的场景下使用。大作业常用
ON DELETE RESTRICT或ON DELETE CASCADE,但不要用NO ACTION——它在 MySQL 8.0 中和RESTRICT完全等价,写进文档反而显得概念陈旧。 - 给外键列建普通索引。如果不建索引,关联查询时子表会走全表扫描,大作业数据量小看不出来,但面试时被问到“外键列为什么需要索引”答不上来,就很尴尬。
- 联合唯一索引用于表达 ER 图中的唯一关系,例如
(course_id, student_id)在选课表里通常需要唯一约束,放在建表语句里写成UNIQUE KEY uk_course_student (course_id, student_id)。
为了对照参考,补一条范式检查:大作业最常见的范式问题是在选课表里冗余了“课程名称”。课程名只属于课程表,选课表只应该保存course_id。如果文档数据字典里出现这种冗余,应当先从database_design.docx改起,再回到 DDL。
4. 让 docx 文档和 MySQL 数据库比对:字段级元数据对账
4.1 为什么要做文档-数据库一致性校验
数据库课设和公司交付文档最大的区别是:课设只需要“文档写得像样”,公司文档则要求“文档和线上库一致”。但工程师真正痛苦的是:改了几轮表结构,忘了同步文档;或者文档被人用 WPS 改过表格,字段注释全丢了。这种情况下再人工核对几十张表,浪费时间且漏检率高。
所以我一般会写一个“元数据对账脚本”,从 MySQLinformation_schema里取真实字段信息,再和从 docx 里解析出的字段字典做比对。比对项包括:
- 表名在文档和数据库两侧是否存在;
- 每张表的字段名是否一一对应;
- 字段类型是否一致(文档写
varchar(20),库里是varchar(50),提示不匹配)。
4.2 用 information_schema 提取 MySQL 字段元数据
import pymysql import json conn = pymysql.connect( host="127.0.0.1", user="root", password="your_password", database="school", charset="utf8mb4", ) cur = conn.cursor() cur.execute(""" SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'school' ORDER BY TABLE_NAME, ORDINAL_POSITION """) db_meta = {} for table, col, ctype, isnull, comment in cur.fetchall(): db_meta.setdefault(table, {})[col] = { "type": ctype, "nullable": isnull, "comment": comment, } with open("db_meta.json", "w", encoding="utf-8") as f: json.dump(db_meta, f, ensure_ascii=False, indent=2) print(f"已导出 {len(db_meta)} 张表的元数据") conn.close()这段脚本的核心是information_schema.COLUMNS,它保留了某个库下所有表字段的底层定义,比SHOW FULL COLUMNS更适合程序化批量处理。查询结果按表名和字段顺序排序,保证输出稳定可 diff。db_meta.json是数据库侧的事实快照,后续对账只读这个文件,不需要频繁连库。
4.3 比对 docx 字段定义与库表结构的差异
import json with open("db_meta.json", encoding="utf-8") as f: db_meta = json.load(f) # doc_field_map: {"student": {"student_id": {"type": "char(10)", "nullable": "NO", ...}}} doc_field_map = load_docx_field_map("数据库大作业.docx") # 复用第2章的表格解析 for table, cols in doc_field_map.items(): if table not in db_meta: print(f"[缺表] 文档表 {table} 在数据库中不存在") continue for col, info in cols.items(): if col not in db_meta[table]: print(f"[缺字段] {table}.{col} 在数据库中不存在") continue db_type = db_meta[table][col]["type"].lower() doc_type = info["type"].lower() if db_type != doc_type: print(f"[类型不一致] {table}.{col}: 文档={doc_type}, 数据库={db_type}")说明几个关键点:
- 这里的
load_docx_field_map是第 2 章解析逻辑的封装,返回嵌套字典。建议在解析时把CHAR(10)转成char(10)统一格式,否则大小写不一致会误报。 - 类型比对的粒度是字符串精确相等。
varchar(20)和varchar(50)会被识别为不一致。如果做课设,这种差异通常意味着你改了库但没更文档;如果是正式交付,应该主动把文档同步成最新版。 - “缺表”“缺字段”“类型不一致”这三级信息至少要打全,要知道对账的目的不是“找出问题再手工改”,而是“问题清单直接进入批量修订脚本”。
4.4 对账结果如何回写 docx:自动修订字段备注
对账发现数据库某字段的COLUMN_COMMENT比文档里的备注更完整时,可以考虑直接回填到 docx 表格。回写用 python-docx 的单元格文本替换:
from docx import Document doc = Document("数据库大作业.docx") replace_map = { "student.student_id": "学号,主键,格式:8位数字", "course.course_id": "课程编号,关联 course 表", } for tb in doc.tables: for row in tb.rows: cells = [c.text.strip() for c in row.cells] # 简单判断:第1列是表名字段名,第3列是备注 if len(cells) < 3: continue key = f"{cells[0]}.{cells[1]}" if key in replace_map: row.cells[2].text = replace_map[key] doc.save("数据库大作业_修订.docx")这段脚本的局限在于定位方式比较依赖表格列顺序,但课程设计和内部交付文档的常见格式就是“表头 + 字段 + 类型 + 备注”,够用。注意row.cells[2].text = ...会覆盖整格内容,如果备注里有换行或独立段落,需要按paragraph[0].text处理而不是直接赋给整个 cell。
5. 批量修订 docx 的 3 个实用技巧:样式、修订痕迹与最终体检
5.1 统一表格样式:让数据库字段表格可打印可转 PDF
数据库大作业打印或转 PDF 时,最难看的是表格列宽不均、字体大小不一。python-docx 对表格列宽的控制比较薄弱,直接给column.width赋值有时不生效,因为 Word 表格的列宽不只是 attr 值,还受tblLayout影响。我习惯先用脚本把每个表格的autofit关掉,再对每列设置固定宽度:
from docx import Document from docx.shared import Cm doc = Document("数据库大作业.docx") for tb in doc.tables: tb.autofit = False # 假设字段表至少4列,分别为字段名、类型、约束、备注 widths = [Cm(3.5), Cm(3.5), Cm(3.0), Cm(7.0)] for i, w in enumerate(widths): for cell in tb.columns[i].cells: cell.width = w doc.save("数据库大作业_样式.docx")这里有个坑:只设置cell.width不设置tb.columns[i].width是不稳定的,因为 Word 表格底层gridCol和每个单元格的tcW都要一致。稳妥做法是对表格的每一列,先真正改列对象,再把该列所有单元格都赋同一个值。如果不改、打印时表格变成自适应宽度,前端预览还好,打印出来就一塌糊涂。
5.2 清理修订痕迹并生成“干净版定稿”
第 2 章通过document.xml检测到修订记录后,最快的方法是让用户在 Word 里 Ctrl+A、审阅、接受全部修订再另存。但如果你需要自动化,python-docx 本身没有“接受修订”API,得直接处理 XML:
import re from zipfile import ZipFile import shutil src = "数据库大作业.docx" dst = "数据库大作业_无修订.docx" with ZipFile(src) as zin: items = {name: zin.read(name) for name in zin.namelist()} xml = items["word/document.xml"].decode("utf-8") # 删除修订标记标签,保留修订内的文本 xml = re.sub(r"</?w:ins[^>]*>", "", xml) xml = re.sub(r"</?w:del[^>]*>", "", xml) xml = re.sub(r"<w:delText[^>]*/>", "", xml) items["word/document.xml"] = xml.encode("utf-8") with ZipFile(dst, "w") as zout: for name, data in items.items(): zout.writestr(name, data)这段脚本会把删除修订里的<w:delText>清掉,把插入修订的标签剥掉只留文字。但副作用是:如果正文是两处修订重叠的,处理顺序不对会留下多余标签。更稳妥的做法是交给python-docx或 Word 打开后重新保存。脚本方案适合批量处理多份大作业 docx,但要抽查结果,不要无脑提交。定稿文件生成后,建议用 LibreOffice 转一次 PDF,确认没有黄色批注条。
5.3 文档终检:用脚本检查章节编号、表格数量与 SQL 附带情况
最后给一份“可上会”的检查清单,我用它判断一份数据库大作业 docx 是否可以直接提交。这个清单完全可以落地成一个 Python 脚本,对doc.paragraphs做正则匹配,比如:
- “第1章”“1.1”这类章节编号是否存在;
- 文档中是否包含
CREATE TABLE、SELECT、INSERT、UPDATE、DELETE等关键字文本; - 表格数量是否覆盖需求文档中声明的全部表。
一个常见的漏项是:学生把建表 SQL 放在单独.sql文件里,但 docx 里没有内嵌任何 SQL。评审时这属于“文档不完整”而不是“代码有问题”。所以终检脚本的最后一项,是统计 docx 文本中是否出现至少一个CREATE TABLE或SELECT FROM。发现没有时,在 console 里打一行提示,然后你自己决定是手动把 SQL 附录追加到文档里,还是写脚本把.sql文件内容插入到## 附录之后。追加逻辑简单可靠,推荐直接加一页 SQL 附录,而不是在正文中间插入代码段,这样可以避免 docx 分页错乱。
本文还有配套的精品资源,点击获取