migration_drainage_engineering_facility.sql 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. -- 工程设施基础数据(闸门 / 调蓄设施 / 泵站)表重建与迁移(KingbaseES, schema app_user)
  2. --
  3. -- 需求依据:《排水管网安全运行监测系统》5.3.3.1.3 工程设施基础数据管理
  4. -- 「支持对闸门、调蓄设施、泵站等工程设施的查看、查询、筛选等管理操作,
  5. -- 并可将相关要素在 GIS 地图中叠加展示。」
  6. --
  7. -- 背景:旧表 app_user.psgw_engineering_facility 是「监测点」口径的临时表结构,
  8. -- 字段名与语义都不对:name 注释写成「监测点名称/设施名称」、area 实为行政区域、
  9. -- time 是「数据时间」、point_count 由前端现算却落库、points 存多节点 JSON,
  10. -- 而一个工程设施在地图上就是一个点要素,应直接落经纬度。
  11. --
  12. -- 本脚本做三件事:
  13. -- 1. 建新表 app_user.drainage_engineering_facility(按基础档案口径设计);
  14. -- 2. 登记两个字典:drainage_facility_type(设施类型)、drainage_facility_status(运行状态);
  15. -- 3. 把旧表数据搬到新表(坐标取 points 的第一个点;旧表保留不删,确认无误后再自行下线)。
  16. --
  17. -- 幂等:建表/索引/字典均可重复执行;数据迁移仅在「新表为空」时执行一次。
  18. --
  19. -- =====================================================================
  20. -- 为什么字典 INSERT 要「显式指定主键」
  21. -- ---------------------------------------------------------------------
  22. -- 本库由 MySQL 迁移至 Kingbase,sys_dict_data / sys_dict_type 沿用了 MySQL 的
  23. -- auto_increment(KES 的 MySQL 兼容自增,不是 PG 的 serial/identity)。迁数据时是带
  24. -- 显式主键写入的,自增计数器没有跟着推进,于是「INSERT 不写主键」会发出一个已被占用的号:
  25. -- ERROR: 重复键违反唯一约束 "sys_dict_data_pkey"
  26. -- DETAIL: 键值 "dict_code"=(415) 已经存在
  27. -- 且该列不是 identity 列,pg_get_serial_sequence / pg_depend 都拿不到序列。
  28. -- 因此这里统一写成 max(主键) + n,插入后再尽力校准序列。
  29. -- =====================================================================
  30. --
  31. -- 前置(必须先执行):结束上一次失败执行遗留的 aborted 事务
  32. -- 上一次语句报错后,PG/KES 会把该事务标记为已失败,此时同一连接上后续任何语句都只返回
  33. -- 「当前事务被终止,事务块结束之前的查询被忽略」,必须先回滚才能继续。
  34. -- 注意:本句会回滚该连接上所有未提交事务;若你有别的未提交手工操作,请先单独提交再跑本脚本。
  35. ROLLBACK;
  36. BEGIN;
  37. -- =====================================================================
  38. -- 1. 建新表
  39. -- =====================================================================
  40. CREATE TABLE IF NOT EXISTS app_user.drainage_engineering_facility (
  41. facility_id varchar(64) NOT NULL,
  42. facility_code varchar(64),
  43. facility_name varchar(200) NOT NULL,
  44. facility_type varchar(8) NOT NULL,
  45. facility_level varchar(8),
  46. management_unit varchar(200),
  47. location varchar(500),
  48. longitude numeric(12, 8),
  49. latitude numeric(12, 8),
  50. status varchar(8) DEFAULT '0',
  51. build_date date,
  52. design_flow numeric(12, 2),
  53. design_head numeric(10, 2),
  54. storage_capacity numeric(14, 2),
  55. equipment_id varchar(500),
  56. remark varchar(500),
  57. create_by varchar(64),
  58. create_time timestamp,
  59. update_by varchar(64),
  60. update_time timestamp,
  61. del_flag char(1) DEFAULT '0',
  62. CONSTRAINT pk_drainage_engineering_facility PRIMARY KEY (facility_id)
  63. );
  64. COMMENT ON TABLE app_user.drainage_engineering_facility IS '工程设施基础数据(闸门/调蓄设施/泵站)';
  65. COMMENT ON COLUMN app_user.drainage_engineering_facility.facility_id IS '设施ID(主键)';
  66. COMMENT ON COLUMN app_user.drainage_engineering_facility.facility_code IS '设施编码';
  67. COMMENT ON COLUMN app_user.drainage_engineering_facility.facility_name IS '设施名称';
  68. COMMENT ON COLUMN app_user.drainage_engineering_facility.facility_type IS '设施类型(字典 drainage_facility_type:0闸门 1调蓄设施 2泵站)';
  69. COMMENT ON COLUMN app_user.drainage_engineering_facility.facility_level IS '设施等级';
  70. COMMENT ON COLUMN app_user.drainage_engineering_facility.management_unit IS '管理单位';
  71. COMMENT ON COLUMN app_user.drainage_engineering_facility.location IS '位置描述';
  72. COMMENT ON COLUMN app_user.drainage_engineering_facility.longitude IS 'WGS84 经度';
  73. COMMENT ON COLUMN app_user.drainage_engineering_facility.latitude IS 'WGS84 纬度';
  74. COMMENT ON COLUMN app_user.drainage_engineering_facility.status IS '运行状态(字典 drainage_facility_status:0正常 1检修 2停用 3故障)';
  75. COMMENT ON COLUMN app_user.drainage_engineering_facility.build_date IS '建设日期';
  76. COMMENT ON COLUMN app_user.drainage_engineering_facility.design_flow IS '设计流量(m³/s)';
  77. COMMENT ON COLUMN app_user.drainage_engineering_facility.design_head IS '设计扬程(m)';
  78. COMMENT ON COLUMN app_user.drainage_engineering_facility.storage_capacity IS '调蓄容积(m³)';
  79. COMMENT ON COLUMN app_user.drainage_engineering_facility.equipment_id IS '关联设备ID(多个用逗号分隔,沿用旧口径)';
  80. COMMENT ON COLUMN app_user.drainage_engineering_facility.remark IS '备注';
  81. COMMENT ON COLUMN app_user.drainage_engineering_facility.del_flag IS '删除标志(0正常 2删除)';
  82. CREATE INDEX IF NOT EXISTS idx_def_facility_type ON app_user.drainage_engineering_facility (facility_type);
  83. CREATE INDEX IF NOT EXISTS idx_def_facility_status ON app_user.drainage_engineering_facility (status);
  84. CREATE INDEX IF NOT EXISTS idx_def_management_unit ON app_user.drainage_engineering_facility (management_unit);
  85. CREATE INDEX IF NOT EXISTS idx_def_facility_name ON app_user.drainage_engineering_facility (facility_name);
  86. CREATE UNIQUE INDEX IF NOT EXISTS uk_def_facility_code ON app_user.drainage_engineering_facility (facility_code);
  87. -- =====================================================================
  88. -- 2. 字典:设施类型、运行状态
  89. -- =====================================================================
  90. -- 2.1 设施类型
  91. INSERT INTO app_user.sys_dict_type
  92. (dict_id, dict_name, dict_type, status, create_by, create_time, remark)
  93. SELECT (SELECT COALESCE(MAX(dict_id), 0) FROM app_user.sys_dict_type) + 1,
  94. '工程设施类型', 'drainage_facility_type', '0', 'admin', CURRENT_TIMESTAMP,
  95. '工程设施类型:0闸门 1调蓄设施 2泵站'
  96. WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'drainage_facility_type');
  97. UPDATE app_user.sys_dict_type
  98. SET dict_name = '工程设施类型',
  99. status = '0',
  100. update_by = 'admin',
  101. update_time = CURRENT_TIMESTAMP,
  102. remark = '工程设施类型:0闸门 1调蓄设施 2泵站'
  103. WHERE dict_type = 'drainage_facility_type';
  104. DELETE FROM app_user.sys_dict_data WHERE dict_type = 'drainage_facility_type';
  105. INSERT INTO app_user.sys_dict_data
  106. (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
  107. is_default, status, create_by, create_time, remark)
  108. SELECT (SELECT COALESCE(MAX(dict_code), 0) FROM app_user.sys_dict_data) + v.rn,
  109. v.dict_sort, v.dict_label, v.dict_value, 'drainage_facility_type',
  110. '', v.list_class, v.is_default, '0', 'admin', CURRENT_TIMESTAMP, v.remark
  111. FROM (VALUES
  112. (1, 1, '闸门', '0', 'primary', 'Y', '工程设施类型:闸门'),
  113. (2, 2, '调蓄设施', '1', 'warning', 'N', '工程设施类型:调蓄设施'),
  114. (3, 3, '泵站', '2', 'success', 'N', '工程设施类型:泵站')
  115. ) AS v(rn, dict_sort, dict_label, dict_value, list_class, is_default, remark)
  116. ORDER BY v.rn;
  117. -- 2.2 运行状态
  118. INSERT INTO app_user.sys_dict_type
  119. (dict_id, dict_name, dict_type, status, create_by, create_time, remark)
  120. SELECT (SELECT COALESCE(MAX(dict_id), 0) FROM app_user.sys_dict_type) + 1,
  121. '设施运行状态', 'drainage_facility_status', '0', 'admin', CURRENT_TIMESTAMP,
  122. '设施运行状态:0正常 1检修 2停用 3故障'
  123. WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'drainage_facility_status');
  124. UPDATE app_user.sys_dict_type
  125. SET dict_name = '设施运行状态',
  126. status = '0',
  127. update_by = 'admin',
  128. update_time = CURRENT_TIMESTAMP,
  129. remark = '设施运行状态:0正常 1检修 2停用 3故障'
  130. WHERE dict_type = 'drainage_facility_status';
  131. DELETE FROM app_user.sys_dict_data WHERE dict_type = 'drainage_facility_status';
  132. INSERT INTO app_user.sys_dict_data
  133. (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
  134. is_default, status, create_by, create_time, remark)
  135. SELECT (SELECT COALESCE(MAX(dict_code), 0) FROM app_user.sys_dict_data) + v.rn,
  136. v.dict_sort, v.dict_label, v.dict_value, 'drainage_facility_status',
  137. '', v.list_class, v.is_default, '0', 'admin', CURRENT_TIMESTAMP, v.remark
  138. FROM (VALUES
  139. (1, 1, '正常', '0', 'success', 'Y', '设施运行状态:正常'),
  140. (2, 2, '检修', '1', 'warning', 'N', '设施运行状态:检修'),
  141. (3, 3, '停用', '2', 'info', 'N', '设施运行状态:停用'),
  142. (4, 4, '故障', '3', 'danger', 'N', '设施运行状态:故障')
  143. ) AS v(rn, dict_sort, dict_label, dict_value, list_class, is_default, remark)
  144. ORDER BY v.rn;
  145. -- =====================================================================
  146. -- 3. 自增计数器校准(放在插入之后:此时 max 已含新插入行)
  147. -- 三种方式依次尝试,任一成功即可;都不成功只提示,不影响已插入的数据。
  148. -- =====================================================================
  149. DO $$
  150. DECLARE
  151. t record;
  152. v_seq text;
  153. v_max bigint;
  154. v_default text;
  155. BEGIN
  156. FOR t IN
  157. SELECT * FROM (VALUES ('sys_dict_data', 'dict_code'),
  158. ('sys_dict_type', 'dict_id')) AS x(tbl, col)
  159. LOOP
  160. v_seq := NULL;
  161. v_default := NULL;
  162. -- 方式一
  163. BEGIN
  164. v_seq := pg_get_serial_sequence(format('app_user.%I', t.tbl), t.col);
  165. EXCEPTION WHEN OTHERS THEN
  166. v_seq := NULL;
  167. END;
  168. -- 方式二:解析列默认值里的 nextval('xxx')
  169. IF v_seq IS NULL THEN
  170. BEGIN
  171. SELECT pg_get_expr(ad.adbin, ad.adrelid) INTO v_default
  172. FROM pg_attribute a
  173. JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
  174. WHERE a.attrelid = format('app_user.%I', t.tbl)::regclass
  175. AND a.attname::text = t.col
  176. AND a.attnum > 0
  177. AND NOT a.attisdropped;
  178. EXCEPTION WHEN OTHERS THEN
  179. v_default := NULL;
  180. END;
  181. IF v_default LIKE '%nextval%' THEN
  182. v_seq := substring(v_default from 'nextval\(''([^'']+)''');
  183. END IF;
  184. END IF;
  185. -- 方式三
  186. IF v_seq IS NULL THEN
  187. BEGIN
  188. SELECT d.objid::regclass::text INTO v_seq
  189. FROM pg_depend d
  190. JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
  191. JOIN pg_class sc ON sc.oid = d.objid AND sc.relkind = 'S'
  192. WHERE d.refobjid = format('app_user.%I', t.tbl)::regclass
  193. AND a.attname::text = t.col
  194. AND d.deptype IN ('a', 'i')
  195. LIMIT 1;
  196. EXCEPTION WHEN OTHERS THEN
  197. v_seq := NULL;
  198. END;
  199. END IF;
  200. IF v_seq IS NULL THEN
  201. RAISE NOTICE '%.% 没有关联序列(应为 MySQL 兼容 auto_increment),跳过校准。本次数据已显式指定主键插入,不影响本脚本;但后续在该表手工新增行仍可能撞主键。',
  202. t.tbl, t.col;
  203. CONTINUE;
  204. END IF;
  205. EXECUTE format('SELECT COALESCE(max(%I), 0) FROM app_user.%I', t.col, t.tbl) INTO v_max;
  206. BEGIN
  207. PERFORM setval(v_seq::regclass, GREATEST(v_max, 1), true);
  208. RAISE NOTICE '%.% 自增序列已校准: % (max=%, next=%)', t.tbl, t.col, v_seq, v_max, v_max + 1;
  209. EXCEPTION WHEN OTHERS THEN
  210. RAISE NOTICE '%.% 序列校准失败: %,跳过', t.tbl, t.col, SQLERRM;
  211. END;
  212. END LOOP;
  213. END $$;
  214. -- =====================================================================
  215. -- 4. 旧表数据迁移(仅当新表为空时执行一次)
  216. -- 坐标:旧表 points 是 JSON 数组,取第一个元素的 lng/lat;
  217. -- 解析失败或为空时置 NULL,该设施不会出现在地图上,但列表仍可见。
  218. -- 旧表字段若有缺失,整段迁移跳过并提示,不影响建表与字典。
  219. -- =====================================================================
  220. DO $$
  221. DECLARE
  222. v_idx integer := 0;
  223. r record;
  224. v_lng numeric(12, 8);
  225. v_lat numeric(12, 8);
  226. BEGIN
  227. IF to_regclass('app_user.psgw_engineering_facility') IS NULL THEN
  228. RAISE NOTICE '旧表 app_user.psgw_engineering_facility 不存在,跳过数据迁移';
  229. RETURN;
  230. END IF;
  231. IF EXISTS (SELECT 1 FROM app_user.drainage_engineering_facility) THEN
  232. RAISE NOTICE '新表 app_user.drainage_engineering_facility 已有数据,跳过迁移(避免重复导入)';
  233. RETURN;
  234. END IF;
  235. FOR r IN SELECT * FROM app_user.psgw_engineering_facility LOOP
  236. v_idx := v_idx + 1;
  237. v_lng := NULL;
  238. v_lat := NULL;
  239. BEGIN
  240. IF r.points IS NOT NULL AND r.points <> '' THEN
  241. v_lng := (regexp_match(r.points, '"lng"\s*:\s*(-?[0-9]+(\.[0-9]+)?)'))[1]::numeric(12, 8);
  242. v_lat := (regexp_match(r.points, '"lat"\s*:\s*(-?[0-9]+(\.[0-9]+)?)'))[1]::numeric(12, 8);
  243. END IF;
  244. EXCEPTION WHEN OTHERS THEN
  245. v_lng := NULL;
  246. v_lat := NULL;
  247. END;
  248. INSERT INTO app_user.drainage_engineering_facility (
  249. facility_id, facility_code, facility_name, facility_type, facility_level,
  250. management_unit, location, longitude, latitude, status, build_date,
  251. design_flow, design_head, storage_capacity, equipment_id, remark,
  252. create_by, create_time, update_by, update_time, del_flag
  253. ) VALUES (
  254. r.id,
  255. 'GCSS-' || lpad(v_idx::text, 4, '0'),
  256. COALESCE(NULLIF(trim(r.name), ''), '未命名设施'),
  257. CASE r.facility_type
  258. WHEN '闸门' THEN '0'
  259. WHEN '调蓄设施' THEN '1'
  260. WHEN '泵站' THEN '2'
  261. WHEN '0' THEN '0'
  262. WHEN '1' THEN '1'
  263. WHEN '2' THEN '2'
  264. ELSE '0'
  265. END,
  266. NULL,
  267. NULL,
  268. r.area,
  269. v_lng,
  270. v_lat,
  271. '0',
  272. NULL,
  273. NULL, NULL, NULL,
  274. r.equipment_id,
  275. NULL,
  276. 'admin', COALESCE(r.create_time, CURRENT_TIMESTAMP),
  277. 'admin', COALESCE(r.update_time, r.create_time, CURRENT_TIMESTAMP),
  278. '0'
  279. );
  280. END LOOP;
  281. RAISE NOTICE '旧表数据迁移完成,共迁移 % 条', v_idx;
  282. EXCEPTION WHEN OTHERS THEN
  283. RAISE NOTICE '旧表数据迁移跳过:%', SQLERRM;
  284. END $$;
  285. -- =====================================================================
  286. -- 5. 结果核验(可选,取消注释执行)
  287. -- =====================================================================
  288. -- 设施类型与数量
  289. -- SELECT facility_type, count(*) FROM app_user.drainage_engineering_facility
  290. -- WHERE del_flag = '0' GROUP BY facility_type ORDER BY facility_type;
  291. -- 能在地图上展示的条数(有坐标)
  292. -- SELECT count(*) FROM app_user.drainage_engineering_facility
  293. -- WHERE del_flag = '0' AND longitude IS NOT NULL AND latitude IS NOT NULL;
  294. -- 字典是否齐全(应分别返回 3 行、4 行)
  295. -- SELECT dict_type, dict_value, dict_label, list_class FROM app_user.sys_dict_data
  296. -- WHERE dict_type IN ('drainage_facility_type', 'drainage_facility_status')
  297. -- ORDER BY dict_type, dict_sort;
  298. COMMIT;