Oracle PL/SQL应用迁移 AnalyticDB for PostgreSQL指导

AnalyticDB for PostgreSQL(简称:ADB for PG)对Oracle语法有着较好的兼容,本文介绍如何将Oracle 应用迁移到AnalyticDB for PostgreSQL。

1 开源工具ora2pg使用

在开始转换Oracle pl之前可以使用开源工具ora2pg(https://github.com/darold/ora2pg)进行最初始的转换。具体的使用,用户可以阅读其用户文档,在此不做赘述。用户可以使用ora2pg,将oracle的表DDL,view,package等转换成PG兼容的语法。但是,由于脚本转换后的PG语法版本比ADB for PG使用的PG内核版本高,而且ora2pg依赖规则进行转换,难免有所疏漏和错误,因此用户还需要人工去对转换后的sql脚本做纠正。具体推荐的做法是:可以将转换后的sql脚本在ADB for PG上试运行,观察是否有语法报错或者结果不符预期,然后在依据本文接下来几个章节的内容,进行人工纠正。

2 Orafunc插件使用

ADB for PG提供了Orafunc插件的使用,该插件提供了一些兼容Oracle的函数。对于这些函数,用户无需任何修改转换就可以直接在ADB for PG中使用。
在使用Orafunc插件前,只需执行create extension orafunc;命令即可。

postgres=> create extension orafunc;
CREATE EXTENSION

Orafunc插件提供的兼容函数如下表所示:

函数 作用 示例
nvl(anyelement, anyelement) 如果第一个参数为null,那么会返回第二个参数;否则返回第一个参数。注意:两个参数必须是相同类型。 postgres=# select nvl(null,1);
nvl
-----
  1
(1 row)
postgres=# select nvl(0,1);
nvl
-----
  0
(1 row)
postgres=# select nvl(0,null);
nvl
-----
  0
(1 row)
add_months(day date, value int)
RETURNS date
在第一个月份参数上加上第二个参数月数,返回一个date postgres=# select add_months(current_date, 2);
add_months
------------
2019-08-31
(1 row)
last_day(value date) 返回某年某个月份的最后一个天,返回类型为date postgres=# select last_day('2018-06-01');
 last_day
------------
2018-06-30
(1 row)
next_day(value date, weekday text) 参数一:开始的日期
参数二:包含星期几的英文字符串,如Friday
返回开始日期后的第二个星期几的日期,如第二个Friday
postgres=# select next_day(current_date, 'FRIDAY');
 next_day
------------
2019-07-05
(1 row)
next_day(value date, weekday integer) 参数一:开始的日期
参数二:星期几的数字,取值为1到7,1为星期日,2为星期1,以此类推
返回日期加天数之后的日期
postgres=# select next_day('2019-06-22', 1);
 next_day
------------
2019-06-23
(1 row)
postgres=# select next_day('2019-06-22', 2);
 next_day
------------
2019-06-24
(1 row)
months_between(date1 date, date2 date) 返回date1和date2之间的月数
如果 date1晚于date2,结果为正
如果date1早于date2,结果为负
postgres=# select months_between('2019-01-01', '2018-11-01');
months_between
----------------
             2
(1 row)
postgres=# select months_between('2018-11-01', '2019-01-01');
months_between
----------------
            -2
(1 row)
trunc(value timestamp with time zone, fmt text) 参数一:要被截断的timestamp
参数二:应用于截断的度量单位,如年,月,日,周,时,分,秒等,Y:会截断成日期年份的第一天,Q返回季度的第一天。
postgres=# SELECT TRUNC(current_date,'Q');
  trunc
------------
2019-04-01
(1 row)
postgres=# SELECT TRUNC(current_date,'Y');
  trunc
------------
2019-01-01
(1 row)
trunc(value timestamp with time zone) 截断timestamp,默认截断时分秒 postgres=# SELECT TRUNC('2019-12-11'::timestamp);
        trunc
------------------------
2019-12-11 00:00:00+08
(1 row)
trunc(value date) 截断日期 postgres=# SELECT TRUNC('2019-12-11'::timestamp,'Y');
        trunc
------------------------
2019-01-01 00:00:00+08
round(value timestamp with time zone, fmt text) 将timestamp圆整到最近的unit_of_measure(日,周等) postgres=# SELECT round('2018-10-06 13:11:11'::timestamp, 'YEAR');
        round
------------------------
2019-01-01 00:00:00+08
(1 row)
round(value timestamp with time zone) 默认圆整到天 postgres=# SELECT round('2018-10-06 13:11:11'::timestamp);
        round
------------------------
2018-10-07 00:00:00+08
(1 row)
round(value date, fmt text) 参数为date postgres=# SELECT round(TO_DATE('27-OCT-00','DD-MON-YY'), 'YEAR');
  round
------------
2001-01-01
(1 row)
postgres=# SELECT round(TO_DATE('27-FEB-00','DD-MON-YY'), 'YEAR');
  round
------------
2000-01-01
(1 row)
round(value date) 参数为date ostgres=# SELECT round(TO_DATE('27-FEB-00','DD-MON-YY'));
  round
------------
2000-02-27
(1 row)
instr(str text, patt text, start int, nth int) 在一个string中搜索一个substring,如果找到返回substring在string中位置,没找到,则返回0
start表示搜索的起始位置
nth表示搜索第几次出现的位置
postgres=# SELECT instr('Greenplum', 'e',1,2);
instr
-------
    4
(1 row)
postgres=# SELECT instr('Greenplum', 'e',1,1);
instr
-------
    3
(1 row)
instr(str text, patt text, start int) 没给nth,默认是第一次出现的位置 postgres=# SELECT instr('Greenplum', 'e',1);
instr
-------
    3
(1 row)
instr(str text, patt text) 没给start,默认从头开始搜索 postgres=# SELECT instr('Greenplum', 'e');
instr
-------
    3
(1 row)
reverse(str text, start int, _end int) str为输入的字符串
start,end分别为对字符串从start到end这一段进行逆序
postgres=> select reverse('adb4pg', 5,6);
reverse
---------
gp
reverse(str text, start int) 从start开始到字符串结束进行逆序 ostgres=> select reverse('adb4pg', 4);
reverse
---------
gp4
reverse(str text) 逆序整个字符串 postgres=> select reverse('adb4pg');
reverse
---------
gp4bda
(1 行记录)
concat(text, text) 将两个字符串拼接在一起 postgres=> select concat('adb','4pg');
concat
--------
adb4pg
(1 行记录)
concat(text, anyarray)/concat(anyarray, text)/concat(anyarray, anyarray) 可以拼接任意类型的数据 postgres=> select concat('adb4pg', 6666);
  concat
------------
adb4pg6666
(1 行记录)
postgres=> select concat(6666, 6666);
 concat
----------
66666666
(1 行记录)
postgres=> select concat(current_date, 6666);
    concat
----------------
2019-06-306666
(1 行记录)
nanvl(float4, float4)
/nanvl(float4, float4)/nanvl(numeric, numeric)
如果第一个参数是数值类型的,则返回第一个参数。如果不是,则返回第二参数 postgres=> select nanvl('NaN', 1.1);
nanvl
-------
  1.1
(1 行记录)
postgres=> select nanvl('1.2', 1.1);
nanvl
-------
  1.2
(1 行记录)
bitand(bigint, bigint) 将两个整形的二进制做and操作,并返回and之后的结果,只输出一行 postgres=# select bitand(1,3);
bitand
--------
     1
(1 row)
postgres=# select bitand(2,6);
bitand
--------
     2
(1 row)
postgres=# select bitand(4,6);
bitand
--------
     4
(1 row)
listagg1_transfn(text, text) 最后输出一个数组(多行),第二个参数会放在每行结果的最后,相当于分割符 postgres=> SELECT listagg1_transfn(t, '.') FROM (VALUES('abc'), ('def')) as l(t);
listagg1_transfn
------------------
abc.
def.
listagg2_transfn(text, text, text) 最后输出一个数组(多行),第二个参数会放在每行结果的最后,相当于分割符,第三个参数会和第一个参数做聚集
listagg(text) 将文本值聚集成一个串 postgres=> SELECT listagg(t) FROM (VALUES('abc'), ('def')) as l(t);
listagg
---------
abcdef
(1 行记录)
listagg(text, text) 将文本值聚集成一个串,第二个参数执行了分割符 postgres=> SELECT listagg(t, '.') FROM (VALUES('abc'), ('def')) as l(t);
listagg
---------
abc.def
(1 行记录)
nvl2(anyelement, anyelement, anyelement) 如果第一个参数不为null,那么返回第二个参数,如果为null,则返回第三个参数 postgres=> select nvl2(null, 1, 2);
nvl2
------
   2
(1 行记录)
postgres=> select nvl2(0, 1, 2);
nvl2
------
   1
(1 行记录)
lnnvl(bool) 如果参数为null或者false,则返回true,如果为true,则返回false postgres=> select lnnvl(null);
lnnvl
-------
t
(1 行记录)
postgres=> select lnnvl(false);
lnnvl
-------
t
(1 行记录)
postgres=> select lnnvl(true);
lnnvl
-------
f
(1 行记录)
dump("any") 返回一个文本值,该文本中包含第一个参数的数据类型代码、以字节计的长度和内部表示。 postgres=> select dump('adb4pg');
                dump
---------------------------------------
Typ=705 Len=7: 97,100,98,52,112,103,0
(1 行记录)
dump("any", integer) 第二个参数表示返回文本值的内部表示是使用10进制还是16进制,目前只支持10和16 postgres=> select dump('adb4pg', 10);
                dump
---------------------------------------
Typ=705 Len=7: 97,100,98,52,112,103,0
(1 行记录)
postgres=> select dump('adb4pg', 16);
               dump
------------------------------------
Typ=705 Len=7: 61,64,62,34,70,67,0
(1 行记录)
postgres=> select dump('adb4pg', 2);
ERROR:  unknown format (others.c:430)
nlssort(text, text) 指定排序规则的排序数据函数 create table t1 (name text);
INSERT INTO t1 VALUES('Anne'), ('anne'), ('Bob'), ('bob');
postgres=> select from t1 order by nlssort(name, 'en_US.UTF-8');
name
------
anne
Anne
bob
Bob
(4 行记录)
postgres=> select
from t1 order by nlssort(name, 'C');
name
------
Anne
Bob
anne
bob
(4 行记录)
substr(str text, start int) 获取参数一中字符串的子串。
参数二表示获取子串的起始位置 >= start
postgres=> select substr('adb4pg', 1);
substr
--------
adb4pg
(1 行记录)
postgres=> select substr('adb4pg', 4);
substr
--------
4pg
(1 行记录)
substr(str text, start int, len int) 参数三会指定子串的结束位置
>= start and <= end
postgres=> select substr('adb4pg', 5,6);
substr
--------
pg
(1 行记录)
pg_catalog.substrb(varchar2, integer, integer) varchar2类型的获取子串函数,参数二为start pos,参数三为end pos postgres=> select substr('adb4pg'::varchar2, 5,6) ;
substr
--------
pg
(1 行记录)
pg_catalog.substrb(varchar2, integer) varchar2类型的获取子串函数,参数二为start pos,从start pos一直取到字符串结束 postgres=> select substr('adb4pg'::varchar2, 4) ;
substr
--------
4pg
(1 行记录)
pg_catalog.lengthb(varchar2) 获取varchar2类型字符串占的字节数,如果输入为null返回null,输入为空字符,则返回0 ostgres=> select lengthb('adb4pg'::varchar2) ;
lengthb
---------
      6
(1 行记录)
postgres=> select lengthb('分析型'::varchar2) ;
lengthb
---------
      9
(1 行记录)

Orafunc插件除了提供上述兼容函数,还对Oracle的Varchar2数据类型提供了兼容。

另外,对于下面的四个Oracle函数,在ADB for PG中,无需安装orafunc插件就可以提供兼容支持:

函数 作用 示例
sinh(float) 双曲正弦值 postgres=# select sinh(0.1);
      sinh
-------------------
0.100166750019844
(1 row)
tanh(float) 双曲正切值 postgres=# select tanh(3);
      tanh
------------------
0.99505475368673
(1 row)
cosh(float) 双曲余弦值 postgres=# select cosh(0.2);
      cosh
------------------
1.02006675561908
(1 row)
decode(expression, value, return [,value, return]...
       [, default])
在表达是中寻找一个搜索值,被找到返回指定的值。如果没有搜索到,返回default值。 create table t1(id int, name varchar(20));
postgres=# insert into t1 values(1,'alibaba');
postgres=# insert into t1 values(2,'adb4pg');
postgres=# select decode(id, 1, 'alibaba', 2, 'adb4pg', 'not found') from t1;
 case
---------
alibaba
adb4pg
(2 rows)
postgres=# select decode(id, 3, 'alibaba', 4, 'adb4pg', 'not found') from t1;
  case
-----------
not found
not found
(2 rows)

3 数据类型转换对照表

Oracle ADB for PG
VARCHAR2 varchar or text
DATE timestamp
LONG text
LONG RAW bytea
CLOB text
NCLOB text
BLOB bytea
RAW bytea
ROWID oid
FLOAT double precision
DEC decimal
DECIMAL decimal
DOUBLE PRECISION double precision
INT int
INTERGE integer
REAL real
SMALLINT smallint
NUMBER numeric
BINARY_FLOAT double precision
BINARY_DOUBLE double precision
TIMESTAMP timestamp
XMLTYPE xml
BINARY_INTEGER integer
PLS_INTEGER integer
TIMESTAMP WITH TIME ZONE timestamp with time zone
TIMESTAMP WITH LOCAL TIME ZONE timestamp with time zone

4 系统函数转换对照表

Oracle ADB for PG
sysdate current timestamp
trunc trunc/ date trunc
dbms_output.put_line raise 语句
decode 转成case when/直接使用decode
NVL coalesce

5 PL/SQL

PL/SQL(Procedural Language/SQL)是一种过程化的SQL语言,是Oracle对SQL语句的拓展,使得SQL的使用可以具有一般编程语言的特点,因此,可以用来实现复杂的业务逻辑。PL/SQL对应了ADB for PG中的PL/PGSQL

5.1Package

ADB for PG的plpgsql不支持package,需要把package 转换成 schema,并package里面的所有procedure和 function转换成ADB for PG的function。
例如:

create or replace package pkg is 
…
end;

可以转换成:

create schema pkg;
  1. Package定义的变量

     procedure/function的局部变量保持不变,全局变量在ADB for PG中可以使用临时表进行保存。详见1.4.5节。
  2. Package初始化块

    如果可以删掉,就删掉,删不掉的话,可以使用function封装,在需要的时候主动调用该function。
  3. Package 内定义的procedure/function

    Package 内定义的procedure和function 转成adb for pg的function,并把function 定义到package对应的schema内。
    例如,有一个Package名为pkg中有如下函数:
    FUNCTION test_func (args int) RETURN int is 
    var number := 10;
    BEGIN
    … … 
    END;
    转换成如下ADB for PG的function:
    CREATE OR REPLACE FUNCTION pkg. test_func(args int) RETURNS int AS 
    $$
    
      … …  
    
    $$
     LANGUAGE plpgsql;

5.2 Procedure/function

对于oracle的procedure和function,不论是package的还是全局的,都转换成adb for pg 的function。
例如:

CREATE OR REPLACE FUNCTION test_func (v_name varchar2, v_version varchar2)
RETURN varchar2 IS
    ret varchar(32);
BEGIN
    IF v_version IS NULL THEN
        ret := v_name;
ELSE
    ret := v_name || '/' || v_version;
    END IF;
    RETURN ret;
END;

转化成:

CREATE OR REPLACE FUNCTION test_func (v_name varchar, v_version varchar)
RETURNS varchar AS 
$$

DECLARE
    ret varchar(32);
BEGIN
    IF v_version IS NULL THEN
        ret := v_name;
ELSE
    ret := v_name || '/' || v_version;
    END IF;
    RETURN ret;
END;

$$
 LANGUAGE plpgsql;

Procedure/function转换的关键点:

  1. RETURN 关键字转成RETURNS
  2. 函数体使用&dollar;\$ ... &dollar;\$封装起来
  3. 函数语言声明
  4. Subprocedure需要转换成ADB for PG的function

5.3 PL statement

5.3.1 For语句

带有REVERSE的整数FOR循环的工作方式不同:PL/SQL中是从第二个数向第一个数倒数,而PL/pgSQL是从第一个数向第二个数倒数,因此在移植时需要交换循环边界。
示例:

FOR i IN REVERSE 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE (TO_CHAR(i));
END LOOP;

转换成:

FOR i IN REVERSE 3..1 LOOP
    RAISE ‘%’ ,i;
END LOOP;

5.3.2 PRAGMA语句

ADB for PG 无PRAGMA语句,删除。

5.3.3 事务处理

ADB for PG 的function 内部无法使用事务控制语句,如begin,commit,rollback等。
修改方法:

  1. 删除函数体内的事务控制语句,把事务控制放在函数体外;
  2. 把函数按照commit/rollback 拆分成多个。

5.3.4 EXECUTE语句

ADB for PG支持类似oracle的动态sql语句,不同之处如下:

  1. 不支持using 语法,解决方法是把参数拼接到sql串中;
  2. 数据库标识符使用quote_ident包裹,数值使用quote_literal包裹。

示例:

EXECUTE 'UPDATE employees_temp SET commission_pct = :x' USING a_null;

转换成:

EXECUTE 'UPDATE employees_temp SET commission_pct = ' || quote_literal(a_null);

5.3.5 Pipe row

Pipe row函数,使用adb for pg的table function来替换。
示例:

TYPE pair IS RECORD(a int, b int);
TYPE numset_t IS TABLE OF pair;

FUNCTION f1(x int) RETURN numset_t PIPELINED IS
DECLARE
    v_p pair;
BEGIN
    FOR i IN 1..x LOOP
      v_p.a := i;
      v_p.b := i+10;
      PIPE ROW(v_p);
    END LOOP;
    RETURN;
END;

select * from f1(10);

转换成:

create type pair as (a int, b int);

create or replace function f1(x int) returns setof pair as 
$$

declare
rec pair;
begin
    for i in 1..x loop
        rec := row(i, i+10);
        return next rec;
    end loop;
    return ;
end

$$
 language 'plpgsql';

select * from f1(10);

说明:

  1. 自定义类型pair转换成adb for pg的复合类型pair
  2. Table of类型不需要定义,使用adb for pg的setof 替换
  3. Pipe row 语句转换成下面两个语句:

      rec := row(i);
      return next rec;
  4. 上面的oracle function还可以转换成如下:

    create or replace function f1(x int) returns setof record as 
    $$
    
    declare
    rec record;
    begin
        for i in 1..x loop
            rec := row(i, i+10);
            return next rec;
        end loop;
        return ;
    end
    
    $$
    language 'plpgsql';

与第一种改法的不同支持是,不需要提前定义数据类型numset_t.正因为这一点所以在查询的时候需要指定返回的类型,如下:select * from f1(10) as (a int, b int);

5.3.6 异常处理

  1. 使用raise抛出异常
  2. Catch异常后,不能rollback事务,只能在udf外做rollback
  3. ADB for PG支持的error,可以参考: https://www.postgresql.org/docs/8.3/errcodes-appendix.html

5.3.7 function中同时有Return和OUT参数

在adb pg中,不允许fucntion同时有return和out参数,因此,可以把需要返回的参数改写成out类型参数。

示例:

CREATE OR REPLACE FUNCTION test_func(id int, name varchar(10), out_id out int) returns varchar(10)
AS $body$
BEGIN
      out_id := id + 1;
      return name;
end
$body$
LANGUAGE PLPGSQL;

改写成:

CREATE OR REPLACE FUNCTION test_func(id int, name varchar(10), out_id out int, out_name out varchar(10))
AS $body$
BEGIN
      out_id := id + 1;
      out_name := name;
end
$body$
LANGUAGE PLPGSQL;

然后select * from test_func(1,’1’) into rec;从rec中取对应字段的返回值即可。

5.3.8 字符串连接中变量含有单引号

在下面的示例中,变量param2是一个字符串类型。假设param2的值为adb'-'pg。下面的sql_str直接放到ADB for PG中使用会将-识别成一个operator而报错。需要使用quote_literal函数来进行转换。
示例:

sql_str := 'select * from test1 where col1 = ' || param1 || ' and col2 = '''|| param2 || '''and col3 = 3';

转换成:

sql_str := 'select * from test1 where col1 = ' || param1 || ' and col2 = '|| quote_literal(param2) || 'and col3 = 3';

5.3.9 获取两个timestamp相减后的天数

示例:

SELECT to_date('2019-06-30 16:16:16') – to_date('2019-06-29 15:15:15') + 1 INTO v_days from dual;

转换成:

SELECT extract('days' from '2019-06-30 16:16:16'::timestamp - '2019-06-29 15:15:15'::timestamp + '1 days'::interval)::int INTO v_days;

5.4 PL数据类型

5.4.1 Record

使用ADB for PG的复合数据类型替换
示例:

TYPE rec IS RECORD (a int, b int);

改写成:

CREATE TYPE rec AS (a int, b int);

5.4.2 Nest table

  1. Nest table 作为pl 变量,可以使用ADB for PG的array类型替换。
    示例:
DECLARE
  TYPE Roster IS TABLE OF VARCHAR2(15);
  names Roster := 
  Roster('D Caruso', 'J Hamil', 'D Piro', 'R Singh');
BEGIN
  FOR i IN names.FIRST .. names.LAST
  LOOP
      IF names(i) = 'J Hamil' THEN
        DBMS_OUTPUT.PUT_LINE(names(i));
      END IF;
  END LOOP;
END;

改写成:

create or replace function f1() returns void as 
$$

declare
    names varchar(15)[] := '{"D Caruso", "J Hamil", "D Piro", "R Singh"}';
    len int := array_length(names, 1);
begin
    for i in 1..len loop
        if names[i] = 'J Hamil' then
            raise notice '%', names[i];
        end if;
    end loop;
    return ;
end

$$
 language 'plpgsql';

select f();
  1. 作为function返回值,则可以使用table function替换,参考1.3.5节。

5.4.3 Associative Array

无替换类型。

5.4.4 Variable-Size Arrays

与nest table 一样,使用array类型替换。

5.4.5 Global variables

目前ADB for PG不支持global variables,一种方法是把一个package中的所有global variables存入一张临时表(temporary table)中, 然后定义修改、获取global variables的函数。

示例:

create temporary table global_variables (
        id int,
        g_count int,
        g_set_id varchar(50),
        g_err_code varchar(100)
);

insert into global_variables values(0, 1, null,null);

CREATE OR REPLACE FUNCTION get_variable() returns setof global_variables AS

$$

DECLARE
    rec global_variables%rowtype;
BEGIN
    execute 'select * from global_variables' into rec;
    return next rec;
END;

$$
 LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION set_variable(in param varchar(50), in value anyelement) returns void AS

$$

BEGIN
    execute 'update global_variables set ' ||  quote_ident(param) || ' = ' || quote_literal(value);
END;

$$
 LANGUAGE plpgsql;

其中,临时表global_variables中,字段id为这个表的分布列,因为ADB for PG中不允许对于分布列的修改,需要多加一个这样的字段。
tmp_rec record;
修改一个全局变量时,使用:select * from set_variable(‘g_error_code’, ‘error’::varchar) into tmp_rec;
获取一个全局变量时,使用:select * from get_variable() into tmp_rec; error_code := tmp_rec.g_error_code;

5.5 SQL

5.5.1 Connect by

Oracle 层次查询,adb for pg没有等价替换的sql语句。转换思路是使用循环按层次遍历。
示例:

create table employee(
       emp_id numeric(18),
       lead_id numeric(18),
       emp_name varchar(200),
       salary numeric(10,2),
       dept_no varchar(8)
);
insert into employee values('1',0,'king','1000000.00','001');
insert into employee values('2',1,'jack','50500.00','002');
insert into employee values('3',1,'arise','60000.00','003');
insert into employee values('4',2,'scott','30000.00','002');
insert into employee values('5',2,'tiger','25000.00','002');
insert into employee values('6',3,'wudde','23000.00','003');
insert into employee values('7',3,'joker','21000.00','003');
insert into employee values('3',7,'joker','21000.00','003');
select emp_id,lead_id,emp_name,prior emp_name as lead_name,salary
     from employee
     start with  lead_id=0
     connect by prior emp_id =  lead_id

转换成:

create or replace function f1(tablename text, lead_id int, nocycle boolean) returns setof employee as 
$$

declare
    idx int := 0;
    res_tbl varchar(265) := 'result_table';
    prev_tbl varchar(265) := 'tmp_prev';
    curr_tbl varchar(256) := 'tmp_curr';

    current_result_sql varchar(4000);
    tbl_count int;

    rec record;
begin
    
    execute 'truncate ' || prev_tbl;
    execute 'truncate ' || curr_tbl;
    execute 'truncate ' || res_tbl;
    loop
        -- 查询当前层次结果,并插入到tmp_curr表
        current_result_sql := 'insert into ' || curr_tbl || ' select t1.* from ' || tablename || ' t1';

        if idx > 0 then
            current_result_sql := current_result_sql || ', ' || prev_tbl || ' t2 where t1.lead_id = t2.emp_id';
        else
            current_result_sql := current_result_sql || ' where t1.lead_id = ' || lead_id;
        end if;
        execute current_result_sql;

        -- 如果有环,删除已经遍历过的数据
        if nocycle is false then
            execute 'delete from ' || curr_tbl || ' where (lead_id, emp_id) in (select lead_id, emp_id from ' || res_tbl || ') ';
        end if;

        -- 如果没有数据,则退出
        execute 'select count(*) from ' || curr_tbl into tbl_count;
        exit when tbl_count = 0;

        -- 把tmp_curr数据保存到result表
        execute 'insert into ' || res_tbl || ' select * from ' || curr_tbl;
        execute 'truncate ' || prev_tbl;
        execute 'insert into ' || prev_tbl || ' select * from ' || curr_tbl;
        execute 'truncate ' || curr_tbl;
        idx := idx + 1;
    end loop;

    -- 返回结果
    current_result_sql := 'select * from ' || res_tbl;
    for rec in execute current_result_sql loop
        return next rec;
    end loop;
    return;
end

$$
 language plpgsql;

5.5.2 Rownum

  1. 限定查询结果集大小,可以使用limit替换
    示例:
select * from t where rownum < 10;

转换成:

select * from t limit 10;
  1. 使用row_number() over()生成rownum
    示例:

select rownum, * from t;
转换成:

select row_number() over() as rownum, * from t;

5.5.3 Dual表

  1. 去掉dual
    示例:
select sysdate from dual;

转换成:

select current_timestamp;
  1. 创建一个叫dual的表。

5.5.4 Select中的udf

ADB for PG支持在select中调用udf,但是udf中不能有sql语句,否则会收到如下的错误信息:
ERROR: function cannot execute on segment because it accesses relation "public.t2" (functions.c:155) (seg1 slice1 127.0.0.1:25433 pid=52153) (cdbdisp.c:1326)
DETAIL:
SQL statement "select b from t2 where a = $1 "

转换方法是把select中的udf转换成sql表达式或者子查询等
示例:

create or replace FUNCTION f1(arg int) RETURN int IS
    v int;
BEGIN
    select b into v from t2 where a = arg;
    return v;
END;

select a, f1(b) from t1;

转换成:

select t1.a, t2.b from t1, t2 where t1.b = t2.a;

5.5.5 (+)多表外链接

ADB for PG 不支持(+)这样的语法形式,需要转换成标准的outer join语法。
示例:

oracle
select * from a,b where a.id=b.id(+)

转换成:

select * from a left join b on a.id=b.id

如果在(+)中有三表的join,需要先用wte做两表的join,再用+号那个表跟wte表做outer join。
示例:

Select * from test1 t1, test2 t2, test3 t3 where t1.col1(+) between NVL(t2.col1, t3.col1) and NVL(t3.col1, t2.col1);

转换成:

with cte as (select t2.col1 as low, t2.col2, t3.col1 as high, t3.col2 as c2 from t2, t3)
select * from t1 right outer join cte on t1.col1 between coalesce(cte.low, cte.high) and coalesce(cte.high,cte.low);

5.5.6 Merge into

对于merge into语法的转换,在ADB for PG中先使用update进行更新,然后使用GET DIAGNOSTICS rowcount := ROW_COUNT;语句获取update更新的行数,如果update更新的行数为0,那么再使用insert语句进行插入。

MERGE INTO test1 t1
            USING (SELECT t2.col1 col1, t3.col2 col2,
                     FROM test2 t2, test3 t3) S
            ON S.col1 = 1 and S.col2 = 2            
WHEN MATCHED THEN
              UPDATE
              SET test1.col1 = S.col1+1,
                     test1.col2 = S.col2+2
WHEN NOT MATCHED THEN
              INSERT (col1, col2)
              VALUES
                (S.col1+1, S.col2+2);

转换成:

Update test1 t1 SET t1.col1 = test2.col1+1, test3.col2 = S.col2+2 where test2.col1 = 1 and test2.col2 = 2;
GET DIAGNOSTICS rowcount := ROW_COUNT;
if rowcount = 0 then
    insert into test1 values(test2.col1+1, test3.col2+2);
end if;

5.5.7 Sequence

示例:

create sequence seq1; 
select seq1.nextval from dual;

转换成:

create SEQUENCE seq1;
select nextval('seq1');

5.5.8 Cursor的使用

  • 在oracle中,可以使用下面的语句对cursor进行遍历
    示例:
FUNCTION test_func() IS
    Cursor data_cursor IS SELECT * from test1;
BEGIN
    FOR I IN data_cursor LOOP
        Do something with I;
END LOOP;
END;

转换成:

CREATE OR REPLACE FUNCTION test_func()
AS $body$
DECLARE
data_cursor cursor for select * from test1;
I record;
BEGIN
    Open data_cursor;
    LOOP
       Fetch data_cursor INTO I;
      If not found then
            Exit;
      End if;
      Do something with I;
    END LOOP;
    Close data_cursor;
END;
$body$
LANGUAGE PLPGSQL;
  • ORACLE可以在递归调用的函数里,重复打开名字相同的cursor。但是在ADB for PG中,则不允许,需要改写成 for I in query的形式。
    示例:
FUNCTION test_func(level IN numer) IS
    Cursor data_cursor IS SELECT * from test1;
BEGIN
If level > 5 then
        return;
   End if;

    FOR I IN data_cursor LOOP
        Do something with I;
        test_func(level + 1);
END LOOP;
END;

转换成:

CREATE OR REPLACE FUNCTION test_func(level int) returns void
AS $body$
DECLARE
data_cursor cursor for select * from test1;
I record;
BEGIN
    If level > 5 then
        return;
    End if;
    For I in select * from test1 LOOP
      Do something with I;
       PERFORM test_func(level+1);
    END LOOP;
END;
$body$
LANGUAGE PLPGSQL;

猜你喜欢

转载自yq.aliyun.com/articles/705487
今日推荐