migration_drainage_district_dict.sql 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. -- 排水「所属片区」字典(KingbaseES)
  2. -- 用途:排水子系统里凡是出现「所属区域 / 所属片区」的下拉框,统一读取本字典,
  3. -- 不再从业务表的存量值里去重生成选项(旧做法会把脏数据回灌到下拉里)。
  4. -- 口径:与既有 DistrictEnum(com.zksy.base.enums.DistrictEnum)、
  5. -- equipment_base.district 保持一致:0-城南 / 1-城北 / 2-太常。
  6. -- 库中存编码,中文只在前端展示。
  7. -- 影响:drainage_waterlogging_point.district(varchar)由「中文地名」改为「编码」。
  8. -- 说明:脚本幂等,可重复执行;只维护字典 + 归一存量片区值,不改表结构。
  9. --
  10. -- =====================================================================
  11. -- 为什么本脚本要「显式指定主键」
  12. -- ---------------------------------------------------------------------
  13. -- 本库由 MySQL 迁移至 Kingbase,sys_dict_data / sys_dict_type 建表沿用了 MySQL 的
  14. -- auto_increment(KES 的 MySQL 兼容自增,不是 PG 的 serial/identity)。迁数据时是带显式
  15. -- 主键写入的,自增计数器没有跟着推进,于是「INSERT 不写主键」会发出一个已被占用的号:
  16. -- ERROR: 重复键违反唯一约束 "sys_dict_data_pkey"
  17. -- DETAIL: 键值 "dict_code"=(415) 已经存在
  18. -- 且该列不是 identity 列,pg_get_serial_sequence / pg_depend 都拿不到序列,
  19. -- ALTER COLUMN ... RESTART WITH 也会报「不是标识列」。
  20. --
  21. -- 因此处理方式是:
  22. -- ① INSERT 一律写成 max(主键) + n,显式指定,彻底绕开自增计数器;
  23. -- ② 插入完再把能找到的序列 setval 到最新 max(步骤 5),让后续新增不受影响;
  24. -- 若确实没有关联序列(纯 auto_increment),步骤 5 只提示、不报错。
  25. -- =====================================================================
  26. --
  27. -- 前置(必须先执行):结束上一次失败执行遗留的 aborted 事务
  28. -- 上一次语句报错后,PG/KES 会把该事务标记为已失败,此时同一连接上后续任何语句都只返回
  29. -- 「当前事务被终止,事务块结束之前的查询被忽略」,必须先回滚才能继续。
  30. -- 注意:本句会回滚该连接上所有未提交事务;若你有别的未提交手工操作,请先单独提交再跑本脚本。
  31. ROLLBACK;
  32. BEGIN;
  33. -- 1. 字典类型(幂等登记;dict_id 显式取 max+1)
  34. INSERT INTO app_user.sys_dict_type
  35. (dict_id, dict_name, dict_type, status, create_by, create_time, remark)
  36. SELECT (SELECT COALESCE(MAX(dict_id), 0) FROM app_user.sys_dict_type) + 1,
  37. '所属片区', 'drainage_district', '0', 'admin', CURRENT_TIMESTAMP,
  38. '排水所属片区:0城南 1城北 2太常(与 DistrictEnum 对齐)'
  39. WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'drainage_district');
  40. UPDATE app_user.sys_dict_type
  41. SET dict_name = '所属片区',
  42. status = '0',
  43. update_by = 'admin',
  44. update_time = CURRENT_TIMESTAMP,
  45. remark = '排水所属片区:0城南 1城北 2太常(与 DistrictEnum 对齐)'
  46. WHERE dict_type = 'drainage_district';
  47. -- 2. 字典数据(幂等重写;dict_code 显式取 max+序号)
  48. -- 片区不是严重性等级,list_class 统一留空走默认色,不做颜色区分。
  49. DELETE FROM app_user.sys_dict_data WHERE dict_type = 'drainage_district';
  50. INSERT INTO app_user.sys_dict_data
  51. (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
  52. is_default, status, create_by, create_time, remark)
  53. SELECT (SELECT COALESCE(MAX(dict_code), 0) FROM app_user.sys_dict_data) + v.rn,
  54. v.dict_sort, v.dict_label, v.dict_value, 'drainage_district',
  55. '', '', v.is_default, '0', 'admin', CURRENT_TIMESTAMP, v.remark
  56. FROM (VALUES
  57. (1, 1, '城南', '0', 'N', '所属片区:城南'),
  58. (2, 2, '城北', '1', 'N', '所属片区:城北'),
  59. (3, 3, '太常', '2', 'N', '所属片区:太常')
  60. ) AS v(rn, dict_sort, dict_label, dict_value, is_default, remark)
  61. ORDER BY v.rn;
  62. -- 3. 列注释对齐(幂等,可重复执行)
  63. COMMENT ON COLUMN app_user.drainage_waterlogging_point.district
  64. IS '所属片区(字典 drainage_district:0城南 1城北 2太常)';
  65. -- 4. 存量片区值归一:中文地名 → 编码
  66. -- 只处理能精确匹配的三个片区;其余值(历史脏数据,如乱填的 '123123')
  67. -- 一律原样保留,交由步骤 6 的体检输出,由人工确认后再处理,避免误伤真实数据。
  68. UPDATE app_user.drainage_waterlogging_point
  69. SET district = CASE TRIM(district)
  70. WHEN '城南' THEN '0'
  71. WHEN '城北' THEN '1'
  72. WHEN '太常' THEN '2'
  73. END
  74. WHERE TRIM(district) IN ('城南', '城北', '太常');
  75. -- 5. 自增计数器校准(放在插入之后:此时 max 已含新插入的 3 行)
  76. -- 三种方式依次尝试,任一成功即可;都不成功只提示,不影响本脚本已插入的数据。
  77. -- 方式一 pg_get_serial_sequence(serial / identity)
  78. -- 方式二 从列默认值里解析 nextval('...')(KES 常见形态)
  79. -- 方式三 pg_depend 关联序列(兜底)
  80. DO $$
  81. DECLARE
  82. t record;
  83. v_seq text;
  84. v_max bigint;
  85. v_default text;
  86. BEGIN
  87. FOR t IN
  88. SELECT * FROM (VALUES ('sys_dict_data', 'dict_code'),
  89. ('sys_dict_type', 'dict_id')) AS x(tbl, col)
  90. LOOP
  91. v_seq := NULL;
  92. v_default := NULL;
  93. -- 方式一
  94. BEGIN
  95. v_seq := pg_get_serial_sequence(format('app_user.%I', t.tbl), t.col);
  96. EXCEPTION WHEN OTHERS THEN
  97. v_seq := NULL;
  98. END;
  99. -- 方式二:解析列默认值里的 nextval('xxx')
  100. IF v_seq IS NULL THEN
  101. BEGIN
  102. SELECT pg_get_expr(ad.adbin, ad.adrelid) INTO v_default
  103. FROM pg_attribute a
  104. JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
  105. WHERE a.attrelid = format('app_user.%I', t.tbl)::regclass
  106. AND a.attname::text = t.col
  107. AND a.attnum > 0
  108. AND NOT a.attisdropped;
  109. EXCEPTION WHEN OTHERS THEN
  110. v_default := NULL;
  111. END;
  112. IF v_default LIKE '%nextval%' THEN
  113. v_seq := substring(v_default from 'nextval\(''([^'']+)''');
  114. END IF;
  115. END IF;
  116. -- 方式三
  117. IF v_seq IS NULL THEN
  118. BEGIN
  119. SELECT d.objid::regclass::text INTO v_seq
  120. FROM pg_depend d
  121. JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
  122. JOIN pg_class sc ON sc.oid = d.objid AND sc.relkind = 'S'
  123. WHERE d.refobjid = format('app_user.%I', t.tbl)::regclass
  124. AND a.attname::text = t.col
  125. AND d.deptype IN ('a', 'i')
  126. LIMIT 1;
  127. EXCEPTION WHEN OTHERS THEN
  128. v_seq := NULL;
  129. END;
  130. END IF;
  131. IF v_seq IS NULL THEN
  132. RAISE NOTICE '%.% 没有关联序列(应为 MySQL 兼容 auto_increment),跳过校准。本次数据已显式指定主键插入,不影响本脚本;但后续在该表手工新增行仍可能撞主键。',
  133. t.tbl, t.col;
  134. CONTINUE;
  135. END IF;
  136. EXECUTE format('SELECT COALESCE(max(%I), 0) FROM app_user.%I', t.col, t.tbl) INTO v_max;
  137. BEGIN
  138. PERFORM setval(v_seq::regclass, GREATEST(v_max, 1), true);
  139. RAISE NOTICE '%.% 自增序列已校准: % (max=%, next=%)', t.tbl, t.col, v_seq, v_max, v_max + 1;
  140. EXCEPTION WHEN OTHERS THEN
  141. RAISE NOTICE '%.% 序列校准失败: %,跳过', t.tbl, t.col, SQLERRM;
  142. END;
  143. END LOOP;
  144. END $$;
  145. -- 6. 存量数据体检(只提示不改数据):片区值不在 0/1/2 的记录
  146. -- 出现提示说明该行是历史脏数据,前端下拉已由字典接管不会再产生新脏值,
  147. -- 存量部分需要人工判定归属后单独 UPDATE。
  148. DO $$
  149. DECLARE
  150. r record;
  151. v_rows bigint := 0;
  152. BEGIN
  153. FOR r IN
  154. SELECT district, COUNT(*) AS cnt
  155. FROM app_user.drainage_waterlogging_point
  156. WHERE del_flag = '0'
  157. AND (district IS NULL OR TRIM(district) = '' OR TRIM(district) NOT IN ('0', '1', '2'))
  158. GROUP BY district
  159. ORDER BY COUNT(*) DESC
  160. LOOP
  161. IF r.district IS NULL OR TRIM(r.district) = '' THEN
  162. RAISE NOTICE '片区待核对: (空值) —— % 条', r.cnt;
  163. ELSE
  164. RAISE NOTICE '片区待核对: % —— % 条', r.district, r.cnt;
  165. END IF;
  166. v_rows := v_rows + r.cnt;
  167. END LOOP;
  168. IF v_rows = 0 THEN
  169. RAISE NOTICE '片区值全部合法(0/1/2),无需处理。';
  170. ELSE
  171. RAISE NOTICE '共 % 条记录片区值不合法,请人工核对后处理。', v_rows;
  172. END IF;
  173. END $$;
  174. -- 7. 结果核验(可选,取消注释执行):应返回 3 行,dict_value 依次 0/1/2
  175. -- SELECT dict_value, dict_label FROM app_user.sys_dict_data
  176. -- WHERE dict_type = 'drainage_district' ORDER BY dict_sort;
  177. COMMIT;