前言
在制造业 MES 项目里,核心计算逻辑有很大一部分是写在 SQL Server 存储过程里的。这不是技术选型偏好,而是现实约束:低代码插件平台能配置表单和流程,但复杂的聚合、跨库关联、区间判断,最终还是得落到 SQL 上。
这一篇用一个真实项目------制程破屏率统计系统------来讲两个我认为最重要的设计决策:
- 业务阈值必须配置化,不能硬编码
- 区间匹配用
OUTER APPLY,而不是CASE WHEN或BETWEEN
同时会拆解一个上线后真实发生的问题:数据口径不一致导致的误报与漏报。
一、需求:破屏率超标自动判定
先交代业务背景。
屏幕是电视整机里价值最高、也最易损的部件,产线破屏率是核心质量指标。超标需要发起奖惩流程。
判定规则(脱敏后的示意值):
|------------|--------|
| 尺寸段 | 目标破屏率 |
| ≤ 32 寸 | 0.025% |
| 32 ~ 50 寸 | 0.040% |
| ≥ 55 寸 | 0.050% |
统计维度有三个,触发频率不同:
|-----|----|-------------------|
| 维度 | 周期 | 触发时间 |
| 线体 | 按天 | 每日 08:00 计算前一天 |
| 车间 | 按月 | 每月 1 日 09:00 计算上月 |
| 生产部 | 按月 | 每月 1 日 09:00 计算上月 |
二、第一个决策:把阈值抽成配置表
2.1 最直观的写法,也是最容易埋雷的写法
很多人第一反应是这样写:
这段代码的问题不是"能不能跑",而是每次业务标准调整都要改代码。
改代码意味着什么?在制造业意味着:
一套流程下来,快则两天,慢则一周。而品质标准调整往往是"这个月就开始执行"。
2.2 配置表设计
我的做法是把阈值抽成一张区间配置表:
设计要点有三个:
① 区间用「左开右闭」+ NULL 表示无上限
(MinInch, MaxInch] 的形式避免了区间重叠的歧义。最后一档 MaxInch = NULL 表示"以上全部",比写一个 999 之类的魔法数字干净得多。
② 保留 IsEnabled 而不是物理删除
配置变更要留痕。哪天有人问"6 月份的标准是多少",能查得到。
③ 带 UpdatedBy / UpdatedAt
制造业的配置表一定要有审计字段。这不是形式主义------当数据出问题需要追溯时,第一个被问到的就是"这个标准谁改的、什么时候改的"。
2.3 配置化带来的额外收益
|--------|------------------------------------|
| 收益 | 说明 |
| 变更免发布 | 业务标准调整只需 UPDATE 一行 |
| 支持多套标准 | 加一个 StandardType 字段就能支持不同产品线不同标准 |
| 可审计 | 配合审计表可查历史变更 |
| 可测试 | 测试环境灌不同配置即可覆盖边界场景 |
我在另一个项目(超周期发料管控)里也用了同样的思路------物料最大存储周期查表获取,新增物料类型只需加一条记录,不用动代码。这两个项目共用一套"规则配置化"的方法论。
三、第二个决策:区间匹配用什么写法
有了配置表,接下来的问题是:怎么把一条记录的尺寸映射到对应的区间?
我对比了三种方案。
方案 A:CASE WHEN 硬编码
前面已展示。缺点 :阈值改了要改代码,区间多了 CASE 分支爆炸。
方案 B:BETWEEN 关联
问题:
ISNULL(c.MaxInch, 2147483647)是个魔法数字,可读性差
- 如果配置表存在区间重叠(人为录入错误),会匹配出多条记录,导致数据翻倍
- 无法保证"匹配最精确的那一条"
方案 C:OUTER APPLY + TOP 1(推荐)
优势:
|---------------|----------------------------------------|
| 优势 | 说明 |
| NULL 上界自然表达 | OR c.MaxInch IS NULL,无需魔法数字 |
| 天然防重复 | TOP 1 保证一条记录最多匹配一档,杜绝数据翻倍 |
| 优先级可控 | ORDER BY MinInch DESC 明确"命中多条时取最精确档" |
| 区间顺序无关 | 不依赖配置表的插入顺序 |
关键理解 :
APPLY是逐行执行 的表值函数式关联。左表每一行都会拿自己的值去右表查一次,因此可以在里面写TOP 1 + ORDER BY。这是JOIN做不到的。
性能方面,如果 Cfg_BrokenRateStandard 只有几十行、几百行,APPLY 的开销可以忽略。如果配置表很大,在 (IsEnabled, MinInch, MaxInch) 上建索引即可。
三种方案对比
|-----------------|----------|---------|------------|------|
| 方案 | 改阈值成本 | NULL 上界 | 区间重叠防护 | 可维护性 |
| A CASE WHEN | 改代码 + 发布 | 天然支持 | 顺序敏感 | 差 |
| B BETWEEN | 改数据 | 需魔法数字 | 会翻倍 | 中 |
| C OUTER APPLY | 改数据 | 自然表达 | TOP 1 防护 | 优 |
四、完整存储过程模板
把上面的设计组合起来,得到一个可直接复用的多维度统计存储过程:
几个容易被忽略的细节
① NULLIF(r.InputQty, 0) 防除零
生产数据里零投入的记录是常态(比如工单建了但没开工)。不加 NULLIF 会直接抛 Divide by zero 错误,让整个定时任务失败。
② WITH (NOLOCK) 的使用边界
统计类查询加 NOLOCK 可以避免阻塞产线的实时写入。但要注意:NOLOCK 可能读到脏数据。对于"统计报表"可接受,对于"财务核算"绝不能用。
③ SET XACT_ABORT ON
配合事务使用,保证出错时完整回滚。
④ 用 @StatType 参数而非三个存储过程
三个维度逻辑高度相似,参数化后只维护一份代码。代价是 CASE 表达式在 GROUP BY 里要重复写一遍------这点冗余是值得的。
五、上线后的真实问题:误报与漏报
功能上线第三天,业务方反馈了两类问题:
- "5 车间 50 寸的没超标,系统却给我推了罚款流程"(误报)
- "5 车间 32 寸以下的超标了,系统没推"(漏报)
误报和漏报同时出现,说明不是阈值配置问题,而是取数口径问题。
排查过程
我的排查方法是逐条比对,而不是看代码猜:
- 业务方提供她手工统计的 Excel(含明细行)
- 我导出系统统计的明细行
- 两边按「车间 + 尺寸 + 日期」做全外连接,逐条标差异
比对结果发现:退料屏检明细表里包含了多种退料类型,其中有些类型(如来料不良退料、工程试验退料)不应计入"制程破屏"。原逻辑把它们全算进去了。
修正方案就是在 WHERE 里加上类型过滤:
经验总结
|-----------------------|----------------------------|
| 教训 | 应对方式 |
| 开发期以为的"数据口径"和业务实际理解不同 | 上线前用真实历史数据回跑 1 个月,交业务方人工复核 |
| 误报比漏报更容易被发现 | 主动回访,不要等业务方来提 |
| 一次修不好是正常的 | 建立"口径对照表",把每条过滤规则写进设计文档 |
可复用做法 :跨系统/自动判定类功能上线后,前两周是数据校准黄金窗口。 这段时间必须每天主动找业务方对一次数据,而不是"上线即结束"。
六、附赠案例:超周期发料管控的编码前缀清洗
再给一个同类型的存储过程实战案例,问题是另一个方向:多格式编码的归一化。
业务背景
PCB 等电子物料有存储周期要求,超期未烘烤直接投线会导致焊接不良。系统需要在发料时校验并拦截。
物料编码格式不统一,有的带字母前缀(N205xxx、ZK601xxx),有的纯数字(205xxx)。原逻辑需要为每种格式单独写分支,维护成本高。
解决方案:前缀清洗函数
LIKE '[NZKS]%' 是 SQL Server 支持的字符集匹配,比 IN (SUBSTRING(@p,1,1), 'N','Z',...) 简洁。
核心校验逻辑
这次重构改了什么
|------|-----------------------------|-----------|
| 项 | 重构前 | 重构后 |
| 规则数量 | 2 套(SKD/CKD 固定 20 周、MTC 查表) | 1 套(统一查表) |
| 缓冲阈值 | 20 周 / 8 周两套 | 取消,仅超期拦截 |
| 编码处理 | 按格式分支 | 统一前缀清洗 |
| 管控盲区 | 205/601 开头料号被屏蔽 | 全部纳管 |
最关键的决策是取消"最低剩余缓冲阈值"。 缓冲阈值的设计初衷是预留烘烤时间,但现场完全可以通过"发料后先烘烤再投线"消化,系统提前锁死属于过度管控,直接影响产线开工。
判断原则 :当"质量安全"和"产线不停"冲突时,优先选择不阻断但留痕/预警的方案;只有在会造成不可逆品质事故时才硬阻断。
七、存储过程编写清单
把这一篇的经验收敛成一张可勾选的清单:
设计阶段
- 业务阈值/规则是否抽成了配置表?
- 配置表是否有
IsEnabled+ 审计字段(UpdatedBy / UpdatedAt)?
- 区间设计是否避免了重叠?
NULL上界是否自然表达?
编码阶段
- 除法是否用
NULLIF防零?
- 多维度逻辑是否参数化,而不是复制多份?
NOLOCK是否只用于统计场景?
- 是否设置了
SET NOCOUNT ON/SET XACT_ABORT ON?
- 错误提示是否包含可定位的信息(品号、线体、具体数值)?
上线阶段
- 是否用真实历史数据回跑并交业务方复核?
- 是否新旧逻辑并行运行比对?
- 口径规则是否写进了设计文档?
- 是否安排了上线后 2 周的每日数据校准?
下篇预告
第 3 篇《MES 与 OA 系统集成:定时任务 + 接口开发的全链路自动化》
存储过程算出超标结果只是第一步。下一篇讲怎么把它推到 OA 系统、自动创建审批流程:
- 三种跨系统集成模式(数据库直连 / 中间表 / API 接口)的选型对比
- 定时任务设计:频率、幂等、失败重试、断点补跑
- OA 流程自动创建的字段映射与标题设计
- 消息通道:企业微信机器人 + 邮件推送的实现
- 老化测试产出自动统计推送的完整案例
免责声明:本文业务参数、表名、字段名均为脱敏示意,涉及人员、客户、子公司均以角色或代号指代,不代表任何企业真实数据。