2. 数据模型设计

配方数据看起来就是一堆物料和比例,对吧?
但真上手做系统,你会发现事情没那么简单。
我见过太多项目,一开始随便建个表存数据,结果后面扩展时改得想哭。

这一章,我们就来聊聊怎么把配方数据「结构化管理」。
说白了,就是给数据定规矩——哪些是核心实体,它们怎么关联,每个字段该有什么约束。

配方数据的核心实体

先问个问题:一个配方系统里,最少需要几张表?
我个人习惯,至少拆成三张核心表:配方头、配方行、物料主数据。

实体 英文名 核心作用
配方头 RecipeHeader 记录配方编号、版本、总产量、状态等全局信息
配方行 RecipeLine 记录每种原料的用量、顺序、损耗率
物料主数据 MaterialMaster 存储原料/半成品/成品的编码、名称、单位、密度等

嗯,这里要注意:
千万别把物料名称直接写死在配方行里。
我在项目中遇到过,有人把「白糖」直接存成字符串,后来物料改名,所有历史配方全乱了。

避坑指南: 我曾经接手过一个系统,配方行里存的是物料名称而不是物料ID。结果物料一改名,所有历史配方都查不到对应原料了。所以,配方行必须引用物料ID,不能直接存名称。

实体关系模型(ER图)

三张表之间的关系,其实很清晰:

  • 配方头 → 配方行:一对多。一个配方可以有多个原料行。
  • 物料主数据 → 配方行:一对多。一个物料可以被多个配方引用。
  • 配方头 → 物料主数据:间接关联,通过配方行连接。

下面这张图,是我常用的配方ER图结构:

配方数据实体关系模型 RecipeHeader 配方头 recipe_id (PK) version, status, total_qty RecipeLine 配方行 line_id (PK) recipe_id (FK), material_id (FK) MaterialMaster 物料主数据 material_id (PK) code, name, unit, density 1 N 1 N 关系说明 • 一个配方头对应多条配方行(一对多) • 一个物料可以被多个配方行引用(一对多) • 配方行是关联配方头和物料主数据的桥梁
我的习惯: 实际项目中,我还会加一张「配方版本历史表」,用来记录每次修改的差异。这样追溯起来特别方便,审计也容易过。

属性与约束定义

光有表结构还不够,每个字段的约束才是关键。
你想想看,如果用量字段允许负数,那生产时配料系统会怎么处理?

下面是我常用的字段约束清单:

配方头(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 关联工序,但简单场景下,一个整数排序字段就够了。

数据模型设计的常见误区

做配方数据模型,有几个坑我踩过,分享给你:

  1. 把配方头和配方行合并成一张表
    这样会导致大量重复的配方头信息,更新时容易不一致。
  2. 物料信息直接写在配方行里
    前面说过了,物料改名就全乱了。
  3. 用量字段用整数
    配方里经常有0.5kg、0.125L这种小数,用整数会丢失精度。
  4. 没有版本控制
    配方改了一次,旧版本就找不回来了。生产时如果出问题,没法回溯。
避坑指南: 我曾经因为用量字段用了 FLOAT 类型,结果累计计算时出现精度误差,导致一批产品原料配比不对。后来全部改成 DECIMAL(12,4),问题才解决。所以,金额、用量、重量等字段,一律用 DECIMAL,别用 FLOAT。

小结

这一章我们聊了配方数据的核心实体、ER图关系、字段约束和常见误区。
说白了,数据模型设计就是给数据「立规矩」。
规矩立好了,后面开发、维护、扩展都会轻松很多。

嗯,记住三句话:

  • 配方头管全局,配方行管明细,物料主数据管基础信息
  • 外键引用不能省,字段约束不能少
  • 版本控制一定要做,不然早晚后悔