| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- -- =====================================================================================
- -- 1.0.539 · S8 Stage-3 采购订单身份桥(Order Identity Propagation → MDP)
- --
- -- ── 背景 ────────────────────────────────────────────────────────────────────────────
- -- 采购的订单归因链在【源侧】已实证成立,但走的不是 PurOrdDetail.WorkOrd,而是 Req:
- -- PurOrdDetail.Req → srm_pr_main.pr_billno → srm_pr_main.pr_mono
- -- → WorkOrdMaster.WorkOrd → WorkOrdMaster.BusinessID (= crm_seorderentry.Id)
- -- → crm_seorderentry.seorder_id → crm_seorder.Id
- -- 实测:PurOrdDetail.Req 207/207 命中 pr_billno;89 条 PO 行归属 13 张真实销售订单。
- --
- -- 但标准层缺两段投影,导致 S8 无法在「只读中台」前提下完成同样归因:
- -- · mdp_std_purchase_request 没有 pr_mono / 订单行 Id
- -- · mdp_std_purchase_order 没有 Req
- --
- -- 本脚本补这两段,使下面这条链可以【全程只读 mdp_*】跑通:
- -- mdp_std_purchase_receipt(ord_nbr, ord_line)
- -- → mdp_std_purchase_order(po_no, po_line).purchase_request_no
- -- → mdp_std_purchase_request(pr_no).sales_order_entry_id
- -- → mdp_std_so(order_entry_id) → order_id
- --
- -- ── 为什么不回填源表 srm_pr_main.sentry_id ──────────────────────────────────────────
- -- 该字段 schema 存在但源侧 1012/1012 全为 NULL(贴源层亦 0 命中),说明生产代码从未写它。
- -- 本批改为在标准层用 pr_mono → mdp_std_work_order_schedule.sales_order_entry_id 派生,
- -- 因此【不需要写任何业务源表】。源侧补写 sentry_id 属独立的数据治理批次。
- --
- -- ── 本脚本不做 ──────────────────────────────────────────────────────────────────────
- -- 不建 dwd_procurement_kpi;不加 S3_PROCUREMENT_COMPLETE;不写采购完成时间;
- -- 不修 239 个 BusinessID=0 的历史/UAT 导入工单(登记为 DATA_QUALITY_KNOWN)。
- -- =====================================================================================
- -- ---------------------------------------------------------------------------
- -- ① mdp_std_purchase_request 补订单身份
- -- work_order = srm_pr_main.pr_mono(PR 所属工单号)
- -- sales_order_entry_id = 经 mdp_std_work_order_schedule 派生的订单行 Id
- -- ⚠️ 归因 Grain 必须保持 PR 行级:同一物料可跨多工单共享采购(实测 152 个物料跨工单),
- -- 但一条 PR 恒属单一工单(实测 pr_multi_wo=0),因此行级归因不会把共享采购
- -- 错并给某一张订单。
- -- ---------------------------------------------------------------------------
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`COLUMNS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
- AND `COLUMN_NAME` = 'work_order'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_request` ADD COLUMN `work_order` VARCHAR(64) NULL COMMENT ''PR 所属工单号(源 srm_pr_main.pr_mono);字面量 null 已归一为 NULL'' AFTER `pr_line`'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`COLUMNS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
- AND `COLUMN_NAME` = 'sales_order_entry_id'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_request` ADD COLUMN `sales_order_entry_id` BIGINT NULL COMMENT ''销售订单明细行Id = crm_seorderentry.Id;经 work_order 从 mdp_std_work_order_schedule 派生,未知时为 NULL'' AFTER `work_order`'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`STATISTICS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
- AND `INDEX_NAME` = 'idx_std_pr_entry'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_request` ADD INDEX `idx_std_pr_entry` (`tenant_id`, `sales_order_entry_id`)'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`STATISTICS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
- AND `INDEX_NAME` = 'idx_std_pr_wo'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_request` ADD INDEX `idx_std_pr_wo` (`tenant_id`, `work_order`)'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- -- ---------------------------------------------------------------------------
- -- ② mdp_std_purchase_order 补 PR 指针
- -- purchase_request_no = PurOrdDetail.Req(PO 行 → PR 的 1:1 Authority)
- -- ⚠️ 这才是 Stage-3 的主归因列。既有 work_order 列【不可】作主归因:
- -- 它只在历史/UAT 批量导入的工单上有值,且 451 行中 208 行是字面量 'null'。
- -- ---------------------------------------------------------------------------
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`COLUMNS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_order'
- AND `COLUMN_NAME` = 'purchase_request_no'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_order` ADD COLUMN `purchase_request_no` VARCHAR(64) NULL COMMENT ''采购申请单号(源 PurOrdDetail.Req)= mdp_std_purchase_request.pr_no;Stage-3 订单归因主键路径'' AFTER `po_line`'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`STATISTICS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_order'
- AND `INDEX_NAME` = 'idx_std_po_req'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_order` ADD INDEX `idx_std_po_req` (`tenant_id`, `purchase_request_no`)'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- -- ---------------------------------------------------------------------------
- -- ③ mdp_std_purchase_receipt 补桥索引
- -- 收货 → PO 的键已存在(ord_nbr / ord_line,源 PurOrdRctDetail.OrdNbr/OrdLine,
- -- 源侧实测 245/245 有值、240 命中 PurOrdDetail(PurOrd,Line)),只缺索引。
- -- 收货侧 raw_data 无 Req,故收货必须经 PO 才能到 PR,不可直连。
- -- ---------------------------------------------------------------------------
- SET @ddl := (SELECT IF(EXISTS(
- SELECT 1 FROM `information_schema`.`STATISTICS`
- WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_receipt'
- AND `INDEX_NAME` = 'idx_std_rct_po'),
- 'SELECT 1',
- 'ALTER TABLE `mdp_std_purchase_receipt` ADD INDEX `idx_std_rct_po` (`tenant_id`, `ord_nbr`, `ord_line`)'));
- PREPARE s FROM @ddl; EXECUTE s; DEALLOCATE PREPARE s;
- -- ---------------------------------------------------------------------------
- -- ④ 清掉标准层的 tenant_id=0 遗留行(租户隔离缺陷)
- -- 实测:mdp_std_purchase_request 224 行、mdp_std_purchase_order 2 行 tenant_id=0,
- -- 全部来自单一历史批次 S3_MDP_FULL_20260810023343(2026-08-10,批次号无租户段,
- -- 属租户化改造前的旧格式)。贴源层 mdp_stg_supply_demand 侧 tenant_id=0 为 0 行,
- -- 故本次清理后正常跑批不会重新产生。
- -- 新的 Stage-3 身份桥绝不能建立在 tenant=0 的数据上 —— 无法确定租户的行一律删除,
- -- 不猜测、不补默认租户。同类先例:1.0.355 / 1.0.356 对 mdp_std_so 的处理。
- -- ---------------------------------------------------------------------------
- DELETE FROM `mdp_std_purchase_request` WHERE `tenant_id` IS NULL OR `tenant_id` <= 0;
- DELETE FROM `mdp_std_purchase_order` WHERE `tenant_id` IS NULL OR `tenant_id` <= 0;
- DELETE FROM `mdp_std_purchase_receipt` WHERE `tenant_id` IS NULL OR `tenant_id` <= 0;
- -- ---------------------------------------------------------------------------
- -- ⑤ 归一既有 work_order 的字面量 'null'
- -- 实测 451 行里 208 行存的是 4 字符字符串 "null"(JSON_UNQUOTE 对 JSON null
- -- 的既有行为)。留着它,任何按 work_order 的 JOIN 都会去匹配一个叫 "null"
- -- 的工单号并互相串起来。本列改造后只作参考、不作归因,但仍必须归一。
- -- ---------------------------------------------------------------------------
- UPDATE `mdp_std_purchase_order` SET `work_order` = NULL WHERE `work_order` IN ('null', '');
- -- ---------------------------------------------------------------------------
- -- ⑥ 一次性身份回填(全程只读 mdp_stg_* / mdp_std_*,不读任何源业务表)
- --
- -- 为什么要在迁移里回填,而不是等下一次跑批:
- -- · PR 侧转换不按批次收窄,下次跑批会自然覆盖全量;
- -- · 但 PO 侧转换按 `sync_batch_id=@BatchId` 收窄 + 每轮淘汰,
- -- 新列要等该租户下一次真实拉取才会被填上。
- -- 两者节奏不一致会让身份链在「已建列但半空」的状态里停留不确定的时间。
- -- 这里用贴源层已经落地的数据把三列一次性补齐,使身份链在迁移完成那一刻
- -- 即可被消费,且与转换逻辑同口径(同样的 JSON 路径、同样的 'null' 归一)。
- --
- -- 回填只写 mdp 标准层的三个身份列,不写任何业务字段、不造任何业务事实,
- -- 不涉及采购完成时间 / Stage-3 事件 / ActualHours。
- --
- -- 去重取 MAX:贴源层同时存在两代 source_biz_key(旧 `Domain:PurOrd:Line` 与
- -- 新的裸 RecID),同一逻辑行可能有多条贴源记录。按 (租户, source_biz_key)
- -- 聚合可保证结果确定,不依赖 MySQL 任取一行。
- -- ---------------------------------------------------------------------------
- -- ⑥-1 PR → 工单号(源 srm_pr_main.pr_mono)
- UPDATE `mdp_std_purchase_request` p
- JOIN ( SELECT g.`tenant_id`, g.`source_biz_key`,
- MAX(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(g.`raw_data`, '$.pr_mono')), 'null'), '')) AS `wo`
- FROM `mdp_stg_supply_demand` g
- WHERE g.`source_table` = 'srm_pr_main' AND g.`tenant_id` > 0
- GROUP BY g.`tenant_id`, g.`source_biz_key` ) s
- ON s.`tenant_id` = p.`tenant_id` AND s.`source_biz_key` = p.`source_biz_key`
- SET p.`work_order` = s.`wo`
- WHERE p.`tenant_id` > 0;
- -- ⑥-2 PR → 销售订单明细行 Id(经 1.0.532 建立的 S2 工单桥派生)
- -- 同租户同工单号在 mdp_std_work_order_schedule 内唯一(实测重复组 = 0),
- -- 故这里不会扇出、不需要再取一行。
- -- 未被 S2 跑批覆盖的工单保持 NULL —— 宁可为空,不猜订单。
- UPDATE `mdp_std_purchase_request` p
- JOIN `mdp_std_work_order_schedule` w
- ON w.`tenant_id` = p.`tenant_id` AND w.`work_order` = p.`work_order`
- SET p.`sales_order_entry_id` = w.`sales_order_entry_id`
- WHERE p.`tenant_id` > 0
- AND p.`work_order` IS NOT NULL
- AND w.`sales_order_entry_id` IS NOT NULL;
- -- ⑥-3 PO → 采购申请单号(源 PurOrdDetail.Req)
- UPDATE `mdp_std_purchase_order` o
- JOIN ( SELECT g.`tenant_id`, g.`source_biz_key`,
- MAX(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(g.`raw_data`, '$.Req')), 'null'), '')) AS `req`
- FROM `mdp_stg_purchase_order` g
- WHERE g.`source_table` = 'PurOrdDetail' AND g.`tenant_id` > 0
- GROUP BY g.`tenant_id`, g.`source_biz_key` ) s
- ON s.`tenant_id` = o.`tenant_id` AND s.`source_biz_key` = o.`source_biz_key`
- SET o.`purchase_request_no` = s.`req`
- WHERE o.`tenant_id` > 0;
|