SMT上料防错的数据结构怎么设计?站位表、飞达、料盘三层绑定建表实践
SMT上料防错要靠数据结构兜底,而不是靠操作员和班长用眼睛对料号:把“程序里的站位表”“飞达(Feeder)实物”“料盘(Reel)批次”拆成独立的主表,再用两张带生效区间的绑定表把它们串起来,扫码时由数据库约束和一条校验SQL拦截错料。本文把这套建模叫作“三层绑定、装即绑、卸即断”:上料核对、接料换盘、质量追溯共用同一份数据,不会出现上料记录、换料台账和追溯报表三份数据互相对不上的情况。下面给出表结构、建表SQL、校验SQL和上线验收项,示例基于 MySQL 8,PostgreSQL 的差异在文中单独说明。
上料防错的数据结构指什么?
上料防错的数据结构,是指用“站位表版本—飞达—料盘”三层实体,加上“站位-飞达”“飞达-料盘”两张时间区间绑定表,来回答“某一时刻、某台贴片机、某个站位上装的是哪个飞达,飞达上挂的是哪一盘料”的数据模型。
三层实体要拆开,是因为它们的变化节奏完全不同:站位表随产品和程序版本变化,一个版本可能用几个月;飞达是设备资产,会在几台贴片机之间来回流转,还有校准周期;料盘是消耗品,一盘几千颗,用完就换,接料时新旧两盘还会短时间并存。把三者塞进一张“机台|站位|料号|料盘码”的宽表,每次换飞达或接料都只能覆盖更新,历史就丢了,事后追溯某块板用了哪一盘料时无从查起。
需要建哪几张表?
核心是五张表加一张日志表,表名和字段可以按自己的命名规范改,但拆分方式建议保持。
| 表名 | 关键字段 | 用途 |
|---|---|---|
| smt_slot_plan | plan_id、program_name、product_code、machine_id、side、version、status | 站位表头,一个程序版本一行,status 区分草稿/生效/停用 |
| smt_slot_plan_item | plan_id、slot_no、sub_slot、part_no、alt_group、feeder_type | 站位明细,每个站位应上什么料、用什么规格的飞达 |
| feeder | feeder_sn、feeder_type、width_mm、status、calib_due_date | 飞达主档,状态和校准到期日用于上料前拦截 |
| reel | reel_id、part_no、lot_no、date_code、vendor_code、qty_init、qty_remain、msl_level、open_time | 料盘档案,来自来料贴标或供应商条码解析 |
| slot_feeder_bind | machine_id、slot_no、sub_slot、feeder_sn、plan_id、mount_time、unmount_time | 站位-飞达绑定,装上写 mount_time,卸下写 unmount_time |
| feeder_reel_bind | feeder_sn、reel_id、bind_type、bind_time、unbind_time | 飞达-料盘绑定,bind_type 区分上料 LOAD 和接料 SPLICE |
| load_check_log | scan_time、operator_id、machine_id、slot_no、feeder_sn、reel_id、result、reason | 每次扫码校验的结果,含失败原因,用于审计 |
替代料单独用 alt_group 表达,组成员放在一张小表 part_alt(alt_group、part_no)里:同一 alt_group 下的料号在该站位可以互换,校验时按组匹配,不要把替代料写成站位明细的多行,否则会把“一个站位只能放一种料”的约束打破。
建表SQL怎么写?
关键点只有一个:同一站位在同一时刻只能有一条“未卸下”的绑定。MySQL 不支持带条件的部分索引,可以用生成列把“未卸下”转换成 1、已卸下转换成 NULL,再建 UNIQUE 约束,因为 UNIQUE 索引允许多个 NULL。
CREATE TABLE smt_slot_plan_item (
plan_id BIGINT NOT NULL,
slot_no VARCHAR(16) NOT NULL,
sub_slot VARCHAR(4) NOT NULL DEFAULT '0',
part_no VARCHAR(40) NOT NULL,
alt_group VARCHAR(40) NULL,
feeder_type VARCHAR(20) NOT NULL,
PRIMARY KEY (plan_id, slot_no, sub_slot)
);
CREATE TABLE slot_feeder_bind (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
machine_id VARCHAR(20) NOT NULL,
slot_no VARCHAR(16) NOT NULL,
sub_slot VARCHAR(4) NOT NULL DEFAULT '0',
feeder_sn VARCHAR(40) NOT NULL,
plan_id BIGINT NOT NULL,
mount_time DATETIME(3) NOT NULL,
unmount_time DATETIME(3) NULL,
is_open TINYINT GENERATED ALWAYS AS (IF(unmount_time IS NULL, 1, NULL)) STORED,
UNIQUE KEY uk_slot_open (machine_id, slot_no, sub_slot, is_open),
UNIQUE KEY uk_feeder_open (feeder_sn, is_open),
KEY idx_feeder_time (feeder_sn, mount_time)
);
CREATE TABLE feeder_reel_bind (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
feeder_sn VARCHAR(40) NOT NULL,
reel_id VARCHAR(60) NOT NULL,
bind_type ENUM('LOAD','SPLICE') NOT NULL,
bind_time DATETIME(3) NOT NULL,
unbind_time DATETIME(3) NULL,
KEY idx_feeder_open (feeder_sn, unbind_time),
KEY idx_reel (reel_id, bind_time)
);
uk_feeder_open 保证一个飞达不会同时出现在两个站位。feeder_reel_bind 没有加同样的约束,是因为接料(SPLICE)期间新旧两盘会并存一段时间,旧盘在用完后才写 unbind_time。PostgreSQL 可以直接写 CREATE UNIQUE INDEX ... WHERE unmount_time IS NULL,不需要生成列。所有时间字段由数据库服务器写入,不信任扫码枪或平板的本地时间。
绑定为什么用生效区间,而不是覆盖更新?
覆盖更新只能回答“现在装的是什么”,生效区间才能回答“当时装的是什么”,而追溯要问的恰恰是当时。三种常见写法的取舍如下:
| 写法 | 能否追溯历史 | 并发安全 | 反查复杂度 | 适用情况 |
|---|---|---|---|---|
| 宽表覆盖更新 | 不能,历史被覆盖 | 依赖程序加锁 | 低 | 只做上料提示、不做追溯 |
| 绑定表+生效区间 | 能,按时间区间重叠查询 | 由 UNIQUE 约束兜底 | 中 | 需要上料防错和单板追溯的产线 |
| 纯事件流水 | 能,但要回放事件 | 高 | 高,需要先重建状态 | 已有消息总线、事件量很大的工厂 |
多数电子厂选第二种就够了:它把“当前状态”和“历史”放在同一张表里,当前状态就是 unmount_time 为空的那一行。
上料扫码时的校验SQL怎么写?
一次上料扫码的顺序是“站位码→飞达码→料盘码”,校验在一个事务里完成:先确认飞达可用且规格匹配,再确认料盘料号属于该站位的应上料号或替代组,通过后写两张绑定表和日志。
SELECT i.part_no AS plan_part, r.part_no AS reel_part,
f.status, f.calib_due_date, i.feeder_type, f.feeder_type AS real_type
FROM smt_slot_plan p
JOIN smt_slot_plan_item i ON i.plan_id = p.plan_id
JOIN feeder f ON f.feeder_sn = :feeder_sn
JOIN reel r ON r.reel_id = :reel_id
WHERE p.machine_id = :machine_id
AND p.status = 'ACTIVE'
AND i.slot_no = :slot_no AND i.sub_slot = :sub_slot
AND (r.part_no = i.part_no
OR (i.alt_group IS NOT NULL AND r.part_no IN
(SELECT part_no FROM part_alt WHERE alt_group = i.alt_group)));
返回空行就是料号不符;有行再逐项判断飞达状态、校准日期、飞达规格和料盘的湿敏开封时间。每一种失败都写进 load_check_log.reason,用固定的原因代码(如 PART_MISMATCH、FEEDER_CALIB_EXPIRED、MSL_EXPIRED),方便按原因统计。追溯反查时,用单板过站时间去和两张绑定表做区间匹配:mount_time <= :t AND (unmount_time IS NULL OR unmount_time > :t),就能得到这块板在这台机上每个站位用的料盘。
落地步骤怎么排?
- 统一站位号格式:不同品牌贴片机导出的站位号写法不同(如“1-L-12”和“12L”),先定一个内部格式和转换规则,再导入站位表。
- 先建飞达主档和料盘贴标:飞达贴耐磨标签,料盘在来料检验时生成 reel_id,解析供应商条码时保留原始条码字符串。
- 导入站位表并做版本管理:每次程序改动生成新版本,切换版本时在同一事务里关闭旧版本的所有未卸下绑定。
- 上线扫码校验:先在一条线试运行,只记录不拦截,核对一周的失败原因,修正替代料组和站位映射后再开启拦截。
- 接通追溯反查:用过站数据和绑定区间做一次真实批次的反查演练,确认能从单板查到料盘、从料盘批次查到受影响的单板。
常见坑有哪些?
- 替代料没建组:操作员被迫“先上再说”,系统里出现大量人工放行记录,防错形同虚设。
- 双轨飞达没有 sub_slot:8mm 双轨飞达一个站位挂两种料,没有子站位字段就只能存一种。
- 接料时没有记录 SPLICE:新盘直接覆盖旧盘,旧盘余下的那段料对应的单板追不到。
- 换版本不关旧绑定:旧版本的绑定一直“未卸下”,新版本上料时被 UNIQUE 约束拦住,现场只好让管理员手工删数据。
- 料盘余量只做理论扣减:抛料和试打不计入,余量越算越不准,预警换料的时间点跟着失真。
- 时间取终端本地时间:几台平板时钟不同步,区间匹配出现重叠或空档,反查结果时多时少。
- 失败原因只写“校验失败”:没有原因代码就无法统计,问题到底出在料号、飞达还是湿敏管控说不清。
车间里是什么场景?
一个典型场景:夜班换线,贴片机从 A 产品切到 B 产品,B 产品程序里 12 号站位是 0402 电容,替代料有两个供应商。操作员先在线边仓领料,扫站位码、飞达码、料盘码,系统发现飞达校准已过期,提示换一个同规格飞达;换好后再扫,料盘是替代组里的另一供应商料号,校验通过并写入绑定。凌晨这盘料快用完,操作员接料,系统写一条 SPLICE 绑定,旧盘在打完后写解绑时间。第二天品质发现某个料批次有问题,用料盘批次查 feeder_reel_bind,再按时间区间关联过站记录,就能圈出受影响的单板范围。
上线怎么验收?
- 错料拦截:故意扫一盘非本站位料号的料,系统拒绝并记录 PART_MISMATCH。
- 并发约束:两个终端同时把同一飞达绑到不同站位,只有一个成功。
- 版本切换:切换程序版本后,旧版本没有残留的未卸下绑定。
- 接料追溯:对一次接料前后生产的单板,反查结果能分别对应到新旧两盘。
- 反向反查:任选一个已生产完的真实料盘批次,能查出全部受影响单板,并记录查询耗时作为基线。
FAQ
问:站位表能直接用贴片机程序导出的文件吗?
答:可以作为来源,但要经过格式转换和人工确认再生效,导出文件里通常没有替代料组和飞达规格。
问:飞达和料盘要不要合成一个码?
答:不建议。飞达是资产、料盘是消耗品,合码后换盘就要重贴标签,追溯也分不清是飞达问题还是来料问题。
问:没有 MES 时能先做这几张表吗?
答:可以。这几张表本身不依赖某个系统,先用扫码终端加数据库落地,后续接入 MES 时保持表结构和时间口径不变即可。
问:绑定表数据量大了怎么办?
答:按月分区或把已卸下超过一定时间的记录归档到历史表,反查时按时间范围路由;当前状态只查 unmount_time 为空的行,量不会大。
问:手工放行要不要允许?
答:建议保留但必须记录放行人、原因代码和时间,并单独统计放行次数,放行多说明替代料组或站位表需要修正。
更新于2026年11月

浙公网安备 33010602011771号