2026/10/14 11:31:15

工厂数据库建模实战:从PDF文档到可运行SQLite

工厂数据库建模实战:从PDF文档到可运行SQLite 简介本资源是一份面向高校数据库课程设计实践的《工厂数据库管理系统》完整课程设计方案适用于计算机、信息管理等专业本科生掌握数据库系统开发全流程。内容覆盖需求分析、E-R建模、3NF关系规范化、物理表结构定义、索引与安全策略含触发器、视图、备份、SQL实现等六大核心环节并针对工厂、车间、工人、产品、零件、仓库等多实体及多对多关系如仓库-零件、仓库-产品给出两种中间表设计方案对比与选型依据。资源为单文件PDF文档共1个2.1MB的PDF文件内容详实含字段定义表、数据结构说明、E-R图示意、关系模式转换过程及主键/函数依赖分析。目前已有405人学习下载可直接用于课程设计报告撰写、答辩准备或数据库原理课后拓展实践是理论结合工程落地的典型教学范例。1. 工厂数据库管理系统不是“工厂模式”的代码抽象而是产线数据闭环的底层操作系统你手头有一份叫《工厂数据库管理系统.pdf》的文档——它大概率不是讲 Python 里的 factory method 或 Spring Boot 里的 bean 工厂而是一套面向真实制造现场的数据底座设计说明书。我见过太多产线工程师拿着这份 PDF 却卡在第一步以为要写 Java 工厂类结果发现里面全是设备 ID、工艺 Recipe 版本号、chamber 占用率、生效日期线性标识这些字段也见过 IT 部门按“管理系统”四个字直接上 Vue3 若依框架结果 PLC 数据一接入就丢帧、批次追溯查不出 3 小时前的参数波动。这份 PDF 的本质是把“设备—工艺—物料—质量—排程”五条业务流用关系型数据库的范式约束住再通过最小可行的增删改查接口让 MES、SCADA、QMS 系统能稳定读写。它不追求炫技但容错率极低一个equipment_id字段没设 NOT NULL整条产线的 OEE 计算就漂移recipe_activity_group和plant_code缺少联合唯一索引版本回滚时会覆盖错误工艺参数。适合正在做设备联网验收、准备上 MES 前置数据治理、或被审计方要求提供“数据可追溯性证明”的现场工程师、自动化集成商、以及懂 SQL 但没碰过 OPC UA 的开发人员。别急着建表先看清楚这张 PDF 里藏着多少个“必须落地”的物理约束。2. 从 PDF 文档反向建模提取字段、识别主外键、确定范式边界PDF 不是代码但比代码更难啃——它用表格和文字混排描述实体关系没有 ER 图没有字段类型标注甚至同一字段在不同章节写法不一致比如 “chamber 总个数” vs “chamber_count”。我们必须把它当原始需求文档来解构而不是当技术手册来执行。2.1 字段提取用正则人工校验双保险抓出全部原子字段先通读全文定位所有带冒号的定义段落如“编号条件码由 plant_code equipment_id recipe_id 拼接生成”再扫描所有表格标题行。重点盯三类内容带下划线/加粗的术语如recipe_activity_group,capachamber_name含单位或说明的短语如“加工时间单位为秒”“线性百分比0~100 整数”重复出现的组合词如多次出现的plant_code equipment_id recipe_id暗示复合主键。用 Python 脚本快速初筛注意PDF 文字抽取易错必须人工复核import re # 假设已用 pdfplumber 提取纯文本 text_content # 此处仅示意正则逻辑实际需配合上下文判断 field_patterns [ r([a-zA-Z_])\s*[:]\s*[^。\n]*, # 冒号定义式 r([^]), # 代码块包裹式部分 PDF 有 r【([^】])】, # 中文括号强调式 r字段名\s*([^\n。]), # 显式标注 ] all_fields set() for pattern in field_patterns: matches re.findall(pattern, text_content) for m in matches: # 清洗去空格、去单位、去括号说明 clean re.sub(r.*?|.*|单位.*|.*?秒|.*?整数, , m).strip() if len(clean) 2 and _ in clean: # 过滤掉“编号条件码”这类中文名保留 snake_case all_fields.add(clean) # 输出后人工剔除误匹配如“创建者”“更改人”是操作字段非业务实体 print(候选字段共, len(all_fields), 个, sorted(all_fields))提示脚本只负责“捞出疑似字段”真正可用字段必须满足三个条件① 在至少两个不同业务场景中被引用如equipment_id同时出现在设备台账和工艺执行表中② 有明确数据类型暗示“整数”“日期”“字符串长度≤32”③ 不是纯计算字段如chamber比例是chamber占用个数/chamber总个数不应建为物理列。我一般会建一张 Excel 表左列写 PDF 原文截图位置页码段落右列写字段名、类型、是否为空、是否主键、业务含义逐条打钩确认。2.2 主外键识别抓住“谁管谁”和“谁依赖谁”的物理事实工厂场景里主外键不是设计出来的是产线规则定死的。例如recipe_id必须引用recipe_master表工艺主数据因为设备执行 Recipe 前必须校验其有效性plant_code和equipment_id组合必须能唯一确定一台设备所以equipment表的主键大概率是(plant_code, equipment_id)recipe_activity_group如果在 PDF 中被定义为“同一组工艺活动共享版本号”那它就是recipe_version表的主键而非recipe表的字段。关键动作画一张最简关系草图纸笔即可只放 4~5 个核心实体Plant、Equipment、Recipe、Chamber、Activity用箭头标出“谁指向谁”。例如Equipment → Plant设备属于某工厂Recipe → Recipe_Activity_Group工艺属于某活动组Chamber → Equipment腔体属于某设备Activity → Recipe某次加工活动执行某工艺注意PDF 中若出现“动因代码”“工艺条件ID”这类词90% 是外键且大概率指向独立配置表如cause_code表存停机原因“动因代码101”对应“真空泵故障”。不要试图把它们塞进主表否则扩展性归零。2.3 范式决策第三范式是底线但“chamber占用个数”必须冗余PDF 里明确写了chamber占用个数和chamber总个数这看似违反 3NF因为chamber占用个数可由实时采集计算但必须物理存储。原因很现实OEE 计算要求毫秒级响应而实时采集点可能延迟 2~5 秒历史报表需要固定快照不能每次查询都 join 实时表。所以这里采用“适度冗余”策略chamber_total_count存equipment表静态属性chamber_occupied_count存equipment_status表动态属性带时间戳同时建视图v_equipment_utilization计算chamber_occupied_count / chamber_total_count供 BI 工具直连。这种设计在 PDF 的“数据一致性要求”章节通常有隐含说明如“利用率报表需基于每小时快照生成”要顺着业务目标反推技术妥协点。3. 用 SQLite 快速验证模型轻量、跨平台、免运维专治 PDF 里的模糊地带别一上来就装 MySQL 或达梦。SQLite 是工厂边缘侧验证数据库模型的黄金标准单文件、无服务进程、Python 内置、支持 WAL 模式高并发写入且.db文件可直接发给产线同事双击打开查数据。更重要的是——它逼你直面 PDF 里没说清的细节。3.1 创建最小可行表结构只实现 PDF 核心字段拒绝过度设计以 PDF 中高频出现的equipment和recipe_execution为例建两张表其他表同理推导-- 设备主表plant_code equipment_id 构成复合主键 CREATE TABLE equipment ( plant_code TEXT NOT NULL, equipment_id TEXT NOT NULL, capachamber_name TEXT, -- 腔体名称如 CHAMBER_A chamber_total_count INTEGER DEFAULT 0, -- 总腔体数静态值 equipment_type TEXT, -- 设备类型用于分类统计 PRIMARY KEY (plant_code, equipment_id), CHECK (chamber_total_count 0) ); -- 工艺执行表记录每次加工活动 CREATE TABLE recipe_execution ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_code TEXT NOT NULL, equipment_id TEXT NOT NULL, recipe_id TEXT NOT NULL, recipe_activity_group TEXT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME, chamber_occupied_count INTEGER DEFAULT 0, linear_percentage INTEGER CHECK (linear_percentage BETWEEN 0 AND 100), created_by TEXT, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 外键约束确保设备存在 FOREIGN KEY (plant_code, equipment_id) REFERENCES equipment(plant_code, equipment_id) );参数说明CHECK (linear_percentage BETWEEN 0 AND 100)直接落实 PDF 中“线性百分比0~100 整数”的约束比应用层校验更可靠FOREIGN KEY强制关联避免出现equipment_idE001但plant_codeP02实际该设备只在 P01的脏数据DEFAULT CURRENT_TIMESTAMP解决 PDF 里“创建时间”字段常被忽略自动填充的问题——产线系统没人手动填时间。3.2 插入测试数据用真实产线片段验证字段逻辑别插(P01,E001,R1001,GRP-A,2024-01-01 08:00:00,...)这种假数据。从 PDF 的“示例数据”章节或现场拍的 MES 截图里抄真实片段-- 示例PDF 第 12 页的“设备状态快照”表格 INSERT INTO equipment (plant_code, equipment_id, capachamber_name, chamber_total_count, equipment_type) VALUES (P01, E001, CHAMBER_A, 4, ETCH), (P01, E002, CHAMBER_B, 2, CVD); -- 示例PDF 第 15 页的“工艺执行日志” INSERT INTO recipe_execution (plant_code, equipment_id, recipe_id, recipe_activity_group, start_time, end_time, chamber_occupied_count, linear_percentage, created_by) VALUES (P01, E001, R1001, GRP-A, 2024-01-01 08:00:00, 2024-01-01 08:12:30, 3, 95, AUTO_SYSTEM), (P01, E002, R2001, GRP-B, 2024-01-01 08:05:00, 2024-01-01 08:18:45, 1, 87, AUTO_SYSTEM);逻辑说明chamber_occupied_count3对应E001的chamber_total_count4占用率 75%与linear_percentage95不冲突后者是工艺参数达标率前者是物理资源占用created_byAUTO_SYSTEM明确区分人工录入和系统自动生成PDF 中“创建者”字段常被忽略来源此处强制约定。3.3 查询验证用 SQL 代替 PDF 文字确认业务规则可执行PDF 说“可追溯单片加工时间”但没说怎么算。我们用 SQL 定义它-- 计算单片加工时间总加工时长 / 片数PDF 中“单片加工时间”字段需由此派生 SELECT e.equipment_id, r.recipe_id, COUNT(*) as batch_count, AVG(julianday(r.end_time) - julianday(r.start_time)) * 24 * 60 as avg_duration_min, -- 假设每批次加工 25 片实际应来自 recipe_master 表 ROUND((AVG(julianday(r.end_time) - julianday(r.start_time)) * 24 * 60) / 25.0, 2) as per_wafer_time_min FROM recipe_execution r JOIN equipment e ON r.plant_code e.plant_code AND r.equipment_id e.equipment_id WHERE r.start_time 2024-01-01 GROUP BY e.equipment_id, r.recipe_id;参数说明julianday()是 SQLite 计算时间差的可靠函数避免字符串解析错误per_wafer_time_min是派生字段PDF 中若要求存储此值需在recipe_execution表中加列并用触发器维护ROUND(..., 2)保证小数位数符合 PDF “时间单位分钟保留两位小数”的要求。4. 避坑PDF 没写的 5 个血泪现场问题90% 的翻车都发生在这里PDF 是理想模型产线是混沌系统。以下问题我在 3 个晶圆厂、2 个电池产线项目中反复踩过每次修复都得改表结构、补迁移脚本、重跑历史数据。4.1 现象equipment_id在 PDF 中写“字符串”但 PLC 实际传过来带不可见字符BOM、空格、全角数字原因PDF 未定义清洗规则而西门子 S7-1500 的字符串寄存器默认填充空格罗克韦尔 CompactLogix 可能插入 UTF-8 BOM。解决建表时加TRIM()触发器并在应用层强制转换CREATE TRIGGER trim_equipment_id BEFORE INSERT ON equipment BEGIN UPDATE NEW SET equipment_id TRIM(REPLACE(REPLACE(NEW.equipment_id, CHAR(0), ), , )); END;玄学经验CHAR(0)是西门子常见全角空格 是日系设备常见必须双杀。4.2 现象start_time和end_time看似 DateTime但 PLC 时间戳精度是毫秒SQLite 默认只存秒级原因PDF 写“时间格式YYYY-MM-DD HH:MM:SS”但产线要求分析 cycle time 到 100ms 级别。解决用TEXT类型存 ISO8601 格式2024-01-01 08:00:00.123查询时用datetime()函数解析-- 插入时确保毫秒 INSERT INTO recipe_execution (start_time, ...) VALUES (2024-01-01 08:00:00.123, ...); -- 查询时可精确计算 SELECT CAST(strftime(%s, start_time) AS REAL) CAST(substr(start_time, -3) AS REAL)/1000.0 FROM recipe_execution;4.3 现象PDF 要求“生效日期”字段但不同设备的工艺版本切换时间不同步导致recipe_id关联失效原因PDF 把recipe_id当作静态主键但实际中R1001在 P01 生效于 1月1日在 P02 生效于 1月5日。解决废掉recipe_id单独主键改为(recipe_id, plant_code, effective_date)复合主键并建recipe_version表CREATE TABLE recipe_version ( recipe_id TEXT NOT NULL, plant_code TEXT NOT NULL, effective_date DATE NOT NULL, version_no TEXT NOT NULL, PRIMARY KEY (recipe_id, plant_code, effective_date) ); -- recipe_execution 表外键指向此表4.4 现象chamber比例字段在 PDF 中定义为“计算字段”但 BI 工具直连时无法实时计算报表卡死原因PDF 未区分 OLTP 和 OLAP 场景chamber_occupied_count / chamber_total_count在大数据量下 join 效率极低。解决建物化视图SQLite 用普通表 定时任务-- 每小时跑一次 CREATE TABLE chamber_utilization_hourly AS SELECT e.plant_code, e.equipment_id, e.capachamber_name, e.chamber_total_count, COALESCE(AVG(r.chamber_occupied_count), 0) as avg_occupied, ROUND(COALESCE(AVG(r.chamber_occupied_count), 0) * 100.0 / e.chamber_total_count, 2) as utilization_pct, DATE(r.start_time, start of hour) as hour_start FROM equipment e LEFT JOIN recipe_execution r ON e.plant_code r.plant_code AND e.equipment_id r.equipment_id AND r.start_time datetime(now, -1 hour) GROUP BY e.plant_code, e.equipment_id, e.capachamber_name, e.chamber_total_count, DATE(r.start_time, start of hour);4.5 现象PDF 写“备注说明TEXT”但现场填入超长文本1000 字符SQLite 默认 page_size1024 导致写入失败原因PDF 未定义长度限制而 SQLite 的 BLOB/TEXT 字段受 page size 限制超长文本需额外页分配。解决建表时显式指定PRAGMA page_size 4096;并在remarks字段加长度检查-- 执行建库前 PRAGMA page_size 4096; -- 字段定义 remarks TEXT CHECK (length(remarks) 4000)5. 连接真实设备用 Python 脚本把 PLC 数据灌进 SQLite绕过 PDF 的“纸上谈兵”PDF 终究是静态文档真正的系统活在设备数据流里。我用一个不到 50 行的 Python 脚本把西门子 S7-1200 的 10 个寄存器数据按 PDF 字段映射规则实时写入 SQLite。它不依赖任何工业网关直接走 TCP是验证模型能否扛住产线节奏的终极考验。5.1 安装最小依赖只用pymodbus和sqlite3Python 内置pip install pymodbus3.5.2 # 注意v3.6 有 breaking change3.5.2 最稳为什么不用 Node-RED 或 Ignition因为 PDF 模型验证阶段你需要完全掌控每个字节——Modbus 请求包长、响应解析逻辑、异常重试机制这些在图形化工具里都是黑匣子。5.2 核心同步脚本字段映射、异常熔断、写入幂等from pymodbus.client import ModbusTcpClient import sqlite3 import time from datetime import datetime # 1. 连接 PLC假设 IP 192.168.1.100端口 502 client ModbusTcpClient(192.168.1.100, port502) client.connect() # 2. SQLite 连接使用 WAL 模式提升并发写入 conn sqlite3.connect(factory.db, check_same_threadFalse) conn.execute(PRAGMA journal_modeWAL;) # 3. 字段映射表PLC 地址 - PDF 字段名 - 数据类型 # 格式(plc_address, pdf_field, data_type, scale_factor) mapping [ (100, chamber_occupied_count, uint16, 1), # DB100.DBW0 (102, linear_percentage, uint16, 0.1), # DB100.DBW2需除以10 (104, equipment_id, string, 1), # DB100.DBB410字节字符串 ] while True: try: # 读取所有寄存器 rr client.read_holding_registers(address100, count10, slave1) if not rr.isError(): # 解析数据简化版实际用 struct.unpack values {} for addr, field, dtype, scale in mapping: if dtype uint16: val rr.registers[(addr-100)//2] # 假设连续地址 values[field] int(val * scale) elif dtype string: # 读取 10 字节转字符串并去空格 raw_bytes b.join([ rr.registers[i].to_bytes(2, big) for i in range((addr-100)//2, (addr-100)//2 5) ]) values[field] raw_bytes.decode(utf-8, errorsignore).strip(\x00 ) # 写入 SQLite关键用 REPLACE 避免重复插入 conn.execute( REPLACE INTO equipment_status (equipment_id, chamber_occupied_count, linear_percentage, updated_time) VALUES (?, ?, ?, ?) , (values[equipment_id], values[chamber_occupied_count], values[linear_percentage], datetime.now().isoformat())) conn.commit() except Exception as e: print(fPLC 读取异常: {e}) time.sleep(5) # 熔断避免刷爆日志 time.sleep(1) # 每秒同步一次PDF 中“实时监控”要求的底线参数说明REPLACE INTO是关键产线数据可能重复上报用INSERT OR IGNORE会丢数据REPLACE自动处理主键冲突scale_factor0.1对应 PDF 中“线性百分比0~100PLC 存 0~1000”这是现场最常见的缩放陷阱errorsignore处理 PLC 字符串乱码比崩溃强——PDF 没说编码但产线就是 GBK/UTF-8 混用。5.3 验证脚本有效性用 SQLite 命令行看真实数据流# 进入数据库 sqlite3 factory.db # 查看最近 5 条设备状态 SELECT equipment_id, chamber_occupied_count, linear_percentage, updated_time FROM equipment_status ORDER BY updated_time DESC LIMIT 5; # 检查数据是否连续排除 PLC 断连 SELECT COUNT(*) as total, MIN(updated_time) as first, MAX(updated_time) as last, (julianday(MAX(updated_time)) - julianday(MIN(updated_time))) * 24 * 60 as duration_min FROM equipment_status;如果duration_min接近total单位分钟说明每秒写入稳定如果duration_min total说明 PLC 断连过——这时就要回头检查pymodbus的重连逻辑而不是怪 PDF 模型。6. 把 PDF 变成可交付物生成 DDL、字段字典、API 接口文档让甲方签字认可PDF 的价值不在阅读而在交付。我从不把 PDF 当最终成果而是用它生成三样东西数据库 DDL 脚本、字段业务字典 Excel、REST API 接口文档Swagger YAML。这三样东西才是甲方信息科、审计方、MES 供应商真正要签收的“系统基线”。6.1 自动生成 DDL用 Python 把 PDF 字段表转成可执行建库脚本# 基于前面提取的字段列表生成完整 DDL def generate_ddl(fields_dict): ddl [-- 工厂数据库管理系统 DDL 生成于 , datetime.now().isoformat()] ddl.append(PRAGMA encoding UTF-8;) ddl.append(PRAGMA journal_mode WAL;) # 按实体分组需提前定义 groups {equipment: [...], recipe: [...]} for table_name, field_list in groups.items(): ddl.append(f\n-- {table_name} 表) ddl.append(fCREATE TABLE IF NOT EXISTS {table_name} () cols [] for f in field_list: col_def f{f[name]} {f[type]} if f.get(not_null): col_def NOT NULL if f.get(default): col_def f DEFAULT {f[default]} if f.get(check): col_def f CHECK ({f[check]}) cols.append(f {col_def}) ddl.append(,\n.join(cols)) # 主键 if table_name equipment: ddl.append( PRIMARY KEY (plant_code, equipment_id)) ddl.append();) return \n.join(ddl) # 输出到文件甲方签字后即为基线版本 with open(factory_db_baseline_v1.0.ddl, w, encodingutf-8) as f: f.write(generate_ddl(extracted_fields))为什么必须生成 DDL因为 PDF 里的“字段说明”无法执行而 DDL 是数据库的宪法——甲方签了字后续任何字段增删都需走变更流程避免开发随意加is_deleted这种破坏范式的字段。6.2 字段业务字典Excel 表让产线班长也能看懂每个字段用openpyxl生成带格式的 Excel包含 6 列字段英文名字段中文名数据类型是否为空业务含义PDF 页码示例值equipment_id设备编号TEXT(32)NOT NULL设备唯一标识由工厂代码流水号组成P8P01-E001linear_percentage线性百分比INTEGERNOT NULL工艺参数达标率0~100 整数P1595血泪经验产线班长不看代码但会盯着“示例值”栏。P01-E001比VARCHAR(32)更让他明白该填什么。这个 Excel 表是我每次项目启动会上第一个投影展示的材料。6.3 REST API 接口文档用 Swagger 自动生成对接 MES 的第一道门用 Flask-SQLAlchemy Flask-RESTX 生成 OpenAPI 3.0 YAMLfrom flask import Flask from flask_restx import Api, Resource, fields from models import Equipment, RecipeExecution # SQLAlchemy 模型 app Flask(__name__) api Api(app, version1.0, title工厂数据库 API, descriptionPDF 定义的数据库对外接口) # 定义 Swagger 模型 equipment_model api.model(Equipment, { plant_code: fields.String(requiredTrue, description工厂代码), equipment_id: fields.String(requiredTrue, description设备编号), capachamber_name: fields.String(description腔体名称), chamber_total_count: fields.Integer(description腔体总数), }) api.route(/api/equipment) class EquipmentList(Resource): api.doc(list_equipment) api.marshal_list_with(equipment_model) def get(self): return Equipment.query.all() if __name__ __main__: app.run(debugFalse) # 生产环境关闭 debug访问http://localhost:5000/swagger.json即可获得标准 YAML导入 Swagger UI 或 Postman。甲方信息科用它验证 MES 是否能正确调用审计方用它确认“数据出口”是否符合 PDF 的安全要求如created_by字段是否必填。最后说一句这份 PDF 不是你项目的终点而是你和产线、IT、审计三方达成共识的起点。我坚持把 PDF 里的每个字段都变成可执行的 SQL、可验证的 Python、可签字的 Excel——因为工厂里没有“理论上应该”只有“此刻数据对不对”。希望帮到你。本文还有配套的精品资源点击获取