-- ===================================================================================== -- 1.0.517 · S1 · dwd_requirement_examine_detail 升级为「工单当前齐套/缺料」的中台权威表 -- -- 本表没有 SqlSugar 实体,也没有 UpdateScripts 里的 CREATE: -- 建表 DDL 内联在 server/Plugins/Admin.NET.Plugin.AiDOP/Order/S1MdpSyncTransformService.cs -- 的 EnsureS1RuntimeObjectsAsync()(CREATE TABLE IF NOT EXISTS)。 -- ⇒ 新库由那段 DDL 一次建全,旧库由本脚本 ALTER 追加。两处必须同名同类型同顺序,否则新旧环境列序分叉。 -- 本脚本的三个 ADD COLUMN 与该 DDL 里 update_time 之后的三列一一对应。 -- -- 本批变更: -- B1 新增 bom_level / material_role / is_current_flag + idx_req_exam_current -- B3 给齐套三量补「标准列」注释(不新增别名列、不重算,理由见下) -- B7 清理前修数据(理由见文件末尾) -- -- ── B3:齐套三量的标准名 ──────────────────────────────────────────────────────────── -- need_count / use_qty / lack_qty 就是本表「需求量 / 已满足量 / 缺口量」的标准列名。 -- 不新增 required_qty / satisfied_qty / shortage_qty 别名列:别名 = 同一数字两个写入点,迟早对不上; -- 更不重算一套,那会造出与源系统不一致的第二口径。 -- 恒等式 lack_qty + use_qty = need_count:实测 aidopdev 1019060/1019060 行全部成立, -- 最大绝对偏差 0.0000,三列均无 NULL ⇒ 该恒等式由源系统保证,可被下游直接依赖。 -- ===================================================================================== SET @tbl := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail'); -- ── B1 · bom_level ─────────────────────────────────────────────────────────────────── SET @sql := IF( @tbl = 1 AND (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail' AND COLUMN_NAME = 'bom_level') = 0, 'ALTER TABLE `dwd_requirement_examine_detail` ADD COLUMN `bom_level` INT NULL COMMENT ''BOM 层级 ← raw_data.$.level;1=成品根,>1=投入料''', 'DO 0'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ── B1 · material_role ─────────────────────────────────────────────────────────────── -- 仓库约定:流水线枚举一律 UPPER_SNAKE VARCHAR(对齐 denominator_status 等),中文只用于业务展示解码。 SET @sql := IF( @tbl = 1 AND (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail' AND COLUMN_NAME = 'material_role') = 0, 'ALTER TABLE `dwd_requirement_examine_detail` ADD COLUMN `material_role` VARCHAR(32) NULL COMMENT ''物料角色枚举:FINISHED_GOOD_ROOT / INPUT_MATERIAL''', 'DO 0'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ── B1 · is_current_flag ───────────────────────────────────────────────────────────── -- 仓库约定:布尔一律 `_flag TINYINT NOT NULL DEFAULT 0`(不用 is_* 裸名、不用 bit)。 -- ⚠️ DEFAULT 0 同时承担 B7 的「失效」职责:本 ALTER 一落地,全部存量前修行立刻变成非当前, -- 不存在「旧批次仍被当作 current」的窗口。失效是 DDL 原子完成的,不依赖后面的 DELETE 是否跑完。 SET @sql := IF( @tbl = 1 AND (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail' AND COLUMN_NAME = 'is_current_flag') = 0, 'ALTER TABLE `dwd_requirement_examine_detail` ADD COLUMN `is_current_flag` TINYINT NOT NULL DEFAULT 0 COMMENT ''1=属于当前快照批次;同一 (tenant, factory) 作用域内恒只有一个批次为 1''', 'DO 0'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ── B1 · 支撑 (tenant_id, is_current_flag, morder_no) 的索引 ───────────────────────── -- 典型读法:WHERE tenant_id=? AND is_current_flag=1 AND morder_no=?(取某工单的当前缺料)。 SET @sql := IF( @tbl = 1 AND (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail' AND INDEX_NAME = 'idx_req_exam_current') = 0, 'ALTER TABLE `dwd_requirement_examine_detail` ADD KEY `idx_req_exam_current` (`tenant_id`,`is_current_flag`,`morder_no`)', 'DO 0'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ── B3 · 把「标准列」契约写进 schema,让任何 SHOW CREATE TABLE 的人都能看到 ─────────── SET @sql := IF( @tbl = 1 AND (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail' AND COLUMN_NAME = 'need_count' AND COLUMN_COMMENT LIKE '标准列%') = 0, 'ALTER TABLE `dwd_requirement_examine_detail` MODIFY COLUMN `need_count` DECIMAL(18,4) NULL COMMENT ''标准列·需求量(齐套恒等式:lack_qty + use_qty = need_count)'', MODIFY COLUMN `use_qty` DECIMAL(18,4) NULL COMMENT ''标准列·已满足量(不要新增 satisfied_qty 别名)'', MODIFY COLUMN `lack_qty` DECIMAL(18,4) NULL COMMENT ''标准列·缺口量(不要新增 shortage_qty 别名)''', 'DO 0'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ===================================================================================== -- B7 · 清理前修数据 -- -- 【为什么必须清,而不是留着】 -- 前修行是在缺少两道过滤的情况下生成的,成规模地错: -- ① 表头软删未过滤:贴源 b_examine_result 5969 行里 5851 行 IsDeleted 为真(仅 118 行存活) -- ⇒ 旧 DWD 约 98% 建立在已作废的核验单上,把作废单的缺料当成现行缺料。 -- ② FULL 当成了追加:贴源 b_bom_child_examine 共 32105 行,最新全量批次只有 15272 行, -- 其余是源侧已删除的孤儿,却仍被算进 DWD。 -- 叠加 uk(tenant_id,row_id,calc_batch_id) 的追加式累积,最终 1019060 行 / 3439 个批次全部不可信。 -- -- 【为什么不回填而是删除】 -- material_role 对前修行没有可信来源: -- · DWD 自身从未物化过 level,回填只能反查贴源; -- · 而贴源 mdp_stg_so 的唯一键 uk_source_key=(source_system, source_table, source_biz_key) 不含批次, -- 每轮 FULL 会就地覆盖同一 biz_key 的 raw_data ⇒ 当年生成某个历史批次时所用的 raw_data 已经不存在了。 -- · 强行用「今天的 level」去回填「历史批次」,等于给历史批次编造一个它当时并不具备的属性 —— 是伪造,不是修复。 -- 且本表当前读者数为 0,删除不影响任何消费方;数据完全派生自贴源层(本脚本不动贴源), -- 下一轮 S1 跑批即可重建 ⇒ 删除在意图上是可逆的。 -- -- 【谓词为什么是 material_role IS NULL】 -- 修复后的投影对 material_role 恒不产生 NULL(level 脏值/缺失一律落 INPUT_MATERIAL), -- ⇒ material_role IS NULL 精确等价于「前修行」。该谓词幂等、可重跑, -- 且若在 ALTER 与本 DELETE 之间恰好跑过一轮新管线,新产生的正确行不会被误删。 -- ===================================================================================== SET @sql := IF( @tbl = 1 AND (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dwd_requirement_examine_detail' AND COLUMN_NAME = 'material_role') = 1, 'DELETE FROM `dwd_requirement_examine_detail` WHERE `material_role` IS NULL', 'DO 0'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ── 回滚(注释,非可执行)── -- ALTER TABLE `dwd_requirement_examine_detail` -- DROP KEY `idx_req_exam_current`, -- DROP COLUMN `is_current_flag`, DROP COLUMN `material_role`, DROP COLUMN `bom_level`; -- 被删除的前修行不做回滚:其内容本身不可信,重建方式是重跑 S1 跑批。