2. 数据模型设计
配方数据看起来就是一堆物料和比例,对吧?
但真上手做系统,你会发现事情没那么简单。
我见过太多项目,一开始随便建个表存数据,结果后面扩展时改得想哭。
这一章,我们就来聊聊怎么把配方数据「结构化管理」。
说白了,就是给数据定规矩——哪些是核心实体,它们怎么关联,每个字段该有什么约束。
配方数据的核心实体
先问个问题:一个配方系统里,最少需要几张表?
我个人习惯,至少拆成三张核心表:配方头、配方行、物料主数据。
| 实体 | 英文名 | 核心作用 |
|---|---|---|
| 配方头 | RecipeHeader | 记录配方编号、版本、总产量、状态等全局信息 |
| 配方行 | RecipeLine | 记录每种原料的用量、顺序、损耗率 |
| 物料主数据 | MaterialMaster | 存储原料/半成品/成品的编码、名称、单位、密度等 |
嗯,这里要注意:
千万别把物料名称直接写死在配方行里。
我在项目中遇到过,有人把「白糖」直接存成字符串,后来物料改名,所有历史配方全乱了。
避坑指南: 我曾经接手过一个系统,配方行里存的是物料名称而不是物料ID。结果物料一改名,所有历史配方都查不到对应原料了。所以,配方行必须引用物料ID,不能直接存名称。
实体关系模型(ER图)
三张表之间的关系,其实很清晰:
- 配方头 → 配方行:一对多。一个配方可以有多个原料行。
- 物料主数据 → 配方行:一对多。一个物料可以被多个配方引用。
- 配方头 → 物料主数据:间接关联,通过配方行连接。
下面这张图,是我常用的配方ER图结构:
我的习惯: 实际项目中,我还会加一张「配方版本历史表」,用来记录每次修改的差异。这样追溯起来特别方便,审计也容易过。
属性与约束定义
光有表结构还不够,每个字段的约束才是关键。
你想想看,如果用量字段允许负数,那生产时配料系统会怎么处理?
下面是我常用的字段约束清单:
配方头(RecipeHeader)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| recipe_id | INT / UUID | PRIMARY KEY, NOT NULL | 配方唯一标识 |
| recipe_code | VARCHAR(50) | UNIQUE, NOT NULL | 配方编码,业务上唯一 |
| version | INT | NOT NULL, DEFAULT 1 | 版本号,每次修改递增 |
| status | VARCHAR(20) | CHECK IN ('Draft','Active','Archived') | 状态约束,防止脏数据 |
| total_qty | DECIMAL(12,4) | CHECK (> 0) | 总产量,必须大于0 |
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | 创建时间 |
配方行(RecipeLine)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| line_id | INT / UUID | PRIMARY KEY, NOT NULL | 行唯一标识 |
| recipe_id | INT | FOREIGN KEY → RecipeHeader | 外键,关联配方头 |
| material_id | INT | FOREIGN KEY → MaterialMaster | 外键,关联物料主数据 |
| qty | DECIMAL(12,4) | CHECK (> 0) | 用量,必须大于0 |
| unit | VARCHAR(10) | NOT NULL | 单位,如 kg、L、pcs |
| seq | INT | NOT NULL, DEFAULT 0 | 投料顺序,越小越先投 |
| loss_rate | DECIMAL(5,2) | CHECK (0 <= loss_rate <= 100) | 损耗率百分比 |
关键约束总结:
- 所有用量字段必须 > 0,不允许负数或零
- 配方编码必须唯一,不能重复
- 状态字段用 CHECK 约束,只允许 Draft / Active / Archived
- 外键必须引用已存在的记录,防止孤儿数据
一个完整的配方数据示例
光说理论太干,我们来看一个实际的配方数据长什么样:
-- 配方头
recipe_id: 1001
recipe_code: 'RCP-2024-001'
version: 2
status: 'Active'
total_qty: 1000.0000
created_at: '2024-03-15 09:30:00'
-- 配方行(共3行)
line_id: 1, material_id: 201 (面粉), qty: 600.0000, unit: 'kg', seq: 1, loss_rate: 2.50
line_id: 2, material_id: 202 (水), qty: 300.0000, unit: 'L', seq: 2, loss_rate: 1.00
line_id: 3, material_id: 203 (酵母), qty: 100.0000, unit: 'kg', seq: 3, loss_rate: 0.50
你看,这个配方生产1000kg面团,用了600kg面粉、300L水、100kg酵母。
损耗率分别记录,方便后续成本核算。
小技巧: 我习惯在配方行里加一个
seq 字段,用来控制投料顺序。有些系统用 step_id 关联工序,但简单场景下,一个整数排序字段就够了。
数据模型设计的常见误区
做配方数据模型,有几个坑我踩过,分享给你:
- 把配方头和配方行合并成一张表
这样会导致大量重复的配方头信息,更新时容易不一致。 - 物料信息直接写在配方行里
前面说过了,物料改名就全乱了。 - 用量字段用整数
配方里经常有0.5kg、0.125L这种小数,用整数会丢失精度。 - 没有版本控制
配方改了一次,旧版本就找不回来了。生产时如果出问题,没法回溯。
避坑指南: 我曾经因为用量字段用了 FLOAT 类型,结果累计计算时出现精度误差,导致一批产品原料配比不对。后来全部改成 DECIMAL(12,4),问题才解决。所以,金额、用量、重量等字段,一律用 DECIMAL,别用 FLOAT。
小结
这一章我们聊了配方数据的核心实体、ER图关系、字段约束和常见误区。
说白了,数据模型设计就是给数据「立规矩」。
规矩立好了,后面开发、维护、扩展都会轻松很多。
嗯,记住三句话:
- 配方头管全局,配方行管明细,物料主数据管基础信息
- 外键引用不能省,字段约束不能少
- 版本控制一定要做,不然早晚后悔