1.3.2.11.7. PostgreSQL
1.3.2.11.7.1. PL/SQL 匿名块
描述
程序由三个块组成,即声明部分(DECLARE)、执行部分(BEGIN)和异常处理部分(EXCEPTION)。
三个部分的作用如下:
声明部分:在此声明 PL/SQL 用到的变量、常量、游标、类型等。
执行部分:包含程序的主要逻辑,执行SQL操作,控制结构和其他语句。
异常处理部分:捕获和处理在执行部分出现的异常。
语法
[DECLARE]
-- 声明部分 (可选)
BEGIN
-- 执行部分: 过程及 SQL 语句,即程序的主要部分
[EXCEPTION]
-- 异常处理部分: 错误处理
END;
警告
不支持匿名块带标签名称。
示例
-- 转换前 :
DECLARE
v int;
BEGIN
NULL;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
-- 转换后匿名块:
DO $$
DECLARE
v bigint;
BEGIN
NULL;
EXCEPTION
WHEN others THEN
NULL;
END;
$$;
1.3.2.11.7.2. 数据类型
基础类型转换 参见 数据类型转换映射表
1.3.2.11.7.3. 声明
语法
variable_name [CONSTANT] datatype [NOT NULL] [:=|DEFAULT expression]
警告
不支持出现重名的变量
类型不支持%rowtype, %type
变量名不支持postgreSQL关键字(保留关键字和非保留关键字)
不支持包函数,比如dbms_output等,会透传
long类型转换成了text类型,rowid转换成char(10),不支持 bfile、TIMESTAMP WITH LOCAL TIME ZONE类型
示例
-- 转换前:
DECLARE
c_MAX_SALARY CONSTANT NUMBER := 10000;
c_DEFAULT_NAME CONSTANT VARCHAR2(50) := 'Default Name';
v11 NUMBER := 100;
BEGIN
NULL;
END;
-- 转换后:
DO $$
DECLARE
c_max_salary CONSTANT number := 10000;
c_default_name CONSTANT varchar2(50) := 'Default Name';
v11 number := 100;
BEGIN
NULL;
END;
$$;
1.3.2.11.7.4. 条件控制
1.3.2.11.7.4.1. IF
语法
IF condition_1 THEN
statements_1
[ ELSIF condition_2 THEN
statements_2
]
[ ELSIF condition_3 THEN
statements_3
]...
[ ELSE
else_statements
]
END IF;
示例
-- 转换前:
DECLARE
grade CHAR(1);
msg VARCHAR(200);
BEGIN
grade := 'B';
if grade = 'A' THEN
msg := 'Excellent';
ELSIF grade = 'B' THEN
msg := 'Very Good';
ELSIF grade = 'C' THEN
msg := 'Good';
ELSIF grade = 'D' THEN
msg := 'Fair';
ELSIF grade = 'F' THEN
msg := 'Poor';
ELSE
msg := 'No such grade';
END IF;
END;
-- 转换后:
DO $$
DECLARE
grade char(1);
msg varchar(200);
BEGIN
grade := 'B';
IF grade='A' THEN
msg := 'Excellent';
ELSIF grade='B' THEN
msg := 'Very Good';
ELSIF grade='C' THEN
msg := 'Good';
ELSIF grade='D' THEN
msg := 'Fair';
ELSIF grade='F' THEN
msg := 'Poor';
ELSE
msg := 'No such grade';
END IF;
END;
$$;
1.3.2.11.7.4.2. CASE 语句
语法
CASE selector
WHEN selector_value_1 THEN statements_1
WHEN selector_value_2 THEN statements_2
...
WHEN selector_value_n THEN statements_n
[ ELSE
else_statements ]
END CASE;
CASE
WHEN condition_1 THEN statements_1
WHEN condition_2 THEN statements_2
...
WHEN condition_n THEN statements_n
[ ELSE
else_statements ]
END CASE;
示例
-- 转换前:
DECLARE
grade CHAR(1);
msg varchar(200);
BEGIN
grade := 'B';
CASE grade
WHEN 'A' THEN
msg := 'Excellent';
WHEN 'B' THEN
msg := 'Very Good';
WHEN 'C' THEN
msg := 'Good';
WHEN 'D' THEN
msg := 'Fair';
WHEN 'F' THEN
msg := 'Poor';
ELSE
msg := 'No such grade';
END CASE;
END;
-- 转换后:
DO $$
DECLARE
grade char(1);
msg varchar(200);
BEGIN
grade := 'B';
CASE grade
WHEN 'A' THEN
msg := 'Excellent';
WHEN 'B' THEN
msg := 'Very Good';
WHEN 'C' THEN
msg := 'Good';
WHEN 'D' THEN
msg := 'Fair';
WHEN 'F' THEN
msg := 'Poor';
ELSE
msg := 'No such grade';
END CASE;
END;
$$;
1.3.2.11.7.5. 循环控制语句
1.3.2.11.7.5.1. LOOP
LOOP
statements
END LOOP;
示例
-- 转换前 :
DECLARE
v_counter NUMBER := 1;
BEGIN
LOOP
v_counter := v_counter + 1;
EXIT WHEN v_counter > 3;
END LOOP;
END;
-- 转换后:
DO $$
DECLARE
v_counter number := 1;
BEGIN
LOOP
v_counter := v_counter+1;
EXIT WHEN v_counter>3;
END LOOP;
END;
$$;
1.3.2.11.7.5.2. FOR LOOP
FOR variable IN [REVERSE] lower_bound..upper_bound LOOP
statements
END LOOP;
警告
REVERSE开启后,转换会对lower_bound和upper_bound交换位置来确保语义一致
不支持FOR LOOP 接游标的用法
variable不支持postgreSQL关键字(保留关键字和非保留关键字)
示例
-- 转换前 :
DECLARE
first INTEGER := 1;
last INTEGER := 10;
high INTEGER := 100;
low INTEGER := 12;
BEGIN
FOR j IN -5..5 LOOP
log_info (j);
END LOOP;
FOR k IN REVERSE first..last LOOP
log_info (k);
END LOOP;
FOR step IN 0..(TRUNC(high/low) * 2) LOOP
log_info (step);
END LOOP;
END;
-- 转换后:
DO $$
DECLARE
first bigint := 1;
last bigint := 10;
high bigint := 100;
low bigint := 12;
BEGIN
FOR j IN -5..5 LOOP
log_info(j) ;
END LOOP;
FOR k IN REVERSE last..first LOOP
log_info(k) ;
END LOOP;
FOR step IN 0..(TRUNC(high/low)*2) LOOP
log_info(step) ;
END LOOP;
END;
$$;
1.3.2.11.7.5.3. WHILE LOOP
WHILE condition LOOP
statements
END LOOP;
示例
-- 转换前 :
DECLARE
v_counter NUMBER := 1;
BEGIN
WHILE v_counter <= 3 LOOP
log_info('Counter value in WHILE loop: ' || v_counter);
v_counter := v_counter + 1;
END LOOP;
END;
-- 转换后:
DO $$
DECLARE
v_counter number := 1;
BEGIN
WHILE v_counter<=3 LOOP
log_info('Counter value in WHILE loop: ' || v_counter) ;
v_counter := v_counter+1;
END LOOP;
END;
$$;
1.3.2.11.7.6. 注释
单行注释和多行注释转换时,会被丢掉,不影响程序的执行。
语法
BEGIN
-- 单行注释
/*
多行注释
*/
END;
警告
转换后注释将会被丢弃
示例
-- 转换前:
DECLARE
v date;
BEGIN
-- 这是单行注释
/*
这是多行注释
示例e.g SELECT CURRENT_DATE INTO v FROM dual
*/
SELECT CURRENT_DATE INTO v FROM dual /*这是测试*/ ;
END;
-- 转换后:
DO $$
DECLARE
v date;
BEGIN
SELECT localtimestamp(0) INTO v FROM dual;
END;
$$;
1.3.2.11.7.7. 游标
语法
声明游标:
CURSOR cursor_name(argName type[%type][,...]) IS select_statement;
打开:
OPEN cur_name(arg [, ...]);
获取游标:
FETCH cur_name INTO variable [,...];
关闭游标:
CLOSE cursor_name;
警告
不支持动态游标,循环游标
不支持声明游标、打开游标、获取游标和关闭游标
不支持游标属性,能转换成功,但是执行失败
1.3.2.11.7.8. 静态SQL
语法
SELECT ...
INSERT ...
DELETE ...
UPDATE ...
警告
在匿名块中,当执行过程中发生异常时,PostgreSQL的事务提交回滚和Oracle存在差异,以实际为准。
统一SQL的语句级别开关
/*+SkipTransform*//*+noSkipTransform*/在PL/SQL中未做支持。for in loop在postgres因为需要把结果集名称单独拿出来声明,因此多个for in loop不允许重名。
示例
-- 转换前:
DECLARE
v_name varchar(64);
v_bonus NUMBER(8,2);
v_employee_id NUMBER(8,2) default 100;
BEGIN
INSERT INTO employees_20250707 (employee_id, name, salary, hire_date) VALUES (100, 'Bob', 5000, TO_DATE('2021-02-15', 'YYYY-MM-DD'));
SELECT salary * 0.10, name INTO v_bonus, v_name FROM employees_20250707 WHERE employee_id = 100;
log_info('name = ' || v_name || ' bonus = ' || TO_CHAR(v_bonus));
UPDATE employees_20250707 SET salary = salary + 1000 WHERE employee_id = v_employee_id;
SELECT salary * 0.10, name INTO v_bonus, v_name FROM employees_20250707 WHERE employee_id = 100;
log_info('name = ' || v_name || ' bonus = ' || TO_CHAR(v_bonus));
DELETE FROM employees_20250707 WHERE employee_id = v_employee_id;
END;
-- 转换后:
DO $$
DECLARE
v_name varchar(64);
v_bonus number(8,2);
v_employee_id number(8,2) := 100;
BEGIN
INSERT INTO employees_20250707 (employee_id,name,salary,hire_date) VALUES (100,'Bob',5000,TO_DATE('2021-02-15', 'YYYY-MM-DD'));
SELECT salary*0.10,name INTO v_bonus, v_name FROM employees_20250707 WHERE employee_id=100;
log_info('name = ' || v_name || ' bonus = ' || TO_CHAR(v_bonus)) ;
UPDATE employees_20250707 SET salary=salary+1000 WHERE employee_id=v_employee_id;
SELECT salary*0.10,name INTO v_bonus, v_name FROM employees_20250707 WHERE employee_id=100;
log_info('name = ' || v_name || ' bonus = ' || TO_CHAR(v_bonus)) ;
DELETE FROM employees_20250707 WHERE employee_id=v_employee_id;
END;
$$;
1.3.2.11.7.9. 动态SQL
语法
EXECUTE IMMEDIATE dynamic_sql_string
[INTO variable [,....]]
[USING bind_arg [,...]]
警告
当且仅当
dynamic_sql_string为字符串文本时,会对dynamic_sql_string进行SQL语句转换,其他情况(如变量,表达式)只会透传USINGbind_arg未支持指定OUT/IN修饰关键字USINGbind_arg源库和目标库存在差异,当且仅当dynamic_sql_string绑定参数不重复使用并且与USING指定的绑定参数个数完全一致并且dynamic_sql_string转换后参数不会拓展成多个,则两个库行为一致,其他情况不支持,见示例不支持 Q 转义,不支持绑定参数
示例
-- 错误示例
-- 转换前:
-- 取 v_employee_id , v_name , v_salary , v_department_id 值插入数据库
DECLARE
v_employee_id number := 1;
v_name varchar2(50) := 'Bob Brown';
v_salary number := 55000;
v_department_id NUMBER DEFAULT 10;
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM employees_20250707';
EXECUTE IMMEDIATE 'INSERT INTO employees_20250707 (employee_id,name,salary,department_id) VALUES (:1,:2,:3,:1)' USING v_employee_id, v_name, v_salary, v_department_id;
END;
-- 转换后:
-- 取 v_employee_id , v_name , v_salary , v_employee_id 值插入数据库
DO $$
DECLARE
v_employee_id number := 1;
v_name varchar2(50) := 'Bob Brown';
v_salary number := 55000;
v_department_id number := 10;
BEGIN
EXECUTE 'DELETE FROM employees_20250707';
EXECUTE 'INSERT INTO employees_20250707 (employee_id,name,salary,department_id) VALUES (:1,:2,:3,:1)' USING v_employee_id, v_name, v_salary, v_department_id;
END;
$$;
-- 正确示例
-- 转换前:
-- 取 v_employee_id , v_name , v_salary , v_department_id 值插入数据库
DECLARE
v_employee_id number := 1;
v_name varchar2(50) := 'Bob Brown';
v_salary number := 55000;
v_department_id NUMBER DEFAULT 10;
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM employees_20250707';
EXECUTE IMMEDIATE 'INSERT INTO employees_20250707 (employee_id,name,salary,department_id) VALUES (:1,:2,:3,:4)' USING v_employee_id, v_name, v_salary, v_department_id;
END;
-- 转换后:
-- 取 v_employee_id , v_name , v_salary , v_department_id 值插入数据库
DO $$
DECLARE
v_employee_id number := 1;
v_name varchar2(50) := 'Bob Brown';
v_salary number := 55000;
v_department_id number := 10;
BEGIN
EXECUTE 'DELETE FROM employees_20250707';
EXECUTE 'INSERT INTO employees_20250707 (employee_id,name,salary,department_id) VALUES (:1,:2,:3,:4)' USING v_employee_id, v_name, v_salary, v_department_id;
END;
$$;
1.3.2.11.7.10. 异常
语法
-- plsql中异常处理:
EXCEPTION
WHEN ex_name_1 THEN statements_1 -- 异常处理程序
WHEN ex_name_2 OR ex_name_3 THEN statements_2 -- 异常处理程序
WHEN OTHERS THEN statements_3 -- 异常处理程序
END;
exception_name EXCEPTION; -- 自定义异常声明:
PRAGMA EXCEPTION_INIT(exception_name, error_code); -- 异常关联错误码
raise [exception_name]; -- 抛出异常
警告
异常码和异常信息对于Oracle和PostgreSQL并不一致,这部分统一SQL未做支持
支持预定义的异常名称
zero_dividdup_val_on_indexOTHERS,其他的异常名也是透传但不做支持SQLCODE在目标库PostgreSQL未做支持,统一SQL也未做转换支持
自定义异常声明、异常关联错误码、抛出异常透传不做支持
示例
-- 转换前:
DECLARE
-- 自定义异常
custom_exception EXCEPTION;
BEGIN
-- 显式抛出异常
RAISE custom_exception;
EXCEPTION
WHEN custom_exception THEN
log_info('捕获自定义异常: 业务逻辑错误');
WHEN OTHERS THEN
log_info('其他未知错误: ' || SQLERRM);
END;
-- 转换后:
DO $$
DECLARE
custom_exception EXCEPTION;
BEGIN
RAISE custom_exception;
EXCEPTION
WHEN custom_exception THEN
log_info('捕获自定义异常: 业务逻辑错误') ;
WHEN others THEN
log_info('其他未知错误: ' || SQLERRM) ;
END;
$$;