SQL Server 存储过程实战:从破屏率统计看区间匹配与配置化设计(二)

前言

在制造业 MES 项目里,核心计算逻辑有很大一部分是写在 SQL Server 存储过程里的。这不是技术选型偏好,而是现实约束:低代码插件平台能配置表单和流程,但复杂的聚合、跨库关联、区间判断,最终还是得落到 SQL 上。

这一篇用一个真实项目------制程破屏率统计系统------来讲两个我认为最重要的设计决策:

  1. 业务阈值必须配置化,不能硬编码
  1. 区间匹配用 OUTER APPLY ,而不是 CASE WHENBETWEEN

同时会拆解一个上线后真实发生的问题:数据口径不一致导致的误报与漏报


一、需求:破屏率超标自动判定

先交代业务背景。

屏幕是电视整机里价值最高、也最易损的部件,产线破屏率是核心质量指标。超标需要发起奖惩流程。

判定规则(脱敏后的示意值):

|------------|--------|
| 尺寸段 | 目标破屏率 |
| ≤ 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 寸以下的超标了,系统没推"(漏报)

误报和漏报同时出现,说明不是阈值配置问题,而是取数口径问题。

排查过程

我的排查方法是逐条比对,而不是看代码猜:

  1. 业务方提供她手工统计的 Excel(含明细行)
  1. 我导出系统统计的明细行
  1. 两边按「车间 + 尺寸 + 日期」做全外连接,逐条标差异

比对结果发现:退料屏检明细表里包含了多种退料类型,其中有些类型(如来料不良退料、工程试验退料)不应计入"制程破屏"。原逻辑把它们全算进去了。

修正方案就是在 WHERE 里加上类型过滤:

经验总结

|-----------------------|----------------------------|
| 教训 | 应对方式 |
| 开发期以为的"数据口径"和业务实际理解不同 | 上线前用真实历史数据回跑 1 个月,交业务方人工复核 |
| 误报比漏报更容易被发现 | 主动回访,不要等业务方来提 |
| 一次修不好是正常的 | 建立"口径对照表",把每条过滤规则写进设计文档 |

可复用做法跨系统/自动判定类功能上线后,前两周是数据校准黄金窗口。 这段时间必须每天主动找业务方对一次数据,而不是"上线即结束"。


六、附赠案例:超周期发料管控的编码前缀清洗

再给一个同类型的存储过程实战案例,问题是另一个方向:多格式编码的归一化

业务背景

PCB 等电子物料有存储周期要求,超期未烘烤直接投线会导致焊接不良。系统需要在发料时校验并拦截。

物料编码格式不统一,有的带字母前缀(N205xxxZK601xxx),有的纯数字(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 流程自动创建的字段映射与标题设计
  • 消息通道:企业微信机器人 + 邮件推送的实现
  • 老化测试产出自动统计推送的完整案例

免责声明:本文业务参数、表名、字段名均为脱敏示意,涉及人员、客户、子公司均以角色或代号指代,不代表任何企业真实数据。

相关推荐
知产xiao_xin2 小时前
版权的精神权利
经验分享·笔记·知识产权
wukong60264 小时前
有没有现成的GEO AGENT可直接使用?选型标准及适用参考
经验分享
神明不懂浪漫4 小时前
【第一章】MySQL简介
数据库·经验分享·笔记·mysql·oracle
冬木家居14 小时前
40㎡客厅变形记,小家住出大自由[特殊字符]
android·经验分享·笔记·智能家居·微信公众平台
小HANN18 小时前
Linux建站实战:CentOS7+LNMP 从零部署 ECShop 开源电商系统
linux·运维·服务器·经验分享
恒锐丰科技林技术员19 小时前
EG2226 全桥驱动芯片:高压电机逆变驱动的高性价比方案
经验分享·嵌入式硬件·硬件工程
数智启示录20 小时前
PostgreSQL 内存调优实战(第 12 篇):work_mem 只调大 64 倍,峰值为什么远不止 64 倍
运维·数据库·经验分享·postgresql·面试
数字生命卡兹克21 小时前
GPT-6 Astra全面解析 - “欢迎来到AGI时代。”
经验分享
富唯智能机器人1 天前
复合机器人售后质保机制|故障响应速度与零部件备货保障|富唯智能
经验分享