diff --git a/doc/sql/transport/blade_transport_driver.sql b/doc/sql/transport/blade_transport_driver.sql index 19809bf5..e81e8d17 100644 --- a/doc/sql/transport/blade_transport_driver.sql +++ b/doc/sql/transport/blade_transport_driver.sql @@ -6,7 +6,7 @@ CREATE TABLE `blade_transport_driver` ( `id` bigint(20) NOT NULL COMMENT '主键', `tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID', `driver_name` varchar(20) NOT NULL COMMENT '司机姓名', - `id_card_no` varchar(18) NOT NULL COMMENT '身份证号', + `id_card_no` varchar(18) DEFAULT NULL COMMENT '身份证号,NULL 表示尚未采集', `birthday` date DEFAULT NULL COMMENT '出生年月', `gender` varchar(10) DEFAULT NULL COMMENT '性别', `nation` varchar(20) DEFAULT NULL COMMENT '民族', @@ -37,6 +37,8 @@ CREATE TABLE `blade_transport_driver` ( `driver_type` varchar(20) NOT NULL COMMENT '司机类型:自有/外协', `mobile` varchar(20) NOT NULL COMMENT '手机号', `user_id` bigint(20) DEFAULT NULL COMMENT '关联系统用户ID', + `profile_source` varchar(20) DEFAULT NULL COMMENT 'login_auto/login_bound/admin/cert_backfill,NULL 为改造前存量', + `account_bound_time` datetime DEFAULT NULL COMMENT '档案与账号建立关联的时点', `contact_relation` varchar(20) DEFAULT NULL COMMENT '与联系人关系', `organization_name` varchar(50) NOT NULL COMMENT '所属组织', `emergency_contact_name` varchar(20) NOT NULL COMMENT '紧急联系人姓名', @@ -54,6 +56,7 @@ CREATE TABLE `blade_transport_driver` ( `is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uk_transport_driver_id_card` (`id_card_no`) USING BTREE, + UNIQUE KEY `uk_transport_driver_user` (`user_id`) USING BTREE, KEY `idx_transport_driver_mobile` (`mobile`) USING BTREE, KEY `idx_transport_driver_type` (`driver_type`) USING BTREE, KEY `idx_transport_driver_org` (`organization_name`) USING BTREE, diff --git a/doc/sql/transport/blade_transport_driver_profile_account_20261023.sql b/doc/sql/transport/blade_transport_driver_profile_account_20261023.sql new file mode 100644 index 00000000..ebb68af7 --- /dev/null +++ b/doc/sql/transport/blade_transport_driver_profile_account_20261023.sql @@ -0,0 +1,148 @@ +-- ---------------------------- +-- 司机档案与登录账号打通(2026-10-23) +-- ---------------------------- +-- 三处结构变更,均在单据「07 数据模型迁移」内: +-- ① user_id 加唯一索引 uk_transport_driver_user —— 一个账号只能对应一条司机档案。 +-- ② id_card_no 放宽为可空 —— NULL 表示「身份证号尚未采集」,是判断档案是否完善的口径。 +-- uk_transport_driver_id_card 保留:MySQL 唯一索引对多行 NULL 不去重, +-- 并发创建多个占位档案不会被唯一键挡住。 +-- ③ 新增 profile_source / account_bound_time 两列留痕: +-- profile_source 取值 login_auto / login_bound / admin / cert_backfill, +-- NULL 表示改造前存量(来源未知,不臆造);account_bound_time 是档案与账号建立关联的时点。 +-- +-- 只做结构变更,不回填数据:存量档案的 profile_source / account_bound_time 一律保持 NULL。 +-- 判定「档案是否采集」用 id_card_no IS NULL,不另加标记列(见票 01)。 +-- +-- 幂等:每段先查 information_schema,条件已满足即跳过,重复执行不报错; +-- 全部用 PREPARE 动态语句而非存储过程,因此不需要 DELIMITER——按分号逐句切分的客户端 +-- (含数据库驱动)也能直接执行。 +-- +-- 前置检查(user_id 有重复时,第 1 节会以 +-- Duplicate entry '<值>' for key 'blade_transport_driver.uk_transport_driver_user' +-- 失败并终止;此时本脚本对表零修改,先修数据再重跑): +-- 唯一索引统计**全表**行,含 is_deleted = 1 的软删除档案。票面给的 is_deleted = 0 口径 +-- 只能发现活跃重复,软删除行之间的重复同样会让第 1 节失败: +-- SELECT user_id, COUNT(*) AS dup_count FROM `blade_transport_driver` +-- WHERE user_id IS NOT NULL +-- GROUP BY user_id HAVING COUNT(*) > 1; +-- 修复口径:一个 user_id 只保留一条档案,其余把 user_id 置 NULL(不要改 id / is_deleted), +-- 确认无误后重跑本脚本。 +-- +-- 注意:uk_transport_driver_user 覆盖软删除行 —— is_deleted = 1 的档案仍占用 user_id, +-- 后台软删除某账号的档案后,该账号不能再建第二条档案(会撞唯一键); +-- 需要释放时,软删除前先把该行的 user_id 置 NULL。 + +SET @transport_driver_schema = DATABASE(); + +-- ---------------------------- +-- 0. 表存在性守卫 +-- ---------------------------- +-- 表不存在时故意执行一条只读查询,让客户端报 1146「Table ... doesn't exist」; +-- 否则下面每段都因查不到列/索引而静默跳过,脚本会假绿。 +SET @transport_driver_guard_sql = ( + SELECT IF(COUNT(*) > 0, + 'SELECT 1', + 'SELECT 1 FROM `blade_transport_driver` LIMIT 0') + FROM information_schema.tables + WHERE table_schema = @transport_driver_schema + AND table_name = 'blade_transport_driver' +); +PREPARE transport_driver_guard_stmt FROM @transport_driver_guard_sql; +EXECUTE transport_driver_guard_stmt; +DEALLOCATE PREPARE transport_driver_guard_stmt; + +-- ---------------------------- +-- 1. 账号唯一索引(先行执行:有重复时立刻失败,脚本对表零修改) +-- ---------------------------- +-- 只读诊断:有输出即为重复清单,先按上面口径修数据再往下跑。 +SELECT `user_id`, COUNT(*) AS `dup_count` + FROM `blade_transport_driver` + WHERE `user_id` IS NOT NULL + GROUP BY `user_id` +HAVING COUNT(*) > 1; + +-- 同名索引若已存在但是普通索引(non_unique = 1),本段会以 +-- Duplicate key name 'uk_transport_driver_user' 失败:先 DROP INDEX 再重跑本脚本。 +SET @transport_driver_user_index_sql = ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE `blade_transport_driver` ADD UNIQUE KEY `uk_transport_driver_user` (`user_id`) USING BTREE', + 'SELECT 1') + FROM information_schema.statistics + WHERE table_schema = @transport_driver_schema + AND table_name = 'blade_transport_driver' + AND index_name = 'uk_transport_driver_user' + AND non_unique = 0 +); +PREPARE transport_driver_user_index_stmt FROM @transport_driver_user_index_sql; +EXECUTE transport_driver_user_index_stmt; +DEALLOCATE PREPARE transport_driver_user_index_stmt; + +-- ---------------------------- +-- 2. 身份证号放宽为可空(唯一索引 uk_transport_driver_id_card 保留) +-- ---------------------------- +-- 条件写成「非空 或 长度不是 18」而不是只看 is_nullable: +-- 该列历史上曾被截短/加长过,只判可空性会漏掉类型漂移。 +SET @transport_driver_id_card_sql = ( + SELECT IF(COUNT(*) > 0, + 'ALTER TABLE `blade_transport_driver` MODIFY COLUMN `id_card_no` varchar(18) NULL COMMENT ''身份证号,NULL 表示尚未采集''', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = @transport_driver_schema + AND table_name = 'blade_transport_driver' + AND column_name = 'id_card_no' + AND (is_nullable = 'NO' OR character_maximum_length <> 18) +); +PREPARE transport_driver_id_card_stmt FROM @transport_driver_id_card_sql; +EXECUTE transport_driver_id_card_stmt; +DEALLOCATE PREPARE transport_driver_id_card_stmt; + +-- ---------------------------- +-- 3. 来源留痕列:profile_source +-- ---------------------------- +SET @transport_driver_profile_source_sql = ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE `blade_transport_driver` ADD COLUMN `profile_source` varchar(20) DEFAULT NULL COMMENT ''login_auto/login_bound/admin/cert_backfill,NULL 为改造前存量'' AFTER `user_id`', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = @transport_driver_schema + AND table_name = 'blade_transport_driver' + AND column_name = 'profile_source' +); +PREPARE transport_driver_profile_source_stmt FROM @transport_driver_profile_source_sql; +EXECUTE transport_driver_profile_source_stmt; +DEALLOCATE PREPARE transport_driver_profile_source_stmt; + +-- ---------------------------- +-- 4. 绑定留痕列:account_bound_time +-- ---------------------------- +SET @transport_driver_account_bound_time_sql = ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE `blade_transport_driver` ADD COLUMN `account_bound_time` datetime DEFAULT NULL COMMENT ''档案与账号建立关联的时点'' AFTER `profile_source`', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = @transport_driver_schema + AND table_name = 'blade_transport_driver' + AND column_name = 'account_bound_time' +); +PREPARE transport_driver_account_bound_time_stmt FROM @transport_driver_account_bound_time_sql; +EXECUTE transport_driver_account_bound_time_stmt; +DEALLOCATE PREPARE transport_driver_account_bound_time_stmt; + +-- ---------------------------- +-- 5. 回滚 +-- ---------------------------- +-- 顺序与正向相反:先删索引与两列,最后把 id_card_no 收回 NOT NULL。 +-- 收窄之前必须先清掉 NULL,否则 ALTER 会以 +-- Invalid use of NULL value / Data truncated for column 'id_card_no' 失败。 +-- 占位档案的身份证号没有真实值可还原,回滚前须业务先决定「删档案」还是「补占位串」: +-- -- ① 处理 NULL(示例为删除登录自动建的占位档案;改为 UPDATE 成占位串亦可,注意会占唯一索引) +-- DELETE FROM `blade_transport_driver` +-- WHERE `id_card_no` IS NULL AND `profile_source` IN ('login_auto', 'login_bound'); +-- -- ② 收回身份证号非空与旧注释 +-- ALTER TABLE `blade_transport_driver` +-- MODIFY COLUMN `id_card_no` varchar(18) NOT NULL COMMENT '身份证号'; +-- -- ③ 删唯一索引、删两列 +-- ALTER TABLE `blade_transport_driver` DROP INDEX `uk_transport_driver_user`; +-- ALTER TABLE `blade_transport_driver` DROP COLUMN `profile_source`, DROP COLUMN `account_bound_time`; +-- +-- uk_transport_driver_id_card 全程保留,回滚不需要恢复它。