apply_drainage_jcsj_fix.sql 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. -- ============================================================================
  2. -- 排水基础数据:一次性补齐脚本(KingbaseES, schema app_user)
  3. --
  4. -- 适用场景
  5. -- 排水子系统页面报「字段 xxx 不存在」,例如:
  6. -- ERROR: 字段 "duty_dept_id" 不存在
  7. -- ERROR: 关系 "early_warning" 的 "longitude" 字段不存在
  8. -- 说明库里的表还停留在旧版本。本脚本把本轮所有相关改动合并成一次执行,
  9. -- 不改表结构之外的东西,不删任何数据。
  10. --
  11. -- 包含四段(均可单独复制执行)
  12. -- 1. drainage_waterlogging_point:补齐实体用到的全部列 + 索引
  13. -- 2. 责任单位/责任人:duty_dept_id / duty_user_id 的存量回填(只做精确匹配)
  14. -- 3. 字典 drainage_district:0城南 1城北 2太常
  15. -- 4. early_warning:device_name 补齐、warning_level 转 integer、
  16. -- 接收人表 psgw_warning_scope_user 建表、旧版遗留列放开非空
  17. --
  18. -- 幂等:可重复执行,已到位的部分只会输出 NOTICE。
  19. -- ============================================================================
  20. -- 结束上一次失败执行遗留的 aborted 事务(在事务外执行只会告警,不报错)
  21. ROLLBACK;
  22. BEGIN;
  23. SET search_path TO app_user, public;
  24. -- ============================================================================
  25. -- 1. 易积水点表:补齐实体所需的全部列
  26. -- (老库可能只缺 duty_* 两列,也可能缺 water_depth / risk_level 等,
  27. -- 这里按实体字段全集逐列判断,缺哪列补哪列)
  28. -- ============================================================================
  29. DO $$
  30. DECLARE
  31. col record;
  32. added int := 0;
  33. BEGIN
  34. IF to_regclass('app_user.drainage_waterlogging_point') IS NULL THEN
  35. RAISE NOTICE '1. 表 app_user.drainage_waterlogging_point 不存在,跳过补列';
  36. RETURN;
  37. END IF;
  38. FOR col IN
  39. SELECT * FROM (VALUES
  40. ('point_code', 'varchar(64)'),
  41. ('point_name', 'varchar(200)'),
  42. ('district', 'varchar(100)'),
  43. ('location', 'varchar(500)'),
  44. ('longitude', 'numeric(12,8)'),
  45. ('latitude', 'numeric(12,8)'),
  46. ('waterlogging_level', 'int2'),
  47. ('water_depth', 'numeric(10,2)'),
  48. ('water_volume', 'numeric(14,2)'),
  49. ('water_area', 'numeric(14,2)'),
  50. ('risk_level', 'varchar(8)'),
  51. ('duty_unit', 'varchar(200)'),
  52. ('duty_person', 'varchar(64)'),
  53. ('duty_phone', 'varchar(32)'),
  54. ('duty_dept_id', 'bigint'),
  55. ('duty_user_id', 'bigint'),
  56. ('camera_ids', 'varchar(500)'),
  57. ('remark', 'varchar(500)'),
  58. ('create_by', 'varchar(64)'),
  59. ('create_time', 'timestamp'),
  60. ('update_by', 'varchar(64)'),
  61. ('update_time', 'timestamp'),
  62. ('del_flag', 'char(1)')
  63. ) AS x(name, type)
  64. LOOP
  65. IF NOT EXISTS (
  66. SELECT 1 FROM information_schema.columns
  67. WHERE table_schema = 'app_user'
  68. AND table_name = 'drainage_waterlogging_point'
  69. AND column_name = col.name
  70. ) THEN
  71. EXECUTE format('ALTER TABLE app_user.drainage_waterlogging_point ADD COLUMN %I %s',
  72. col.name, col.type);
  73. added := added + 1;
  74. RAISE NOTICE '1. 已补列:drainage_waterlogging_point.%', col.name;
  75. END IF;
  76. END LOOP;
  77. IF added = 0 THEN
  78. RAISE NOTICE '1. drainage_waterlogging_point 列已齐全,无需改动';
  79. END IF;
  80. END $$;
  81. -- del_flag 有历史 NULL 值时逻辑删除过滤会漏行,统一补成 '0'
  82. DO $$
  83. BEGIN
  84. IF to_regclass('app_user.drainage_waterlogging_point') IS NULL THEN
  85. RAISE NOTICE '1. 表不存在,跳过索引与注释';
  86. RETURN;
  87. END IF;
  88. UPDATE app_user.drainage_waterlogging_point SET del_flag = '0' WHERE del_flag IS NULL;
  89. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_point_code ON app_user.drainage_waterlogging_point (point_code)';
  90. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_point_name ON app_user.drainage_waterlogging_point (point_name)';
  91. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_district ON app_user.drainage_waterlogging_point (district)';
  92. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_level ON app_user.drainage_waterlogging_point (waterlogging_level)';
  93. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_risk_level ON app_user.drainage_waterlogging_point (risk_level)';
  94. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_duty_dept ON app_user.drainage_waterlogging_point (duty_dept_id)';
  95. EXECUTE 'CREATE INDEX IF NOT EXISTS idx_dwp_duty_user ON app_user.drainage_waterlogging_point (duty_user_id)';
  96. EXECUTE 'COMMENT ON COLUMN app_user.drainage_waterlogging_point.district IS ''所属片区(字典 drainage_district:0城南 1城北 2太常)''';
  97. EXECUTE 'COMMENT ON COLUMN app_user.drainage_waterlogging_point.duty_unit IS ''责任单位(名称快照,对应 duty_dept_id)''';
  98. EXECUTE 'COMMENT ON COLUMN app_user.drainage_waterlogging_point.duty_person IS ''责任人(姓名快照,对应 duty_user_id)''';
  99. EXECUTE 'COMMENT ON COLUMN app_user.drainage_waterlogging_point.duty_phone IS ''联系电话(快照,选人时取 sys_user.phonenumber)''';
  100. EXECUTE 'COMMENT ON COLUMN app_user.drainage_waterlogging_point.duty_dept_id IS ''责任单位ID(sys_dept.dept_id)''';
  101. EXECUTE 'COMMENT ON COLUMN app_user.drainage_waterlogging_point.duty_user_id IS ''责任人ID(sys_user.user_id)''';
  102. END $$;
  103. -- ============================================================================
  104. -- 2. 责任单位/责任人回填:按名称精确匹配,匹配不上保持 NULL(不猜)
  105. -- 同一名称对应多条时取 ID 最小的一个,避免一对多歧义放大。
  106. -- ============================================================================
  107. DO $$
  108. DECLARE
  109. v_dept integer := 0;
  110. v_user integer := 0;
  111. BEGIN
  112. IF to_regclass('app_user.drainage_waterlogging_point') IS NULL THEN
  113. RAISE NOTICE '2. 表不存在,跳过回填';
  114. RETURN;
  115. END IF;
  116. UPDATE app_user.drainage_waterlogging_point p
  117. SET duty_dept_id = d.dept_id
  118. FROM (SELECT dept_name, MIN(dept_id) AS dept_id
  119. FROM app_user.sys_dept
  120. WHERE del_flag = '0'
  121. GROUP BY dept_name) d
  122. WHERE p.duty_dept_id IS NULL
  123. AND p.duty_unit IS NOT NULL
  124. AND trim(p.duty_unit) = d.dept_name;
  125. GET DIAGNOSTICS v_dept = ROW_COUNT;
  126. UPDATE app_user.drainage_waterlogging_point p
  127. SET duty_user_id = u.user_id
  128. FROM (SELECT nick_name, MIN(user_id) AS user_id
  129. FROM app_user.sys_user
  130. WHERE del_flag = '0'
  131. GROUP BY nick_name) u
  132. WHERE p.duty_user_id IS NULL
  133. AND p.duty_person IS NOT NULL
  134. AND trim(p.duty_person) = u.nick_name;
  135. GET DIAGNOSTICS v_user = ROW_COUNT;
  136. RAISE NOTICE '2. 存量回填:责任单位匹配 % 条,责任人匹配 % 条', v_dept, v_user;
  137. END $$;
  138. -- ============================================================================
  139. -- 3. 字典:所属片区 drainage_district
  140. -- 本库 sys_dict_data / sys_dict_type 是 MySQL 兼容 auto_increment,
  141. -- 自增计数器未随迁移数据推进,主键一律显式取 max+序号。
  142. -- ============================================================================
  143. INSERT INTO app_user.sys_dict_type
  144. (dict_id, dict_name, dict_type, status, create_by, create_time, remark)
  145. SELECT (SELECT COALESCE(MAX(dict_id), 0) FROM app_user.sys_dict_type) + 1,
  146. '所属片区', 'drainage_district', '0', 'admin', CURRENT_TIMESTAMP,
  147. '排水所属片区:0城南 1城北 2太常(与 DistrictEnum 对齐)'
  148. WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'drainage_district');
  149. UPDATE app_user.sys_dict_type
  150. SET dict_name = '所属片区',
  151. status = '0',
  152. update_by = 'admin',
  153. update_time = CURRENT_TIMESTAMP,
  154. remark = '排水所属片区:0城南 1城北 2太常(与 DistrictEnum 对齐)'
  155. WHERE dict_type = 'drainage_district';
  156. DELETE FROM app_user.sys_dict_data WHERE dict_type = 'drainage_district';
  157. INSERT INTO app_user.sys_dict_data
  158. (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
  159. is_default, status, create_by, create_time, remark)
  160. SELECT (SELECT COALESCE(MAX(dict_code), 0) FROM app_user.sys_dict_data) + v.rn,
  161. v.dict_sort, v.dict_label, v.dict_value, 'drainage_district',
  162. '', '', v.is_default, '0', 'admin', CURRENT_TIMESTAMP, v.remark
  163. FROM (VALUES
  164. (1, 1, '城南', '0', 'N', '所属片区:城南'),
  165. (2, 2, '城北', '1', 'N', '所属片区:城北'),
  166. (3, 3, '太常', '2', 'N', '所属片区:太常')
  167. ) AS v(rn, dict_sort, dict_label, dict_value, is_default, remark)
  168. ORDER BY v.rn;
  169. -- 存量片区值归一:中文地名 → 编码(无法识别的一律原样保留,避免误伤脏数据)
  170. DO $$
  171. BEGIN
  172. IF to_regclass('app_user.drainage_waterlogging_point') IS NULL THEN
  173. RAISE NOTICE '3. 表不存在,跳过片区值归一';
  174. RETURN;
  175. END IF;
  176. UPDATE app_user.drainage_waterlogging_point
  177. SET district = CASE TRIM(district)
  178. WHEN '城南' THEN '0'
  179. WHEN '城北' THEN '1'
  180. WHEN '太常' THEN '2'
  181. END
  182. WHERE TRIM(district) IN ('城南', '城北', '太常');
  183. END $$;
  184. -- 字典自增序列尽力校准(三种方式兜底,失败只提示)
  185. DO $$
  186. DECLARE
  187. t record;
  188. v_seq text;
  189. v_max bigint;
  190. v_default text;
  191. BEGIN
  192. FOR t IN
  193. SELECT * FROM (VALUES ('sys_dict_data', 'dict_code'),
  194. ('sys_dict_type', 'dict_id')) AS x(tbl, col)
  195. LOOP
  196. v_seq := NULL;
  197. v_default := NULL;
  198. BEGIN
  199. v_seq := pg_get_serial_sequence(format('app_user.%I', t.tbl), t.col);
  200. EXCEPTION WHEN OTHERS THEN
  201. v_seq := NULL;
  202. END;
  203. IF v_seq IS NULL THEN
  204. BEGIN
  205. SELECT pg_get_expr(ad.adbin, ad.adrelid) INTO v_default
  206. FROM pg_attribute a
  207. JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
  208. WHERE a.attrelid = format('app_user.%I', t.tbl)::regclass
  209. AND a.attname::text = t.col
  210. AND a.attnum > 0
  211. AND NOT a.attisdropped;
  212. EXCEPTION WHEN OTHERS THEN
  213. v_default := NULL;
  214. END;
  215. IF v_default LIKE '%nextval%' THEN
  216. v_seq := substring(v_default from 'nextval\(''([^'']+)''');
  217. END IF;
  218. END IF;
  219. IF v_seq IS NULL THEN
  220. BEGIN
  221. SELECT d.objid::regclass::text INTO v_seq
  222. FROM pg_depend d
  223. JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
  224. JOIN pg_class sc ON sc.oid = d.objid AND sc.relkind = 'S'
  225. WHERE d.refobjid = format('app_user.%I', t.tbl)::regclass
  226. AND a.attname::text = t.col
  227. AND d.deptype IN ('a', 'i')
  228. LIMIT 1;
  229. EXCEPTION WHEN OTHERS THEN
  230. v_seq := NULL;
  231. END;
  232. END IF;
  233. IF v_seq IS NULL THEN
  234. RAISE NOTICE '3. %.% 没有关联序列(MySQL 兼容 auto_increment),跳过校准', t.tbl, t.col;
  235. CONTINUE;
  236. END IF;
  237. EXECUTE format('SELECT COALESCE(max(%I), 0) FROM app_user.%I', t.col, t.tbl) INTO v_max;
  238. BEGIN
  239. PERFORM setval(v_seq::regclass, GREATEST(v_max, 1), true);
  240. RAISE NOTICE '3. %.% 自增序列已校准 (max=%)', t.tbl, t.col, v_max;
  241. EXCEPTION WHEN OTHERS THEN
  242. RAISE NOTICE '3. %.% 序列校准失败: %,跳过', t.tbl, t.col, SQLERRM;
  243. END;
  244. END LOOP;
  245. END $$;
  246. -- ============================================================================
  247. -- 4. 预警表 early_warning
  248. -- ============================================================================
  249. DO $$
  250. DECLARE
  251. has_device_name boolean;
  252. has_warning_name boolean;
  253. v_type text;
  254. col_name text;
  255. exists_col boolean;
  256. BEGIN
  257. IF to_regclass('app_user.early_warning') IS NULL THEN
  258. RAISE NOTICE '4. 表 app_user.early_warning 不存在,跳过';
  259. RETURN;
  260. END IF;
  261. -- 4.1 设备名称列:实体映射 device_name
  262. SELECT EXISTS (SELECT 1 FROM information_schema.columns
  263. WHERE table_schema = 'app_user' AND table_name = 'early_warning'
  264. AND column_name = 'device_name') INTO has_device_name;
  265. SELECT EXISTS (SELECT 1 FROM information_schema.columns
  266. WHERE table_schema = 'app_user' AND table_name = 'early_warning'
  267. AND column_name = 'warning_name') INTO has_warning_name;
  268. IF has_device_name THEN
  269. RAISE NOTICE '4.1 early_warning.device_name 已存在';
  270. ELSE
  271. ALTER TABLE app_user.early_warning ADD COLUMN device_name varchar(256);
  272. IF has_warning_name THEN
  273. EXECUTE 'UPDATE app_user.early_warning SET device_name = warning_name WHERE device_name IS NULL';
  274. RAISE NOTICE '4.1 已新增 device_name,并搬迁 warning_name 的数据';
  275. ELSE
  276. RAISE NOTICE '4.1 已新增 device_name';
  277. END IF;
  278. END IF;
  279. -- 4.2 warning_level 统一为 integer(实体/查询/前端都是整数)
  280. SELECT data_type INTO v_type FROM information_schema.columns
  281. WHERE table_schema = 'app_user' AND table_name = 'early_warning' AND column_name = 'warning_level';
  282. IF v_type IS NULL THEN
  283. ALTER TABLE app_user.early_warning ADD COLUMN warning_level integer;
  284. RAISE NOTICE '4.2 已新增 warning_level integer';
  285. ELSIF v_type IN ('integer', 'smallint', 'bigint', 'numeric', 'decimal') THEN
  286. RAISE NOTICE '4.2 warning_level 已是数值类型(%),跳过', v_type;
  287. ELSE
  288. ALTER TABLE app_user.early_warning ALTER COLUMN warning_level DROP DEFAULT;
  289. ALTER TABLE app_user.early_warning
  290. ALTER COLUMN warning_level TYPE integer
  291. USING (CASE WHEN trim(warning_level) ~ '^[0-9]+$' THEN trim(warning_level)::integer ELSE 1 END);
  292. RAISE NOTICE '4.2 warning_level 已由 % 转为 integer', v_type;
  293. END IF;
  294. -- 4.3 旧版实体遗留列:程序已不再写入,放开非空避免插入失败
  295. FOREACH col_name IN ARRAY ARRAY['current_value', 'threshold', 'unit', 'warning_time'] LOOP
  296. SELECT EXISTS (SELECT 1 FROM information_schema.columns
  297. WHERE table_schema = 'app_user' AND table_name = 'early_warning'
  298. AND column_name = col_name) INTO exists_col;
  299. IF exists_col THEN
  300. EXECUTE format('ALTER TABLE app_user.early_warning ALTER COLUMN %I DROP NOT NULL', col_name);
  301. RAISE NOTICE '4.3 early_warning.% 已改为可空', col_name;
  302. END IF;
  303. END LOOP;
  304. END $$;
  305. -- 4.4 指定人员发布的接收人表
  306. CREATE TABLE IF NOT EXISTS app_user.psgw_warning_scope_user (
  307. id varchar(64) NOT NULL,
  308. warning_id varchar(64),
  309. user_id bigint,
  310. user_name varchar(128),
  311. approved integer DEFAULT 0,
  312. approve_time timestamp,
  313. create_time timestamp,
  314. PRIMARY KEY (id)
  315. );
  316. CREATE INDEX IF NOT EXISTS idx_psgw_warning_scope_user_warning
  317. ON app_user.psgw_warning_scope_user (warning_id);
  318. COMMENT ON TABLE app_user.psgw_warning_scope_user IS '排水预警接收人员';
  319. COMMIT;
  320. -- ============================================================================
  321. -- 核验(可选,取消注释后单独执行)
  322. -- ============================================================================
  323. -- SELECT column_name, data_type FROM information_schema.columns
  324. -- WHERE table_schema = 'app_user' AND table_name = 'drainage_waterlogging_point'
  325. -- ORDER BY ordinal_position;
  326. --
  327. -- SELECT dict_value, dict_label FROM app_user.sys_dict_data
  328. -- WHERE dict_type = 'drainage_district' ORDER BY dict_sort;