| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- -- ============================================================================
- -- S5-IQC-B1 · 原材料检规 ↔ 物料 派生映射桥表
- --
- -- 目标:为 S5 IQC 建立 qms_jygf → ItemMaster 的稳定映射基础设施。
- -- 本批 **不改变任何现有业务写路径行为**:不动 qms_qcp_inspbill、
- -- 不动 GenerateInspBill、不动 qms_jygf / qms_jygfzb、不动报检数据。
- --
- -- ⚠️ 数据回填范围:**仅 UAT-A 租户(SysOrg.Code = 'UATTEST_CHL')**
- -- ⚠️ 明确不回填 797403760988229 的 4704 条历史检规,不治理其他租户。
- -- ⚠️ 表结构本身是多租户通用的;只有下方 ③ 的 seed 是 tenant scoped。
- --
- -- 写入范围:CREATE TABLE 1 · INSERT(仅 UAT-A) · UPDATE 0 · DELETE 0 · 其他 DDL 0
- --
- -- ---------------------------------------------------------------------------
- -- 【分词等价性保证 —— 本脚本最重要的约束】
- --
- -- 运行期分词口径唯一定义在 C# S5IqcMaterialTokenizer:
- -- 分隔符 = ; ; , , 、 : : / 空格 Tab CR LF (**不含 . - _**,它们出现在合法物料码内部)
- -- 标准化 = TRIM + UPPER
- -- 分级 = EXACT(命中 ItemMaster) / NOT_FOUND / GLUE_UNRESOLVED
- --
- -- SQL 无法可靠复刻上述正则。为杜绝「迁移一套规则、运行期另一套规则」,本脚本
- -- **只为不含任何分隔符的单 token 检规生成映射**(见 ③ 的 NOT LIKE 守卫)——
- -- 此时分词退化为恒等变换 TRIM(UPPER(wlbm)),SQL 与 C# 结果**可证明相同**。
- -- 含分隔符的检规一律不由本脚本落地,必须调用 POST /api/S5IqcSpecMap/rebuild 生成。
- --
- -- 实测(2026-09-02)UAT-A 8 条检规的 wlbm 全部无分隔符 → 本脚本覆盖 8/8,无遗漏。
- -- 该守卫同时保证:即使将来 UAT-A 新增了多物料检规,脚本也只会「少做」不会「做错」。
- -- ---------------------------------------------------------------------------
- --
- -- 幂等:CREATE TABLE IF NOT EXISTS + INSERT ... WHERE NOT EXISTS(唯一业务键)。
- -- 重复执行结果恒等,不产生重复行。
- -- ============================================================================
- -- ===========================================================================
- -- ① 桥表
- -- 唯一约束 uk_s5_iqc_map(tenant_id, spec_id, raw_token, is_manual):
- -- raw_token 决定 material_code,故该组合即「spec_id + material_code 唯一」的
- -- 等价防重复机制,且在 material_code 为 NULL(NOT_FOUND/GLUE) 时依然有效
- -- (MySQL 唯一索引允许多个 NULL,若用 material_code 做键将无法防重)。
- -- ===========================================================================
- CREATE TABLE IF NOT EXISTS ado_s5_iqc_spec_material_map (
- id BIGINT NOT NULL COMMENT '主键(雪花)',
- tenant_id BIGINT NOT NULL COMMENT '租户ID',
- spec_id BIGINT NOT NULL COMMENT '原材料检规ID → qms_jygf.id',
- spec_no VARCHAR(100) NULL COMMENT '检规文件编号 wjbh(冗余,随主表同步刷新)',
- spec_version VARCHAR(50) NULL COMMENT '检规版本 bb(冗余,随主表同步刷新)',
- seq INT NOT NULL DEFAULT 0 COMMENT 'token 在 wlbm 中的序号;人工绑定行为 0',
- raw_token VARCHAR(200) NOT NULL COMMENT 'wlbm 切分出的原始片段(保留原貌供审计)',
- material_code VARCHAR(100) NULL COMMENT '标准化后的物料码;仅 EXACT/MANUAL 落值,其余 NULL(绝不猜物料)',
- match_status VARCHAR(20) NOT NULL COMMENT 'EXACT / MANUAL / NOT_FOUND / GLUE_UNRESOLVED',
- source_raw TEXT NULL COMMENT '检规 wlbm 原始全串(审计用)',
- is_manual TINYINT NOT NULL DEFAULT 0 COMMENT '1=人工绑定,自动同步永不删除',
- note VARCHAR(500) NULL COMMENT '人工绑定备注',
- create_time DATETIME NULL,
- update_time DATETIME NULL,
- create_user_id BIGINT NULL,
- PRIMARY KEY (id),
- UNIQUE KEY uk_s5_iqc_map (tenant_id, spec_id, raw_token, is_manual),
- KEY idx_s5_iqc_map_item (tenant_id, material_code),
- KEY idx_s5_iqc_map_spec (tenant_id, spec_id)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
- COMMENT='S5 IQC 原材料检规↔物料 派生映射(B-1)';
- -- ===========================================================================
- -- ② 目标租户解析(UAT-A)。解析不到则下方 INSERT 全部为空,脚本安全空转。
- -- ===========================================================================
- SET @uat_a := (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id = t.OrgId
- WHERE o.Code = 'UATTEST_CHL' AND t.Status = 1 LIMIT 1);
- -- ===========================================================================
- -- ③ UAT-A 回填(仅无分隔符的单 token 检规;仅 EXACT/NOT_FOUND 两态)
- --
- -- 分隔符守卫:wlbm 不得含 ; ; , , 、 : : / 空格 Tab CR LF
- -- ('\\' 为 MySQL LIKE 默认转义符,此处无需转义任何目标字符)
- -- GLUE_UNRESOLVED 在「单 token」前提下只可能因长度越界或含中文产生,
- -- 本脚本对这两种情况也如实落 GLUE_UNRESOLVED,与 C# Classify 完全一致。
- -- ===========================================================================
- INSERT INTO ado_s5_iqc_spec_material_map
- (id, tenant_id, spec_id, spec_no, spec_version, seq, raw_token, material_code,
- match_status, source_raw, is_manual, note, create_time, update_time, create_user_id)
- SELECT
- -- 确定性 ID = 3000000000000030000 + 该 spec 在本租户 qms_jygf 中按 id 升序的稳定排名。
- -- 【为什么不用 @rn := @rn + 1】MySQL 8 明确规定 SELECT 中用户变量的求值顺序未定义,
- -- 实测会产生重复值并触发 PRIMARY 冲突(1062)。
- -- 【为什么不用 ROW_NUMBER()】窗口序号是对**过滤后**结果集编号,重跑时若部分行已存在,
- -- 剩余行会被重新编号 → 与首次写入的 id 冲突。
- -- 相关子查询排名只依赖 qms_jygf 自身内容,与本表已有多少行无关,故重跑恒等。
- 3000000000000030000 + (
- SELECT COUNT(*) FROM qms_jygf r
- WHERE r.tenant_id = g.tenant_id AND r.id <= g.id
- ),
- g.tenant_id,
- g.id,
- g.wjbh,
- g.bb,
- 1,
- TRIM(g.wlbm),
- CASE WHEN im.ItemNum IS NOT NULL THEN UPPER(TRIM(g.wlbm)) ELSE NULL END,
- CASE
- WHEN im.ItemNum IS NOT NULL THEN 'EXACT'
- WHEN CHAR_LENGTH(TRIM(g.wlbm)) < 4 OR CHAR_LENGTH(TRIM(g.wlbm)) > 16 THEN 'GLUE_UNRESOLVED'
- WHEN TRIM(g.wlbm) REGEXP '[\\x{4E00}-\\x{9FFF}]' THEN 'GLUE_UNRESOLVED'
- ELSE 'NOT_FOUND'
- END,
- g.wlbm,
- 0,
- NULL,
- NOW(), NOW(), NULL
- FROM qms_jygf g
- LEFT JOIN ItemMaster im
- ON im.tenant_id = g.tenant_id
- AND im.ItemNum = UPPER(TRIM(g.wlbm))
- WHERE @uat_a IS NOT NULL
- AND g.tenant_id = @uat_a
- AND g.wlbm IS NOT NULL
- AND TRIM(g.wlbm) <> ''
- -- 分词等价性守卫:只处理无分隔符的单 token
- AND TRIM(g.wlbm) NOT LIKE '%;%'
- AND TRIM(g.wlbm) NOT LIKE '%;%'
- AND TRIM(g.wlbm) NOT LIKE '%,%'
- AND TRIM(g.wlbm) NOT LIKE '%,%'
- AND TRIM(g.wlbm) NOT LIKE '%、%'
- AND TRIM(g.wlbm) NOT LIKE '%:%'
- AND TRIM(g.wlbm) NOT LIKE '%:%'
- AND TRIM(g.wlbm) NOT LIKE '%/%'
- AND TRIM(g.wlbm) NOT LIKE '% %'
- AND TRIM(g.wlbm) NOT LIKE CONCAT('%', CHAR(9), '%')
- AND TRIM(g.wlbm) NOT LIKE CONCAT('%', CHAR(13), '%')
- AND TRIM(g.wlbm) NOT LIKE CONCAT('%', CHAR(10), '%')
- -- 幂等:同 (tenant, spec, raw_token, is_manual) 已存在则跳过
- AND NOT EXISTS (
- SELECT 1 FROM ado_s5_iqc_spec_material_map x
- WHERE x.tenant_id = g.tenant_id
- AND x.spec_id = g.id
- AND x.raw_token = TRIM(g.wlbm)
- AND x.is_manual = 0);
|