由于前一段时间公司项目需求,参与了把 MySQL 数据库迁移到 PostgreSQL 数据库的工作,本文主要记录常用语法的转换规则,待补充完善。
数据库版本:
MySQL 8.0.x
PostgreSQL 15.0.x
基本规则
引号、反引号
在 PostgreSQL 中,单引号 AS 'col1' 和反引号 `` 都不支持,要么去掉,要么用双引号代替。
在 Java 的 MyBitis xml 文件中,如果返回结果是 Map 结构,并且返回字段用 AS 转为驼峰,则必须用双引号包起来,否则返回后会转换为小写,如
1 2 3 4 5
<selectid="getWaitAuditPurchaseReturn"resultType="java.util.Map"> select coalesce(count(1),0) as "waitAuditNum", min(prepared_bill_time) as "preparedBillMintime" ...
WHERE 中字段值的大小写与实际数据不一致
1 2 3 4 5 6
-- 这样在 MySQL 可以正常查询出来结果,但是在 PostgreSQL 中查询不到 -- 表中存的实践数据是大写的 MATERIAL_USE_TYPE select id from system_user_privilege where tableAlias.privilege_type ='material_use_type'
-- 可以用 UPPER 或 LOWER 函数处理一下 select id from system_user_privilege where tableAlias.privilege_type =UPPER('material_use_type')
PostgreSQL 是强类型的,不同类型的字段进行比较时需要显示的转换成同一类型,否则会报错;而 MySQL 中会自动进行转换。
转换时候可以使用 cast() 函数或 :: 符号。
1 2 3 4 5 6 7
-- MySQL AND (DATE(NOW()) -DATE(wb.start_time)) > ss.value
-- 上面的写法,pg 报错:nested exception is org.postgresql.sdsdf.il.PSQLException: ERROR: operator does not exist: integer > text
-- PostgreSQL AND (DATE(NOW()) -DATE(wb.start_time)) > ss.value::integer
NULL 值的默认排序规则
在 PostgreSQL 中,NULL 值的排序规则与 MySQL 不一致,需要手动指定,才能与原 MySQL 排序规则一致
1 2 3 4 5
-- 降序 NULL 值放最后显示 ORDERBY material_code DESC NULLS LAST
-- 升序 NULL 值放最前面显示 ORDERBY material_code ASC NULLS FIRST
默认排序行为差异
下面这段 SQL
1
SELECT*FROM sys_dict_data ORDERBY dict_sort ASC
查询到的结果中 dict_sort 都是 0,这种情况的查询结果 MySQL 和 PostgreSQL 的默认处理方式会存在差异:
MySQL会再按照表中的主键 id 进行排序;
PostgreSQL 则会根据记录在表中的物理存储顺序排序;
这种差异会导致两边排序不一致,在 PostgreSQL 中需要明确指定再按 id 排序,以保证与 MySQL 的默认行为一致。
FOR UPDATE 不能跟着 GROUP BY 一起使用
1 2 3 4 5
-- 下面这样写会报错:Cause: org.postgresql.util.PSQLException: ERROR: FOR UPDATE is not allowed with GROUP BY clause WHERE tableAlias.id IN ( '' ) GROUPBY tableAlias.id FORUPDATE
需要改成
1 2 3 4 5 6 7 8
-- 再包一层,外层加 FOR UPDATE SELECT t.*FROM ( -- ..... GROUPBY tableAlias.id ) t WHERE t.id IN ( '' ) FORUPDATE
聚合函数不能嵌套
1 2 3 4 5 6
-- wa.warehouse_area_type 不在后面的 GROUP BY 里,所以需要加聚合函数 -- 下面这样写,会报错:### Cause: org.postgresql.util.PSQLException: ERROR: aggregate function calls cannot be nested SELECT -- ..... SUM(CASEWHEN MIN(wa.warehouse_area_type) = 'STORE_AREA'THEN COALESCE(wsb.frozen_stock_num, 0) ELSE0END) AS occupyNum -- .....
需要改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14
-- 使用子查询,外层 sum ,内层使用 case...when 计算明细 SELECT -- ..... SUM(occupyNum) AS occupyNum, -- ..... FROM ( SELECT -- ..... (CASEWHEN wa.warehouse_area_type = 'STORE_AREA'THEN COALESCE(wsb.frozen_stock_num,0) ELSE0END) AS occupyNum -- ..... FROM warehouse_stock_batch wsb ) t GROUPBY -- .....
聚合函数构造的字段,不能直接跟在 HAVING 后面
1 2 3 4 5 6 7 8 9 10
-- 下面这样写会报错:column "custommd5" does not exist LINE 56: having customMd5 in ('') SELECT -- ..... -- customMd5 是由聚合函数 CONCAT_WS() 构造出来的 MD5(CONCAT_WS('_', xx, xx, xx,xx)) AS customMd5 FROM warehouse_stock_batch wsb GROUPBY -- ..... HAVING customMd5 IN ('')
需要像下面这样再包一层,或者把聚合的字段在 having 中再写一遍,这种适合聚合的内容不多的场景。
1 2 3 4 5 6 7 8 9 10 11
SELECT t.* FROM ( SELECT -- ..... md5(concat_ws('_', xx, xx, xx,xx)) as customMd5 -- ..... FROM warehouse_stock_batch wsb GROUPBY -- ..... ) t WHERE t.customMd5 IN ('')
GROUP BY 区分大小写
MySQL 中 GROUP BY 的字段默认不区分大小写,PG 中严格区分,会造成数据行数不一致。
常用函数替换
like –> ilike
PostgreSQL 中 like 默认区分大小,直接改成 ilike 以与 MySQL 保持一致。经测试,二者性能差异不大。
group_concat –> string_agg
1 2 3 4 5
-- MySQL GROUP_CONCAT(DISTINCT c.hospital_area_id SEPARATOR ',') AS hospital_area_id
-- PostgreSQL STRING_AGG(DISTINCT c.hospital_area_id, ',') AS hospital_area_id
ifnull –> coalesce
1 2 3 4 5
-- MySQL IFNULL(sn.price, 0)
-- PostgreSQL COALESCE(sn.price, 0)
date_format –> to_char
1 2 3 4 5
-- MySQL SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s')