✨ 司机档案 ID 存量回填脚本与文本护栏测试
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
-- ============================================================
|
||||
-- blade_loading_manage_driver_id_backfill_20261028.sql
|
||||
-- ============================================================
|
||||
-- 存量配载单补司机档案 ID:把「归属」从手机号快照切到 blade_transport_driver.id
|
||||
-- 写业务数据。第 0 节建存档表,第 1 节分批回填,第 2 节校验(只读,执行后必看),第 3 节回退。可重复执行。
|
||||
--
|
||||
-- 为什么要订正:
|
||||
-- 票 01 之后配载单的归属键是 driver_id(blade_transport_driver.id),driver_phone 只是配载时的号码快照。
|
||||
-- 存量配载单全部只有号码、没有 driver_id(票 01 才加的列),司机端按档案 ID 查配载单会一张都查不到;
|
||||
-- 按号码反查又会踩二次放号与改号,所以才需要先按「唯一命中」把能确定的补齐。
|
||||
-- 本脚本只处理「号码在本租户内唯一命中一条未删除档案」的配载单,其余留在原样、进清单等人工处置。
|
||||
--
|
||||
-- 判定与边界(严格按票面口径,不扩大):
|
||||
-- ① 只动 driver_id IS NULL 的行。driver_id = -1 是「无主」哨兵,正数表示已有归属,两者一行都不动;
|
||||
-- 第 1 节的 WHERE 里另加了一道显式排除((driver_id = -1) IS NOT TRUE),
|
||||
-- 防止后人把条件放宽成 driver_id <= 0 时把哨兵一起覆盖。
|
||||
-- ② 唯一命中:同租户内、is_deleted = 0、mobile 与 driver_phone 相等的档案恰好 1 条。
|
||||
-- 命中 0 条 → 进「无法匹配」清单;命中 ≥2 条 → 进「冲突」清单。两类都跳过,不猜。
|
||||
-- 逻辑删除的档案不参与命中:档案删了以后重跑脚本,不能把这些单挂到废弃档案上。
|
||||
-- ③ 手机号为空、空白、含 * 的跳过(* 是脱敏占位,不是真号码)。
|
||||
-- ④ driver_phone、update_time 与其它任何列都不改,只写 driver_id。
|
||||
-- ⑤ 逻辑删除的配载单同样回填:票面口径只有 driver_id IS NULL,不额外收窄范围;
|
||||
-- 这些单不会回到列表,回填对它们无副作用。
|
||||
-- ⑥ 跨表比较显式 COLLATE:blade_loading_manage.driver_phone / tenant_id 是 utf8mb4_0900_ai_ci,
|
||||
-- blade_transport_driver.mobile / tenant_id 是 utf8mb4_general_ci;
|
||||
-- 不显式 COLLATE 会抛 ERROR 1267 Illegal mix of collations,只有真跑库才会暴露。
|
||||
-- ⑦ 取数与写入全在 SQL 内完成,不经 JSON / 客户端中转:19 位雪花 ID 过 JSON 会丢精度。
|
||||
-- ⑧ 分批执行(默认每批 500 行),每批独立提交,中断后重跑从剩余 NULL 行继续。
|
||||
--
|
||||
-- 范围说明:与运单脚本同一口径,只做「手机号 → 档案唯一命中」这一条规则;其余留给人工处置。
|
||||
-- 幂等:命中映射先落存档表;重复执行时映射新增 0 行、回填更新 0 行,结果不变(第 2.2 节校验)。
|
||||
-- 执行方式(脚本里有 DELIMITER 与存储过程,按分号切句的 GUI 客户端会切错):
|
||||
-- mysql -h127.0.0.1 -uroot -p transport < blade_loading_manage_driver_id_backfill_20261028.sql
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 0. 存档(必须在订正之前执行;重复执行不会覆盖已有存档)
|
||||
-- ------------------------------------------------------------
|
||||
-- 存档表同时是第 1 节的「命中映射」:一行 = 一个待回填的配载单 + 它唯一命中的档案 ID。
|
||||
-- 回填前先把映射定下来:回填只按映射写,跑之前就能看出「打算填谁」,回退也按它还原。
|
||||
CREATE TABLE IF NOT EXISTS blade_loading_manage_driver_id_bak_20261028 (
|
||||
loading_id bigint NOT NULL COMMENT '配载单ID',
|
||||
driver_id_before bigint DEFAULT NULL COMMENT '回填前的司机ID(本脚本只处理 NULL)',
|
||||
driver_id_after bigint DEFAULT NULL COMMENT '回填写入的司机档案ID(blade_transport_driver.id)',
|
||||
driver_phone varchar(50) COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '回填前的手机号快照,用来证明没有被改写',
|
||||
update_time_before datetime DEFAULT NULL COMMENT '回填前的更新时间,用来证明没有被改写',
|
||||
PRIMARY KEY (loading_id),
|
||||
KEY idx_driver_id_after (driver_id_after)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='20261028 配载单司机档案ID回填存档';
|
||||
|
||||
-- 命中映射:只收「号码唯一命中一条未删除档案」的 NULL 行。
|
||||
-- INSERT IGNORE + WHERE driver_id IS NULL:第二次执行不会重复建映射,已经填过的行不会再进映射。
|
||||
INSERT IGNORE INTO blade_loading_manage_driver_id_bak_20261028 (loading_id, driver_id_before, driver_id_after, driver_phone, update_time_before)
|
||||
SELECT matched.biz_id, NULL, matched.driver_id, matched.driver_phone, matched.update_time
|
||||
FROM (
|
||||
SELECT w.id AS biz_id,
|
||||
MIN(d.id) AS driver_id,
|
||||
w.driver_phone AS driver_phone,
|
||||
w.update_time AS update_time
|
||||
FROM blade_loading_manage w
|
||||
JOIN blade_transport_driver d
|
||||
ON d.tenant_id COLLATE utf8mb4_0900_ai_ci = w.tenant_id
|
||||
AND d.mobile COLLATE utf8mb4_0900_ai_ci = w.driver_phone
|
||||
AND d.is_deleted = 0
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND w.driver_phone IS NOT NULL
|
||||
AND TRIM(w.driver_phone) <> ''
|
||||
AND w.driver_phone NOT LIKE '%*%'
|
||||
GROUP BY w.id, w.driver_phone, w.update_time
|
||||
HAVING COUNT(*) = 1
|
||||
) matched;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 1. 订正(分批回填,默认每批 500 行)
|
||||
-- ------------------------------------------------------------
|
||||
-- 循环退出条件是「这一批没有更新任何行」,所以重复执行时第一批就是 0 行,脚本空转结束。
|
||||
-- 每批单独提交(不开长事务):大表上不会长时间持锁,中断后重跑从剩余 NULL 行继续。
|
||||
DELIMITER $$
|
||||
DROP PROCEDURE IF EXISTS backfill_blade_loading_manage_driver_id_20261028$$
|
||||
CREATE PROCEDURE backfill_blade_loading_manage_driver_id_20261028(IN p_batch_size INT)
|
||||
BEGIN
|
||||
DECLARE v_batch_size INT DEFAULT 500;
|
||||
DECLARE v_affected INT DEFAULT 0;
|
||||
|
||||
IF p_batch_size IS NOT NULL AND p_batch_size > 0 THEN
|
||||
SET v_batch_size = p_batch_size;
|
||||
END IF;
|
||||
|
||||
CREATE TEMPORARY TABLE IF NOT EXISTS tmp_loading_manage_driver_id_20261028 (
|
||||
loading_id bigint NOT NULL,
|
||||
PRIMARY KEY (loading_id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
REPEAT
|
||||
-- 取批:只取还空着的行,填过的行不再入批,所以每一批都在推进,不会原地打转
|
||||
DELETE FROM tmp_loading_manage_driver_id_20261028;
|
||||
|
||||
INSERT INTO tmp_loading_manage_driver_id_20261028 (loading_id)
|
||||
SELECT b.loading_id
|
||||
FROM blade_loading_manage_driver_id_bak_20261028 b
|
||||
JOIN blade_loading_manage w ON w.id = b.loading_id
|
||||
WHERE w.driver_id IS NULL
|
||||
ORDER BY b.loading_id
|
||||
LIMIT v_batch_size;
|
||||
|
||||
UPDATE blade_loading_manage w
|
||||
JOIN blade_loading_manage_driver_id_bak_20261028 b ON b.loading_id = w.id
|
||||
JOIN tmp_loading_manage_driver_id_20261028 t ON t.loading_id = w.id
|
||||
SET w.driver_id = b.driver_id_after
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND b.driver_id_after IS NOT NULL;
|
||||
|
||||
SET v_affected = ROW_COUNT();
|
||||
UNTIL v_affected = 0 END REPEAT;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_loading_manage_driver_id_20261028;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL backfill_blade_loading_manage_driver_id_20261028(500);
|
||||
DROP PROCEDURE IF EXISTS backfill_blade_loading_manage_driver_id_20261028;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 2. 校验(执行后必看;本节只有 SELECT,不动数据)
|
||||
-- ------------------------------------------------------------
|
||||
-- 2.1 规模:存档多少行、还剩多少 NULL 行、正数归属多少行(跑前跑后各查一次)
|
||||
-- remaining_null_rows 只会减少「本次命中」的那部分;差额应当等于第 2.7 节的命中行数。
|
||||
SELECT COUNT(*) AS archived_rows FROM blade_loading_manage_driver_id_bak_20261028;
|
||||
SELECT COUNT(*) AS remaining_null_rows FROM blade_loading_manage WHERE driver_id IS NULL;
|
||||
SELECT SUM(driver_id > 0) AS owned_rows,
|
||||
SUM(driver_id = -1) AS sentinel_rows,
|
||||
SUM(driver_id IS NULL) AS null_rows
|
||||
FROM blade_loading_manage;
|
||||
|
||||
-- 2.2 哨兵不变:跑前跑后各执行一次,四列必须完全相同(-1 一行都不能动)
|
||||
SELECT COUNT(*) AS sentinel_rows,
|
||||
SUM(id) AS sentinel_id_sum,
|
||||
MIN(id) AS min_id,
|
||||
MAX(id) AS max_id
|
||||
FROM blade_loading_manage
|
||||
WHERE driver_id = -1;
|
||||
|
||||
-- 2.3 写入正确:本次映射的行,driver_id 必须等于映射值(期望 0 行)
|
||||
SELECT COUNT(*) AS wrong_driver_id
|
||||
FROM blade_loading_manage_driver_id_bak_20261028 b
|
||||
JOIN blade_loading_manage w ON w.id = b.loading_id
|
||||
WHERE NOT (w.driver_id <=> b.driver_id_after);
|
||||
|
||||
-- 2.4 快照没有被改写:本次映射的行,driver_phone 与 update_time 必须与存档逐行一致(期望 0 行)
|
||||
SELECT COUNT(*) AS snapshot_rewritten
|
||||
FROM blade_loading_manage_driver_id_bak_20261028 b
|
||||
JOIN blade_loading_manage w ON w.id = b.loading_id
|
||||
WHERE NOT (w.driver_phone <=> b.driver_phone)
|
||||
OR NOT (w.update_time <=> b.update_time_before);
|
||||
|
||||
-- 2.5 冲突清单:同号多档案,这些行被跳过(期望 0 行;有输出就是「为什么没填」的答案)
|
||||
SELECT w.driver_phone AS conflicting_phone,
|
||||
COUNT(DISTINCT w.id) AS pending_rows,
|
||||
COUNT(DISTINCT d.id) AS driver_profiles,
|
||||
GROUP_CONCAT(DISTINCT d.id ORDER BY d.id) AS driver_profile_ids
|
||||
FROM blade_loading_manage w
|
||||
JOIN blade_transport_driver d
|
||||
ON d.tenant_id COLLATE utf8mb4_0900_ai_ci = w.tenant_id
|
||||
AND d.mobile COLLATE utf8mb4_0900_ai_ci = w.driver_phone
|
||||
AND d.is_deleted = 0
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND w.driver_phone IS NOT NULL
|
||||
AND TRIM(w.driver_phone) <> ''
|
||||
AND w.driver_phone NOT LIKE '%*%'
|
||||
GROUP BY w.driver_phone
|
||||
HAVING COUNT(DISTINCT d.id) > 1;
|
||||
|
||||
-- 2.6 无法匹配清单:号码在本租户内命中 0 条档案,这些行按规则跳过(等人工处置,脚本不猜)
|
||||
SELECT w.driver_phone AS unmatched_phone,
|
||||
COUNT(*) AS pending_rows
|
||||
FROM blade_loading_manage w
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND w.driver_phone IS NOT NULL
|
||||
AND TRIM(w.driver_phone) <> ''
|
||||
AND w.driver_phone NOT LIKE '%*%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM blade_transport_driver d
|
||||
WHERE d.tenant_id COLLATE utf8mb4_0900_ai_ci = w.tenant_id
|
||||
AND d.mobile COLLATE utf8mb4_0900_ai_ci = w.driver_phone
|
||||
AND d.is_deleted = 0)
|
||||
GROUP BY w.driver_phone
|
||||
ORDER BY pending_rows DESC, unmatched_phone;
|
||||
|
||||
-- 2.7 逐司机对照:按手机号可见单量 vs 按档案 ID 可见单量
|
||||
-- by_phone = 号码快照等于该档案号码的单量(旧口径,改号/二次放号会串)
|
||||
-- by_driver_id = 档案 ID 等于该档案的单量(新口径)
|
||||
-- phone_only_null = 号码对上、但归属还空着的单(本次应被回填的那部分)
|
||||
-- phone_only_sentinel = 号码对上、但归属被标成 -1 无主的单(回填不碰,需要人工确认)
|
||||
-- phone_only_other_owner = 号码对上、却已归属另一条档案的单(改号/换司机后号码被复用)
|
||||
-- id_only_phone_differs = 已归属该档案、但号码快照不是该档案当前号码的单(改号后的历史快照)
|
||||
SELECT d.id AS driver_id,
|
||||
d.driver_name,
|
||||
d.mobile,
|
||||
(SELECT COUNT(*) FROM blade_loading_manage w WHERE w.driver_id = d.id) AS by_driver_id,
|
||||
(SELECT COUNT(*) FROM blade_loading_manage w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile) AS by_phone,
|
||||
(SELECT COUNT(*) FROM blade_loading_manage w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile
|
||||
AND w.driver_id IS NULL) AS phone_only_null,
|
||||
(SELECT COUNT(*) FROM blade_loading_manage w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile
|
||||
AND w.driver_id = -1) AS phone_only_sentinel,
|
||||
(SELECT COUNT(*) FROM blade_loading_manage w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile
|
||||
AND w.driver_id > 0
|
||||
AND w.driver_id <> d.id) AS phone_only_other_owner,
|
||||
(SELECT COUNT(*) FROM blade_loading_manage w
|
||||
WHERE w.driver_id = d.id
|
||||
AND (w.driver_phone IS NULL
|
||||
OR NOT (w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile))) AS id_only_phone_differs
|
||||
FROM blade_transport_driver d
|
||||
WHERE d.is_deleted = 0
|
||||
ORDER BY by_phone DESC, by_driver_id DESC, driver_id;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 3. 回退(仅在需要撤销本次回填时手工执行)
|
||||
-- ------------------------------------------------------------
|
||||
-- 按存档表把本次写入的行还原成 NULL。只还原「当前值仍等于本次写入值」的行:
|
||||
-- 回填之后又被人改派过的单不会被冲掉。
|
||||
-- UPDATE blade_loading_manage w
|
||||
-- JOIN blade_loading_manage_driver_id_bak_20261028 b ON b.loading_id = w.id
|
||||
-- SET w.driver_id = b.driver_id_before
|
||||
-- WHERE w.driver_id <=> b.driver_id_after;
|
||||
-- 回退只碰 driver_id,不碰 driver_phone / update_time。备份表确认不再需要时再删:
|
||||
-- DROP TABLE blade_loading_manage_driver_id_bak_20261028;
|
||||
@@ -0,0 +1,241 @@
|
||||
-- ============================================================
|
||||
-- blade_waybill_driver_id_backfill_20261028.sql
|
||||
-- ============================================================
|
||||
-- 存量运单补司机档案 ID:把「归属」从手机号快照切到 blade_transport_driver.id
|
||||
-- 写业务数据。第 0 节建存档表,第 1 节分批回填,第 2 节校验(只读,执行后必看),第 3 节回退。可重复执行。
|
||||
--
|
||||
-- 为什么要订正:
|
||||
-- 票 01 之后运单的归属键是 driver_id(blade_transport_driver.id),driver_phone 只是派单时的号码快照。
|
||||
-- 存量运单大多只有号码、没有 driver_id,司机端按档案 ID 查单会整批漏掉它们;
|
||||
-- 按号码反查又会踩二次放号与改号(本机库实测:黄宇富档案已改号 15577122721,
|
||||
-- 名下 92 张单仍写着旧号 17587421208,其中 20 张 driver_id 为空、5 张能从接单司机推出来,
|
||||
-- 剩下 15 张号码已经对不上任何档案——本脚本不猜,它们会出现在第 2.6 节清单里)。
|
||||
-- 本脚本只处理「号码在本租户内唯一命中一条未删除档案」的运单,其余留在原样、进清单等人工处置。
|
||||
--
|
||||
-- 判定与边界(严格按票面口径,不扩大):
|
||||
-- ① 只动 driver_id IS NULL 的行。driver_id = -1 是「无主」哨兵,正数表示已有归属,两者一行都不动;
|
||||
-- 第 1 节的 WHERE 里另加了一道显式排除((driver_id = -1) IS NOT TRUE),
|
||||
-- 防止后人把条件放宽成 driver_id <= 0 时把哨兵一起覆盖。
|
||||
-- ② 唯一命中:同租户内、is_deleted = 0、mobile 与 driver_phone 相等的档案恰好 1 条。
|
||||
-- 命中 0 条 → 进「无法匹配」清单;命中 ≥2 条 → 进「冲突」清单。两类都跳过,不猜。
|
||||
-- 逻辑删除的档案不参与命中:档案删了以后重跑脚本,不能把这些单挂到废弃档案上。
|
||||
-- ③ 手机号为空、空白、含 * 的跳过(* 是脱敏占位,不是真号码)。
|
||||
-- ④ driver_phone、update_time 与其它任何列都不改,只写 driver_id。
|
||||
-- ⑤ 逻辑删除的运单同样回填:票面口径只有 driver_id IS NULL,不额外收窄范围;
|
||||
-- 这些单不会回到列表,回填对它们无副作用。
|
||||
-- ⑥ 跨表比较显式 COLLATE:blade_waybill.driver_phone / tenant_id 是 utf8mb4_0900_ai_ci,
|
||||
-- blade_transport_driver.mobile / tenant_id 是 utf8mb4_general_ci;
|
||||
-- 不显式 COLLATE 会抛 ERROR 1267 Illegal mix of collations,只有真跑库才会暴露。
|
||||
-- ⑦ 取数与写入全在 SQL 内完成,不经 JSON / 客户端中转:19 位雪花 ID 过 JSON 会丢精度。
|
||||
-- ⑧ 分批执行(默认每批 500 行),每批独立提交,中断后重跑从剩余 NULL 行继续。
|
||||
--
|
||||
-- 范围说明:本脚本只做「手机号 → 档案唯一命中」这一条规则。
|
||||
-- 不引入 driver_accept_driver_id 推断,也不按姓名/历史号码猜归属:这些属于人工处置,在票面范围外。
|
||||
-- 幂等:命中映射先落存档表;重复执行时映射新增 0 行、回填更新 0 行,结果不变(第 2.2 节校验)。
|
||||
-- 执行方式(脚本里有 DELIMITER 与存储过程,按分号切句的 GUI 客户端会切错):
|
||||
-- mysql -h127.0.0.1 -uroot -p transport < blade_waybill_driver_id_backfill_20261028.sql
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 0. 存档(必须在订正之前执行;重复执行不会覆盖已有存档)
|
||||
-- ------------------------------------------------------------
|
||||
-- 存档表同时是第 1 节的「命中映射」:一行 = 一个待回填的运单 + 它唯一命中的档案 ID。
|
||||
-- 回填前先把映射定下来:回填只按映射写,跑之前就能看出「打算填谁」,回退也按它还原。
|
||||
CREATE TABLE IF NOT EXISTS blade_waybill_driver_id_bak_20261028 (
|
||||
waybill_id bigint NOT NULL COMMENT '运单ID',
|
||||
driver_id_before bigint DEFAULT NULL COMMENT '回填前的司机ID(本脚本只处理 NULL)',
|
||||
driver_id_after bigint DEFAULT NULL COMMENT '回填写入的司机档案ID(blade_transport_driver.id)',
|
||||
driver_phone varchar(50) COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '回填前的手机号快照,用来证明没有被改写',
|
||||
update_time_before datetime DEFAULT NULL COMMENT '回填前的更新时间,用来证明没有被改写',
|
||||
PRIMARY KEY (waybill_id),
|
||||
KEY idx_driver_id_after (driver_id_after)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='20261028 运单司机档案ID回填存档';
|
||||
|
||||
-- 命中映射:只收「号码唯一命中一条未删除档案」的 NULL 行。
|
||||
-- INSERT IGNORE + WHERE driver_id IS NULL:第二次执行不会重复建映射,已经填过的行不会再进映射。
|
||||
INSERT IGNORE INTO blade_waybill_driver_id_bak_20261028 (waybill_id, driver_id_before, driver_id_after, driver_phone, update_time_before)
|
||||
SELECT matched.biz_id, NULL, matched.driver_id, matched.driver_phone, matched.update_time
|
||||
FROM (
|
||||
SELECT w.id AS biz_id,
|
||||
MIN(d.id) AS driver_id,
|
||||
w.driver_phone AS driver_phone,
|
||||
w.update_time AS update_time
|
||||
FROM blade_waybill w
|
||||
JOIN blade_transport_driver d
|
||||
ON d.tenant_id COLLATE utf8mb4_0900_ai_ci = w.tenant_id
|
||||
AND d.mobile COLLATE utf8mb4_0900_ai_ci = w.driver_phone
|
||||
AND d.is_deleted = 0
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND w.driver_phone IS NOT NULL
|
||||
AND TRIM(w.driver_phone) <> ''
|
||||
AND w.driver_phone NOT LIKE '%*%'
|
||||
GROUP BY w.id, w.driver_phone, w.update_time
|
||||
HAVING COUNT(*) = 1
|
||||
) matched;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 1. 订正(分批回填,默认每批 500 行)
|
||||
-- ------------------------------------------------------------
|
||||
-- 循环退出条件是「这一批没有更新任何行」,所以重复执行时第一批就是 0 行,脚本空转结束。
|
||||
-- 每批单独提交(不开长事务):大表上不会长时间持锁,中断后重跑从剩余 NULL 行继续。
|
||||
DELIMITER $$
|
||||
DROP PROCEDURE IF EXISTS backfill_blade_waybill_driver_id_20261028$$
|
||||
CREATE PROCEDURE backfill_blade_waybill_driver_id_20261028(IN p_batch_size INT)
|
||||
BEGIN
|
||||
DECLARE v_batch_size INT DEFAULT 500;
|
||||
DECLARE v_affected INT DEFAULT 0;
|
||||
|
||||
IF p_batch_size IS NOT NULL AND p_batch_size > 0 THEN
|
||||
SET v_batch_size = p_batch_size;
|
||||
END IF;
|
||||
|
||||
CREATE TEMPORARY TABLE IF NOT EXISTS tmp_waybill_driver_id_20261028 (
|
||||
waybill_id bigint NOT NULL,
|
||||
PRIMARY KEY (waybill_id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
REPEAT
|
||||
-- 取批:只取还空着的行,填过的行不再入批,所以每一批都在推进,不会原地打转
|
||||
DELETE FROM tmp_waybill_driver_id_20261028;
|
||||
|
||||
INSERT INTO tmp_waybill_driver_id_20261028 (waybill_id)
|
||||
SELECT b.waybill_id
|
||||
FROM blade_waybill_driver_id_bak_20261028 b
|
||||
JOIN blade_waybill w ON w.id = b.waybill_id
|
||||
WHERE w.driver_id IS NULL
|
||||
ORDER BY b.waybill_id
|
||||
LIMIT v_batch_size;
|
||||
|
||||
UPDATE blade_waybill w
|
||||
JOIN blade_waybill_driver_id_bak_20261028 b ON b.waybill_id = w.id
|
||||
JOIN tmp_waybill_driver_id_20261028 t ON t.waybill_id = w.id
|
||||
SET w.driver_id = b.driver_id_after
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND b.driver_id_after IS NOT NULL;
|
||||
|
||||
SET v_affected = ROW_COUNT();
|
||||
UNTIL v_affected = 0 END REPEAT;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_waybill_driver_id_20261028;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL backfill_blade_waybill_driver_id_20261028(500);
|
||||
DROP PROCEDURE IF EXISTS backfill_blade_waybill_driver_id_20261028;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 2. 校验(执行后必看;本节只有 SELECT,不动数据)
|
||||
-- ------------------------------------------------------------
|
||||
-- 2.1 规模:存档多少行、还剩多少 NULL 行、正数归属多少行(跑前跑后各查一次)
|
||||
-- remaining_null_rows 只会减少「本次命中」的那部分;差额应当等于第 2.7 节的命中行数。
|
||||
SELECT COUNT(*) AS archived_rows FROM blade_waybill_driver_id_bak_20261028;
|
||||
SELECT COUNT(*) AS remaining_null_rows FROM blade_waybill WHERE driver_id IS NULL;
|
||||
SELECT SUM(driver_id > 0) AS owned_rows,
|
||||
SUM(driver_id = -1) AS sentinel_rows,
|
||||
SUM(driver_id IS NULL) AS null_rows
|
||||
FROM blade_waybill;
|
||||
|
||||
-- 2.2 哨兵不变:跑前跑后各执行一次,四列必须完全相同(-1 一行都不能动)
|
||||
SELECT COUNT(*) AS sentinel_rows,
|
||||
SUM(id) AS sentinel_id_sum,
|
||||
MIN(id) AS min_id,
|
||||
MAX(id) AS max_id
|
||||
FROM blade_waybill
|
||||
WHERE driver_id = -1;
|
||||
|
||||
-- 2.3 写入正确:本次映射的行,driver_id 必须等于映射值(期望 0 行)
|
||||
SELECT COUNT(*) AS wrong_driver_id
|
||||
FROM blade_waybill_driver_id_bak_20261028 b
|
||||
JOIN blade_waybill w ON w.id = b.waybill_id
|
||||
WHERE NOT (w.driver_id <=> b.driver_id_after);
|
||||
|
||||
-- 2.4 快照没有被改写:本次映射的行,driver_phone 与 update_time 必须与存档逐行一致(期望 0 行)
|
||||
SELECT COUNT(*) AS snapshot_rewritten
|
||||
FROM blade_waybill_driver_id_bak_20261028 b
|
||||
JOIN blade_waybill w ON w.id = b.waybill_id
|
||||
WHERE NOT (w.driver_phone <=> b.driver_phone)
|
||||
OR NOT (w.update_time <=> b.update_time_before);
|
||||
|
||||
-- 2.5 冲突清单:同号多档案,这些行被跳过(期望 0 行;有输出就是「为什么没填」的答案)
|
||||
SELECT w.driver_phone AS conflicting_phone,
|
||||
COUNT(DISTINCT w.id) AS pending_rows,
|
||||
COUNT(DISTINCT d.id) AS driver_profiles,
|
||||
GROUP_CONCAT(DISTINCT d.id ORDER BY d.id) AS driver_profile_ids
|
||||
FROM blade_waybill w
|
||||
JOIN blade_transport_driver d
|
||||
ON d.tenant_id COLLATE utf8mb4_0900_ai_ci = w.tenant_id
|
||||
AND d.mobile COLLATE utf8mb4_0900_ai_ci = w.driver_phone
|
||||
AND d.is_deleted = 0
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND w.driver_phone IS NOT NULL
|
||||
AND TRIM(w.driver_phone) <> ''
|
||||
AND w.driver_phone NOT LIKE '%*%'
|
||||
GROUP BY w.driver_phone
|
||||
HAVING COUNT(DISTINCT d.id) > 1;
|
||||
|
||||
-- 2.6 无法匹配清单:号码在本租户内命中 0 条档案,这些行按规则跳过(等人工处置,脚本不猜)
|
||||
SELECT w.driver_phone AS unmatched_phone,
|
||||
COUNT(*) AS pending_rows
|
||||
FROM blade_waybill w
|
||||
WHERE w.driver_id IS NULL
|
||||
AND (w.driver_id = -1) IS NOT TRUE
|
||||
AND w.driver_phone IS NOT NULL
|
||||
AND TRIM(w.driver_phone) <> ''
|
||||
AND w.driver_phone NOT LIKE '%*%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM blade_transport_driver d
|
||||
WHERE d.tenant_id COLLATE utf8mb4_0900_ai_ci = w.tenant_id
|
||||
AND d.mobile COLLATE utf8mb4_0900_ai_ci = w.driver_phone
|
||||
AND d.is_deleted = 0)
|
||||
GROUP BY w.driver_phone
|
||||
ORDER BY pending_rows DESC, unmatched_phone;
|
||||
|
||||
-- 2.7 逐司机对照:按手机号可见单量 vs 按档案 ID 可见单量
|
||||
-- by_phone = 号码快照等于该档案号码的单量(旧口径,改号/二次放号会串)
|
||||
-- by_driver_id = 档案 ID 等于该档案的单量(新口径)
|
||||
-- phone_only_null = 号码对上、但归属还空着的单(本次应被回填的那部分)
|
||||
-- phone_only_sentinel = 号码对上、但归属被标成 -1 无主的单(回填不碰,需要人工确认)
|
||||
-- phone_only_other_owner = 号码对上、却已归属另一条档案的单(改号/换司机后号码被复用)
|
||||
-- id_only_phone_differs = 已归属该档案、但号码快照不是该档案当前号码的单(改号后的历史快照)
|
||||
SELECT d.id AS driver_id,
|
||||
d.driver_name,
|
||||
d.mobile,
|
||||
(SELECT COUNT(*) FROM blade_waybill w WHERE w.driver_id = d.id) AS by_driver_id,
|
||||
(SELECT COUNT(*) FROM blade_waybill w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile) AS by_phone,
|
||||
(SELECT COUNT(*) FROM blade_waybill w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile
|
||||
AND w.driver_id IS NULL) AS phone_only_null,
|
||||
(SELECT COUNT(*) FROM blade_waybill w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile
|
||||
AND w.driver_id = -1) AS phone_only_sentinel,
|
||||
(SELECT COUNT(*) FROM blade_waybill w
|
||||
WHERE w.tenant_id COLLATE utf8mb4_0900_ai_ci = d.tenant_id
|
||||
AND w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile
|
||||
AND w.driver_id > 0
|
||||
AND w.driver_id <> d.id) AS phone_only_other_owner,
|
||||
(SELECT COUNT(*) FROM blade_waybill w
|
||||
WHERE w.driver_id = d.id
|
||||
AND (w.driver_phone IS NULL
|
||||
OR NOT (w.driver_phone COLLATE utf8mb4_0900_ai_ci = d.mobile))) AS id_only_phone_differs
|
||||
FROM blade_transport_driver d
|
||||
WHERE d.is_deleted = 0
|
||||
ORDER BY by_phone DESC, by_driver_id DESC, driver_id;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 3. 回退(仅在需要撤销本次回填时手工执行)
|
||||
-- ------------------------------------------------------------
|
||||
-- 按存档表把本次写入的行还原成 NULL。只还原「当前值仍等于本次写入值」的行:
|
||||
-- 回填之后又被人改派过的单不会被冲掉。
|
||||
-- UPDATE blade_waybill w
|
||||
-- JOIN blade_waybill_driver_id_bak_20261028 b ON b.waybill_id = w.id
|
||||
-- SET w.driver_id = b.driver_id_before
|
||||
-- WHERE w.driver_id <=> b.driver_id_after;
|
||||
-- 回退只碰 driver_id,不碰 driver_phone / update_time。备份表确认不再需要时再删:
|
||||
-- DROP TABLE blade_waybill_driver_id_bak_20261028;
|
||||
Reference in New Issue
Block a user