3.2.11.3. GaussDB-Oracle

3.2.11.3.1. PL/SQL 匿名块

描述

程序由三个块组成,即声明部分(DECLARE)、执行部分(BEGIN)和异常处理部分(EXCEPTION)。

三个部分的作用如下:

声明部分:在此声明 PL/SQL 用到的变量、常量、游标、类型等。

执行部分:包含程序的主要逻辑,执行SQL操作,控制结构和其他语句。

异常处理部分:捕获和处理在执行部分出现的异常。

语法

[DECLARE]
   -- 声明部分 (可选)
BEGIN
   -- 执行部分:  过程及 SQL 语句,即程序的主要部分
[EXCEPTION]
   -- 异常处理部分: 错误处理
END;

警告

  1. 不支持匿名块带标签名称。

示例

-- 转换前Oracle :
DECLARE
        v int;
BEGIN
        NULL;
EXCEPTION
        WHEN OTHERS THEN
        NULL;
END;

-- 转换后GaussDB-Oracle 匿名块:
DECLARE
        v bigint;
BEGIN
        NULL;
EXCEPTION
        WHEN others THEN
        NULL;
END;

3.2.11.3.2. 数据类型

基础类型转换 参见 数据类型转换映射表

PLSQL内置数据类型

表名.列%type --> 表名.列%type
表名%rowtype --> 表名%rowtype

警告

  1. CHAR(N) CHARACTER(N) NCHAR VARYING(N) NVARCHAR2(N) RAW(N) N最大支持2000 。

  2. NCHAR(N) N 最大支持1000 。

  3. DECIMAL(p,s) DEC(p,s) NUMBER(p,s) NUMERIC(p,s) p和s最大支持38 。

  4. FLOAT(N) 最大支持53 。

3.2.11.3.3. 声明

语法

variable_name [CONSTANT] datatype [NOT NULL] [:=|DEFAULT expression]

警告

  1. 不支持出现重名的变量

示例

-- 转换前Oracle:
     DECLARE
             c_MAX_SALARY CONSTANT NUMBER := 10000;
             c_DEFAULT_NAME CONSTANT VARCHAR2(50) := 'Default Name';
             v11 NUMBER := 100;
     BEGIN
             NULL;
     END;

-- 转换后GaussDB-Oracle:
     DECLARE
             c_max_salary CONSTANT number := 10000;
             c_default_name CONSTANT varchar2(50) := 'Default Name';
             v11 number := 100;
     BEGIN
             NULL;
     END;

3.2.11.3.4. 条件控制

3.2.11.3.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;

示例

-- 转换前Oracle:
     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;

-- 转换后GaussDB-Oracle:
     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;

3.2.11.3.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;

示例

-- 转换前Oracle:
     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;

-- 转换后GaussDB-Oracle:
     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;

3.2.11.3.5. 循环控制语句

3.2.11.3.5.1. LOOP

LOOP
        statements
END LOOP;

示例

-- 转换前Oracle :
     DECLARE
             v_counter NUMBER := 1;
     BEGIN
             LOOP
                     v_counter := v_counter + 1;
                     EXIT WHEN v_counter > 3;
             END LOOP;
     END;

-- 转换后GaussDB-Oracle:
     DECLARE
             v_counter number := 1;
     BEGIN
             LOOP
                     v_counter := v_counter+1;
                     EXIT WHEN v_counter>3;
             END LOOP;
     END;

3.2.11.3.5.2. FOR LOOP

FOR variable IN [REVERSE] lower_bound..upper_bound LOOP
        statements
END LOOP;

警告

  1. REVERSE开启后,转换会对lower_bound和upper_bound交换位置来确保语义一致。

  2. 不支持FOR LOOP 接游标的用法。

示例

-- 转换前Oracle :
     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;

-- 转换后GaussDB-Oracle:
     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/unisql.null_if_zero(low))*2) LOOP
                     log_info(step) ;
             END LOOP;
     END;

3.2.11.3.5.3. WHILE LOOP

WHILE condition LOOP
        statements
END LOOP;

示例

-- 转换前Oracle :
     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;

-- 转换后GaussDB-Oracle:
     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;

3.2.11.3.6. 注释

单行注释和多行注释转换时,会被丢掉,不影响程序的执行。

语法

BEGIN
        -- 单行注释
        /*
        多行注释
        */
END;

警告

  1. 转换后注释将会被丢弃

示例

-- 转换前Oracle:
DECLARE
        v date;
BEGIN
        -- 这是单行注释
        /*
                这是多行注释
                示例e.g SELECT CURRENT_DATE INTO v FROM dual
        */
        SELECT CURRENT_DATE INTO v FROM dual /*这是测试*/ ;
END;

-- 转换后GaussDB-Oracle:
DECLARE
        v date;
BEGIN
        SELECT localtimestamp(0) INTO v FROM sys_dummy;
END;

3.2.11.3.7. 游标

语法

声明游标:
CURSOR cursor_name(argName type[%type][,...]) IS select_statement;

打开:
OPEN cur_name(arg [, ...]);

获取游标:
FETCH cur_name INTO variable [,...];

