1.0.412.sql 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. -- ===========================================================================
  2. -- S0-LINESKILL-TENANT-UK-CLOSURE-1 —— 产线岗位(line-post)
  3. --
  4. -- 本脚本做两件互相独立的事:
  5. --
  6. -- 【1】MULTI-TENANT SCHEMA CORRECTNESS(主要目的)
  7. -- LineSkillMaster / LineSkillDetail 的 UNIQUE 索引缺少 tenant_id:
  8. -- IX_LineSkillMaster : Domain + Site + ProdLine + JOBNo
  9. -- IX_LineSkillDetail : Domain + Site + ProdLine + JOBNo + SkillNo
  10. -- 两者都是「跨租户唯一」,一旦 Site 被启用(非 NULL),两个租户持有相同
  11. -- (Domain, Site, ProdLine, JOBNo) 就会互相顶撞。这属于多租户索引缺陷,
  12. -- 必须补 tenant_id。
  13. --
  14. -- ⚠️ 这不是为了「让下面 22 行插得进去」。已实测证明:当前 Site 全为 NULL,
  15. -- MySQL 的 UNIQUE 索引不对 NULL 做相等判定,因此即使不改索引,
  16. -- 11 master + 11 detail 也能插入成功(事务内实测 11/11 + 11/11,随后回滚)。
  17. -- 索引修复的唯一目的是 schema 正确性。
  18. --
  19. -- 【2】line-post 演示数据(22 行)
  20. -- 来源 AIDOP 797:仅取 ProdLine 命中目标租户 LineMaster 的 11 条产线;
  21. -- 其余 29 条因产线不存在于目标租户而丢弃,不为凑数复制。
  22. --
  23. -- Site 处理:保持 NULL。
  24. -- 族级证据显示 Site = 工作组/工作中心类历史列(EmpSkills.Site 注释「工作组」,
  25. -- AIDOPB 实测取值为 数控组/冲压组 等;ProdLineDetail.Site 注释「工作中心编码」),
  26. -- 且 LineSkill 全库 296 行 Site 100% 为 NULL、无 UI、无校验。
  27. -- 因此新行沿用 NULL —— 不得写入租户/工厂/组织编码。
  28. --
  29. -- ⚠️ 事务性说明:MySQL 的 DDL 会隐式提交,且 AutoVersionUpdate 的
  30. -- SqlScriptSplitter.Execute 不开启显式事务。因此本脚本**不是原子的**。
  31. -- 为此所有语句均写成可安全重入:
  32. -- - DDL 以「索引是否已包含 tenant_id」为条件,已修则整段跳过;
  33. -- - 数据以业务键 NOT EXISTS 兜底,重跑影响行数为 0。
  34. -- ===========================================================================
  35. SET @src := (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id = t.OrgId WHERE o.Code = 'AIDOP' AND t.Status = 1 LIMIT 1);
  36. SET @dst := (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);
  37. -- ---------------------------------------------------------------------------
  38. -- 01 · 前置守卫:新 UK 定义下不得存在重复(否则不能建唯一索引)
  39. -- 重复则抛错中止,不带病改索引。
  40. -- ---------------------------------------------------------------------------
  41. SET @dup_m := (SELECT COUNT(*) FROM (
  42. SELECT tenant_id, Domain, Site, ProdLine, JOBNo
  43. FROM LineSkillMaster GROUP BY tenant_id, Domain, Site, ProdLine, JOBNo HAVING COUNT(*) > 1) x);
  44. SET @dup_d := (SELECT COUNT(*) FROM (
  45. SELECT tenant_id, Domain, Site, ProdLine, JOBNo, SkillNo
  46. FROM LineSkillDetail GROUP BY tenant_id, Domain, Site, ProdLine, JOBNo, SkillNo HAVING COUNT(*) > 1) x);
  47. SET @g := IF(@dup_m = 0 AND @dup_d = 0, 'DO 0',
  48. 'SELECT RAISE_LINESKILL_UK_DUPLICATE_FOUND');
  49. PREPARE gstmt FROM @g;
  50. EXECUTE gstmt;
  51. DEALLOCATE PREPARE gstmt;
  52. -- ---------------------------------------------------------------------------
  53. -- 02 · IX_LineSkillMaster → 补 tenant_id
  54. -- 条件:该索引存在且**不含** tenant_id 才重建;已含则整段 no-op(可重入)。
  55. -- ---------------------------------------------------------------------------
  56. SET @m_fixed := (SELECT COUNT(*) FROM information_schema.STATISTICS
  57. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'LineSkillMaster'
  58. AND INDEX_NAME = 'IX_LineSkillMaster' AND COLUMN_NAME = 'tenant_id');
  59. SET @m_exists := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  60. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'LineSkillMaster'
  61. AND INDEX_NAME = 'IX_LineSkillMaster');
  62. SET @s := IF(@m_fixed = 0 AND @m_exists > 0,
  63. 'DROP INDEX IX_LineSkillMaster ON LineSkillMaster', 'DO 0');
  64. PREPARE stmt FROM @s;
  65. EXECUTE stmt;
  66. DEALLOCATE PREPARE stmt;
  67. SET @m_now := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  68. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'LineSkillMaster'
  69. AND INDEX_NAME = 'IX_LineSkillMaster');
  70. SET @s := IF(@m_now = 0,
  71. 'CREATE UNIQUE INDEX IX_LineSkillMaster ON LineSkillMaster (tenant_id, Domain, Site, ProdLine, JOBNo)',
  72. 'DO 0');
  73. PREPARE stmt FROM @s;
  74. EXECUTE stmt;
  75. DEALLOCATE PREPARE stmt;
  76. -- ---------------------------------------------------------------------------
  77. -- 03 · IX_LineSkillDetail → 补 tenant_id(同样条件化、可重入)
  78. -- ---------------------------------------------------------------------------
  79. SET @d_fixed := (SELECT COUNT(*) FROM information_schema.STATISTICS
  80. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'LineSkillDetail'
  81. AND INDEX_NAME = 'IX_LineSkillDetail' AND COLUMN_NAME = 'tenant_id');
  82. SET @d_exists := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  83. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'LineSkillDetail'
  84. AND INDEX_NAME = 'IX_LineSkillDetail');
  85. SET @s := IF(@d_fixed = 0 AND @d_exists > 0,
  86. 'DROP INDEX IX_LineSkillDetail ON LineSkillDetail', 'DO 0');
  87. PREPARE stmt FROM @s;
  88. EXECUTE stmt;
  89. DEALLOCATE PREPARE stmt;
  90. SET @d_now := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  91. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'LineSkillDetail'
  92. AND INDEX_NAME = 'IX_LineSkillDetail');
  93. SET @s := IF(@d_now = 0,
  94. 'CREATE UNIQUE INDEX IX_LineSkillDetail ON LineSkillDetail (tenant_id, Domain, Site, ProdLine, JOBNo, SkillNo)',
  95. 'DO 0');
  96. PREPARE stmt FROM @s;
  97. EXECUTE stmt;
  98. DEALLOCATE PREPARE stmt;
  99. -- ---------------------------------------------------------------------------
  100. -- 04 · LineSkillMaster COPY 11
  101. -- 只取 ProdLine 命中目标租户 LineMaster 的产线(JOIN 即门禁);
  102. -- Site 保持 NULL;RecID 为 auto_increment,不赋值、不复制源主键。
  103. -- ---------------------------------------------------------------------------
  104. INSERT INTO LineSkillMaster
  105. (tenant_id, Domain, Site, ProdLine, JOBNo, JOBDescr, Orderby, WType, RunCrew, CreateTime, UpdateTime)
  106. SELECT @dst, s.Domain, NULL, s.ProdLine, s.JOBNo, s.JOBDescr, s.Orderby, s.WType, s.RunCrew, NOW(), NOW()
  107. FROM LineSkillMaster s
  108. JOIN LineMaster l ON l.tenant_id = @dst AND l.Line = s.ProdLine
  109. WHERE s.tenant_id = @src
  110. AND @src IS NOT NULL AND @dst IS NOT NULL
  111. AND NOT EXISTS (
  112. SELECT 1 FROM LineSkillMaster x
  113. WHERE x.tenant_id = @dst AND x.Domain = s.Domain
  114. AND x.ProdLine = s.ProdLine AND x.JOBNo = s.JOBNo AND x.Site IS NULL);
  115. -- ---------------------------------------------------------------------------
  116. -- 05 · LineSkillDetail COPY 11
  117. -- ★ 内部 ID remap(禁止复制源主键 / 源 PersonSkillId):
  118. -- LineSkillRecID ← 目标租户刚插入的 master RecID(按业务键回查)
  119. -- PersonSkillId ← 目标租户 PersonSkill.Code = 源 SkillNo 的 Id
  120. -- (源库该列指向的是他租户的 PersonSkill,属源侧既有缺陷,不得沿用)
  121. -- ---------------------------------------------------------------------------
  122. INSERT INTO LineSkillDetail
  123. (tenant_id, Domain, Site, ProdLine, JOBNo, SkillNo, LineSkillRecID, PersonSkillId,
  124. required_level, is_enabled, CreateTime, UpdateTime)
  125. SELECT @dst, m.Domain, NULL, m.ProdLine, m.JOBNo, sd.SkillNo, m.RecID, p.Id,
  126. sd.required_level, 1, NOW(), NOW()
  127. FROM LineSkillMaster m
  128. JOIN LineSkillMaster sm
  129. ON sm.tenant_id = @src AND sm.Domain = m.Domain
  130. AND sm.ProdLine = m.ProdLine AND sm.JOBNo = m.JOBNo AND sm.Site IS NULL
  131. JOIN LineSkillDetail sd
  132. ON sd.tenant_id = @src AND sd.LineSkillRecID = sm.RecID
  133. JOIN PersonSkill p
  134. ON p.tenant_id = @dst AND p.Code = sd.SkillNo
  135. WHERE m.tenant_id = @dst AND m.Site IS NULL
  136. AND @src IS NOT NULL AND @dst IS NOT NULL
  137. AND NOT EXISTS (
  138. SELECT 1 FROM LineSkillDetail d
  139. WHERE d.tenant_id = @dst AND d.LineSkillRecID = m.RecID AND d.PersonSkillId = p.Id);