1.0.132.sql 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. -- 1.0.132.sql
  2. -- S0-2026-05-28|物料替代 ItemSubstituteDetail 数据治理导入
  3. --
  4. -- 业务背景:
  5. -- 前端「物料替代」页面(/aidop/s0/manufacturing/material-substitution)
  6. -- 后端 AdoS0ItemSubstituteDetailsController 只查 ItemSubstituteDetail 表(当前 0 行)。
  7. -- 导入数据落在 ic_bom / ic_bom_child / ic_substitute_group_detail 三张 legacy 表。
  8. -- 本脚本将三表 JOIN 后排除原料自身行,导入到 ItemSubstituteDetail。
  9. --
  10. -- 拍板口径:
  11. -- ParentItem = ic_bom.item_number
  12. -- ItemNum = ic_bom_child.item_number
  13. -- SubstituteItem = ic_substitute_group_detail.icitem_number
  14. -- SubstituteQty = ic_substitute_group_detail.replace_qty
  15. -- SubstituteType = '替代'
  16. -- Domain = '8010' (基于 factory_id 字符串化兜底,非正式业务域;后续如业务给正式 Domain,另开批次修正)
  17. -- tenant_id = ic_bom_child.tenant_id
  18. -- 排除原料自身 : g.icitem_number <> c.item_number
  19. --
  20. -- 预期导入行数:1147(已在阶段 2 + 阶段 3 各跑一次只读校验确认)
  21. --
  22. -- 操作流程:
  23. -- 1) 执行段 1【执行前校验】,5 项指标必须命中预期
  24. -- 2) 执行段 2【INSERT SELECT】(含 NOT EXISTS 幂等保护)
  25. -- 3) 执行段 3【执行后对账】,本批次行数应 = 1147
  26. -- 4) 如需回滚,先跑段 4 的 SELECT 预览,再人工取消注释 DELETE
  27. -- ===========================================================================
  28. -- 段 1:执行前校验(只读)
  29. -- 所有 5 项必须全绿才允许执行段 2
  30. -- ===========================================================================
  31. -- 校验 1.1:预计导入行数(应 = 1147)
  32. SELECT COUNT(*) AS expected_insert_rows
  33. FROM ic_bom_child c
  34. INNER JOIN ic_bom b
  35. ON b.Id = c.bom_id
  36. AND b.IsDeleted = 0
  37. INNER JOIN ic_substitute_group_detail g
  38. ON g.substitute_code = c.substitute_code
  39. WHERE c.IsDeleted = 0
  40. AND NULLIF(TRIM(c.substitute_code),'') IS NOT NULL
  41. AND NULLIF(TRIM(g.icitem_number),'') IS NOT NULL
  42. AND g.icitem_number <> c.item_number;
  43. -- 校验 1.2:空值检查(5 个字段都应 = 0)
  44. SELECT
  45. SUM(b.item_number IS NULL OR TRIM(b.item_number) = '') AS empty_parent,
  46. SUM(c.item_number IS NULL OR TRIM(c.item_number) = '') AS empty_itemnum,
  47. SUM(g.icitem_number IS NULL OR TRIM(g.icitem_number) = '') AS empty_substitute,
  48. SUM(g.replace_qty IS NULL) AS null_qty,
  49. SUM(c.tenant_id IS NULL OR c.tenant_id = 0) AS empty_tenant
  50. FROM ic_bom_child c
  51. INNER JOIN ic_bom b
  52. ON b.Id = c.bom_id
  53. AND b.IsDeleted = 0
  54. INNER JOIN ic_substitute_group_detail g
  55. ON g.substitute_code = c.substitute_code
  56. WHERE c.IsDeleted = 0
  57. AND NULLIF(TRIM(c.substitute_code),'') IS NOT NULL
  58. AND g.icitem_number <> c.item_number;
  59. -- 校验 1.3:三元组重复(total = distinct = 1147;dup = 0)
  60. SELECT total_rows, distinct_triples, total_rows - distinct_triples AS dup_rows
  61. FROM (
  62. SELECT
  63. COUNT(*) AS total_rows,
  64. COUNT(DISTINCT CONCAT_WS('|', c.tenant_id, '8010', b.item_number, c.item_number, g.icitem_number)) AS distinct_triples
  65. FROM ic_bom_child c
  66. INNER JOIN ic_bom b
  67. ON b.Id = c.bom_id
  68. AND b.IsDeleted = 0
  69. INNER JOIN ic_substitute_group_detail g
  70. ON g.substitute_code = c.substitute_code
  71. WHERE c.IsDeleted = 0
  72. AND NULLIF(TRIM(c.substitute_code),'') IS NOT NULL
  73. AND g.icitem_number <> c.item_number
  74. ) t;
  75. -- 校验 1.4:目标表当前行数(应 = 0;若 > 0,需评估 NOT EXISTS 的去重效果)
  76. SELECT COUNT(*) AS target_existing_rows
  77. FROM ItemSubstituteDetail;
  78. -- 校验 1.5:tenant_id 三表一致性(应只出现 797403760988229 一行)
  79. SELECT
  80. c.tenant_id AS child_tenant,
  81. b.tenant_id AS bom_tenant,
  82. g.tenant_id AS group_tenant,
  83. COUNT(*) AS row_cnt
  84. FROM ic_bom_child c
  85. INNER JOIN ic_bom b
  86. ON b.Id = c.bom_id
  87. INNER JOIN ic_substitute_group_detail g
  88. ON g.substitute_code = c.substitute_code
  89. WHERE c.IsDeleted = 0
  90. AND NULLIF(TRIM(c.substitute_code),'') IS NOT NULL
  91. GROUP BY c.tenant_id, b.tenant_id, g.tenant_id;
  92. -- ===========================================================================
  93. -- 段 2:INSERT SELECT(数据导入)
  94. -- 含 NOT EXISTS 幂等保护:重复执行不会产生重复数据
  95. -- ===========================================================================
  96. INSERT INTO ItemSubstituteDetail
  97. (ParentItem, ItemNum, SubstituteItem, SubstituteQty,
  98. SubstituteType, Remark, Domain, CreateUser, CreateTime, tenant_id)
  99. SELECT
  100. b.item_number AS ParentItem,
  101. c.item_number AS ItemNum,
  102. g.icitem_number AS SubstituteItem,
  103. g.replace_qty AS SubstituteQty,
  104. '替代' AS SubstituteType,
  105. CONCAT('migrated from ic_bom_child Id=', c.Id, ', substitute_code=', c.substitute_code) AS Remark,
  106. '8010' AS Domain,
  107. 'data-migration' AS CreateUser,
  108. NOW() AS CreateTime,
  109. c.tenant_id AS tenant_id
  110. FROM ic_bom_child c
  111. INNER JOIN ic_bom b
  112. ON b.Id = c.bom_id
  113. AND b.IsDeleted = 0
  114. INNER JOIN ic_substitute_group_detail g
  115. ON g.substitute_code = c.substitute_code
  116. WHERE c.IsDeleted = 0
  117. AND NULLIF(TRIM(c.substitute_code),'') IS NOT NULL
  118. AND NULLIF(TRIM(g.icitem_number),'') IS NOT NULL
  119. AND g.icitem_number <> c.item_number
  120. AND NOT EXISTS (
  121. SELECT 1
  122. FROM ItemSubstituteDetail x
  123. WHERE x.tenant_id = c.tenant_id
  124. AND x.Domain = '8010'
  125. AND x.ParentItem = b.item_number
  126. AND x.ItemNum = c.item_number
  127. AND x.SubstituteItem = g.icitem_number
  128. );
  129. -- ===========================================================================
  130. -- 段 3:执行后对账(只读)
  131. -- ===========================================================================
  132. -- 对账 3.1:本批次导入行数(应 = 1147)
  133. SELECT COUNT(*) AS migrated_rows
  134. FROM ItemSubstituteDetail
  135. WHERE CreateUser = 'data-migration'
  136. AND Remark LIKE 'migrated from ic_bom_child Id=%, substitute_code=%'
  137. AND Domain = '8010'
  138. AND tenant_id = 797403760988229;
  139. -- 对账 3.2:ItemSubstituteDetail 总行数(应 = 1147,假设目标表初始为 0)
  140. SELECT COUNT(*) AS total_rows FROM ItemSubstituteDetail;
  141. -- 对账 3.3:按 Domain / SubstituteType / tenant_id 分组
  142. SELECT Domain, SubstituteType, tenant_id, COUNT(*) AS row_cnt
  143. FROM ItemSubstituteDetail
  144. GROUP BY Domain, SubstituteType, tenant_id
  145. ORDER BY row_cnt DESC;
  146. -- ===========================================================================
  147. -- 段 4:回滚 SQL 草案
  148. -- 默认全部注释,仅在需要完整回退本批次时人工评审后取消注释
  149. -- 四重限定(CreateUser + Remark + Domain + tenant_id)避免误删人工维护数据
  150. -- ===========================================================================
  151. -- 回滚 4.1:先 SELECT 预览要删除的行数(应 = 阶段 3 实际 INSERT 行数)
  152. -- SELECT COUNT(*) AS rollback_rows_preview
  153. -- FROM ItemSubstituteDetail
  154. -- WHERE CreateUser = 'data-migration'
  155. -- AND Remark LIKE 'migrated from ic_bom_child Id=%, substitute_code=%'
  156. -- AND Domain = '8010'
  157. -- AND tenant_id = 797403760988229;
  158. -- 回滚 4.2:实际 DELETE(仅当预览数对账无误时才取消注释)
  159. -- DELETE FROM ItemSubstituteDetail
  160. -- WHERE CreateUser = 'data-migration'
  161. -- AND Remark LIKE 'migrated from ic_bom_child Id=%, substitute_code=%'
  162. -- AND Domain = '8010'
  163. -- AND tenant_id = 797403760988229;