关闭游标:
CLOSE cursor_name;

警告

  1. 不支持动态游标,循环游标。

示例

-- 转换前Oracle:
DECLARE
-- 1. 声明游标
CURSOR cur_employees IS
        SELECT employee_id, name, salary
        FROM employees_20250707
        WHERE salary >= 5000 ORDER BY employee_id asc;

-- 定义变量存储游标数据
v_emp_id   NUMBER;
v_emp_name  VARCHAR2(50);
v_salary  NUMBER;
BEGIN
-- 2. 打开游标
OPEN cur_employees;

-- 3. 循环提取数据
LOOP
        FETCH cur_employees INTO v_emp_id, v_emp_name, v_salary;
        EXIT WHEN cur_employees%NOTFOUND;  -- 游标属性判断是否结束

        log_info(
        'Employee ID:' || v_emp_id ||
        ', Name:' || v_emp_name ||
        ', Salary:' || v_salary
        );
END LOOP;

-- 4. 关闭游标
CLOSE cur_employees;
END;

-- 转换后GaussDB-Oracle:
DECLARE
        CURSOR cur_employees IS SELECT employee_id,name,salary FROM employees_20250707 WHERE salary>=5000 ORDER BY employee_id;
        v_emp_id number;
        v_emp_name varchar2(50);
        v_salary number;
BEGIN
        OPEN cur_employees;
        LOOP
        FETCH cur_employees  INTO v_emp_id, v_emp_name, v_salary;
                EXIT WHEN cur_employees%NOTFOUND;
                log_info('Employee ID:' || v_emp_id ||
                        ', Name:' || v_emp_name ||
                        ', Salary:' || v_salary
                        ) ;
        END LOOP;
        CLOSE cur_employees;
END;

3.2.11.3.8. 静态SQL

语法

SELECT ...

INSERT ...

DELETE ...

UPDATE ...

警告

  1. 在匿名块中,GaussDB_Oracle的事务管理和Oracle存在差异,GaussDB_Oracle中发生异常后会对前面的操作进行回滚,而Oracle会进行语句级提交。

  2. 统一SQL的语句级别开关 /*+SkipTransform*/ /*+noSkipTransform*/ 在PL/SQL中未做支持。

示例

     -- 转换前Oracle:
     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;


-- 转换后GaussDB-Oracle:
     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;

3.2.11.3.9. 动态SQL

语法

EXECUTE IMMEDIATE dynamic_sql_string
[INTO variable [,....]]
[USING bind_arg [,...]]

警告

  1. 当且仅当 dynamic_sql_string 为字符串文本时,会对 dynamic_sql_string 进行SQL语句转换,其他情况(如变量,表达式)只会透传。

  2. dynamic_sql_string 使用Q语法(不支持多字节的Q语法)包裹字符串时,转后会改写为单引号的形式。

  3. USING bind_arg 未支持指定OUT/IN修饰关键字。

  4. USING bind_arg 源库和目标库存在差异,当且仅当 dynamic_sql_string 绑定参数不重复使用并且与 USING 指定的绑定参数个数完全一致并且 dynamic_sql_string 转换后参数不会拓展成多个,则两个库行为一致,其他情况不支持,见示例。

示例

-- 错误示例
-- 转换前Oracle:
-- 取 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 q'[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;

-- 转换后GaussDB-Oracle:
-- 取 v_employee_id , v_name , v_salary , v_employee_id 值插入数据库
DECLARE
        v_employee_id number := 1;
        v_name varchar2(50) := 'Bob Brown';
        v_salary number := 55000;
        v_department_id number := 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;

-- 正确示例
-- 转换前Oracle:
-- 取 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 q'[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;

-- 转换后GaussDB-Oracle:
-- 取 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 := 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;

3.2.11.3.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]; -- 抛出异常

警告

  1. 异常码和异常信息对于Oracle和GaussDB-Oracle并不一致,这部分统一SQL未做支持。

  2. 预定义的异常名称 NO_DATA_FOUND TOO_MANY_ROWS OTHERS 支持,其他的异常名也是透传但不做支持。

  3. Oracle exception_init支持绑定内部错误码,GaussDBOracle未做支持。

示例

     -- 转换前Oracle:
     DECLARE
             -- 自定义异常
             custom_exception EXCEPTION;
     BEGIN
             -- 显式抛出异常
             RAISE custom_exception;
     EXCEPTION
             WHEN custom_exception THEN
                     DBMS_OUTPUT.PUT_LINE('捕获自定义异常: 业务逻辑错误');
             WHEN OTHERS THEN
                     DBMS_OUTPUT.PUT_LINE('其他未知错误: ' || SQLERRM);
     END;


-- 转换后GaussDB-Oracle:
     DECLARE
             custom_exception EXCEPTION;
     BEGIN
             RAISE custom_exception;
     EXCEPTION
             WHEN custom_exception THEN
                     dbms_output.put_line('捕获自定义异常: 业务逻辑错误') ;
             WHEN others THEN
                     dbms_output.put_line('其他未知错误: ' || SQLERRM) ;
     END;