AnalyticDB PostgreSQL对Oracle语法有着较好的兼容,本文介绍如何将Oracle应用迁移到AnalyticDB PostgreSQL

基于ora2pg完成初步转换工作

可以使用开源工具ora2pg进行最初的Oracle应用转换。您可以使用ora2pg将Oracle的表DDL,view,package等语法转换成PostgreSQL兼容的语法。具体操作方法请参见ora2pg的用户文档。

说明 由于脚本转换后的PostgreSQL语法版本比AnalyticDB PostgreSQL使用的PostgreSQL内核版本高,而且ora2pg依赖规则进行转换,难免会存在偏差,因此您还需要手动对转换后的SQL脚本做纠正。

使用Orafce插件

AnalyticDB PostgreSQL中提供了Orafce插件,该插件提供了一些兼容Oracle的函数。对于这些函数,您无需任何修改转换即可在AnalyticDB PostgreSQL中使用。

在使用Orafce插件前,请执行如下命令进行安装。

CREATE EXTENSION orafce;

Orafce插件提供的兼容函数如下表所示。

表 1. Orafce插件兼容函数表
函数描述示例
nvl(anyelement, anyelement)
  • 若第一个参数为null,则会返回第二个参数。
  • 若第一个参数不为null,则返回第一个参数。
说明 两个参数必须是相同类型。
  • SELECT nvl(null,1);

    返回信息如下:

    nvl
    -----
      1
    (1 row)
  • SELECT nvl(0,1);

    返回信息如下:

    nvl
    -----
      0
    (1 row)
  • SELECT nvl(0,null);

    返回信息如下:

    nvl
    -----
      0
    (1 row)
add_months(day date, value int)RETURNS date在第一个月份参数上加上第二个月份参数,返回类型为date。
SELECT add_months(current_date, 2);

返回信息如下:

add_months
------------
2019-08-31
(1 row)
last_day(value date)返回某年某个月份的最后一天,返回类型为date。
SELECT last_day('2018-06-01');

返回信息如下:

 last_day
------------
2018-06-30
(1 row)
next_day(value date, weekday text)
  • 参数一:开始的日期。
  • 参数二:包含星期几的英文字符串,如Friday。
返回开始日期后的第二个星期几的日期,如第二个Friday。
SELECT next_day(current_date, 'FRIDAY');

返回信息如下:

 next_day
------------
2019-07-05
(1 row)
next_day(value date, weekday integer)
  • 参数一:开始的日期。
  • 参数二:星期几的数字,取值为1到7,1为星期日,2为星期一,以此类推。
返回开始日期加天数之后的日期。
  • SELECT next_day('2019-06-22', 1);

    返回信息如下:

     next_day
    ------------
    2019-06-23
    (1 row)
  • 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,结果为负。
  • SELECT months_between('2019-01-01', '2018-11-01');

    返回信息如下:

    months_between
    ----------------
                 2
    (1 row)
  • 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:返回季度的第一天。
  • SELECT TRUNC(current_date,'Q');

    返回信息如下:

      trunc
    ------------
    2019-04-01
    (1 row)
  • SELECT TRUNC(current_date,'Y');

    返回信息如下:

      trunc
    ------------
    2019-01-01
    (1 row)
trunc(value timestamp with time zone)截断timestamp,默认截断时分秒。
SELECT TRUNC('2019-12-11'::timestamp);

返回信息如下:

        trunc
------------------------
2019-12-11 00:00:00+08
(1 row)
trunc(value date)截断日期。
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(日,周等)。
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)默认圆整到天。
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。
  • SELECT round(TO_DATE('27-OCT-00','DD-MON-YY'), 'YEAR');

    返回信息如下:

      round
    ------------
    2001-01-01
    (1 row)
  • SELECT round(TO_DATE('27-FEB-00','DD-MON-YY'), 'YEAR');

    返回信息如下:

      round
    ------------
    2000-01-01
    (1 row)
round(value date)参数类型为date。
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:搜索第几次出现的位置。
  • SELECT instr('Greenplum', 'e',1,2);

    返回信息如下:

    instr
    -------
        4
    (1 row)
  • SELECT instr('Greenplum', 'e',1,1);

    返回信息如下:

    instr
    -------
        3
    (1 row)
instr(str text, patt text, start int)未提供nth参数,默认是第一次出现的位置。
SELECT instr('Greenplum', 'e',1);

返回信息如下:

instr
-------
    3
(1 row)
instr(str text, patt text)未提供start参数,默认从头开始搜索。
SELECT instr('Greenplum', 'e');

返回信息如下:

instr
-------
    3
(1 row)
plvstr.rvrs(str text, start int, end int)str为输入的字符串start;end分别为对字符串从start到end这一段进行逆序。
SELECT plvstr.rvrs('adb4pg', 5,6);

返回信息如下:

reverse
---------
gp
(1 row)
plvstr.rvrs(str text, start int)从start开始到字符串结束进行逆序。
SELECT plvstr.rvrs('adb4pg', 4);

返回信息如下:

reverse
---------
gp4
(1 row)
plvstr.rvrs(str text)逆序整个字符串。
SELECT plvstr.rvrs('adb4pg');

返回信息如下:

reverse
---------
gp4bda
(1 row)
concat(text, text)将两个字符串拼接在一起。
SELECT concat('adb','4pg');

返回信息如下:

concat
--------
adb4pg
(1 row)
concat(text, anyarray)/concat(anyarray, text)/concat(anyarray, anyarray)用于拼接任意类型的数据。
  • SELECT concat('adb4pg', 6666);

    返回信息如下:

      concat
    ------------
    adb4pg6666
    (1 row)
  • SELECT concat(6666, 6666);

    返回信息如下:

     concat
    ----------
    66666666
    (1 row)
  • SELECT concat(current_date, 6666);

    返回信息如下:

        concat
    ----------------
    2019-06-306666
    (1 row)
nanvl(float4, float4)/nanvl(float4, float4)/nanvl(numeric, numeric)如果第一个参数为数值类型,则返回第一个参数;如果不为数值类型,则返回第二参数。
  • SELECT nanvl('NaN', 1.1);

    返回信息如下:

    nanvl
    -------
      1.1
    (1 row)
  • SELECT nanvl('1.2', 1.1);

    返回信息如下:

    nanvl
    -------
      1.2
    (1 row)
bitand(bigint, bigint)将两个整型的二进制进行and操作,并返回and之后的结果,只输出一行。
  • SELECT bitand(1,3);

    返回信息如下:

    bitand
    --------
         1
    (1 row)
  • SELECT bitand(2,6);

    返回信息如下:

    bitand
    --------
         2
    (1 row)
  • SELECT bitand(4,6);

    返回信息如下:

    bitand
    --------
         4
    (1 row)
listagg(text)将文本值聚集成一个串。
SELECT listagg(t) FROM (VALUES('abc'), ('def')) as l(t);

返回信息如下:

listagg
---------
abcdef
(1 row)
listagg(text, text)将文本值聚集成一个串,第二个参数执行了分割符。
SELECT listagg(t, '.') FROM (VALUES('abc'), ('def')) as l(t);

返回信息如下:

listagg
---------
abc.def
(1 row)
nvl2(anyelement, anyelement, anyelement)如果第一个参数不为null,那么返回第二个参数,如果第一个参数为null,则返回第三个参数。
  • SELECT nvl2(null, 1, 2);

    返回信息如下:

    nvl2
    ------
       2
    (1 row)
  • SELECT nvl2(0, 1, 2);

    返回信息如下:

    nvl2
    ------
       1
    (1 row)
lnnvl(bool)如果参数为null或者false,则返回true;如果为true,则返回false。
  • SELECT lnnvl(null);

    返回信息如下:

    lnnvl
    -------
    t
    (1 row)
  • SELECT lnnvl(false);

    返回信息如下:

    lnnvl
    -------
    t
    (1 row)
  • SELECT lnnvl(true);

    返回信息如下:

    lnnvl
    -------
    f
    (1 row)
dump("any")返回一个文本值,该文本中包含第一个参数的数据类型代码、以字节计的长度和内部表示。
SELECT  dump('adb4pg');

返回信息如下:

                dump
---------------------------------------
Typ=705 Len=7: 97,100,98,52,112,103,0
(1 row)
dump("any", integer)第二个参数表示返回文本值的内部表示是使用10进制还是16进制,目前仅支持10进制和16进制。
  • SELECT dump('adb4pg', 10);

    返回信息如下:

                    dump
    ---------------------------------------
    Typ=705 Len=7: 97,100,98,52,112,103,0
    (1 row)
  • SELECT dump('adb4pg', 16);

    返回信息如下:

                   dump
    ------------------------------------
    Typ=705 Len=7: 61,64,62,34,70,67,0
    (1 row)
  • 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');
  • SELECT * FROM t1 ORDER BY nlssort(name, 'en_US.UTF-8');

    返回信息如下:

    name
    ------
    anne
    Anne
    bob
    Bob
    (4 row)
  • SELECT * FROM t1 ORDER BY nlssort(name, 'C');

    返回信息如下:

    name
    ------
    Anne
    Bob
    anne
    bob
    (4 row)
substr(str text, start int)获取参数一中字符串的子串。参数二表示获取到子串的起始位置 >= start。
  • SELECT substr('adb4pg', 1);

    返回信息如下:

    substr
    --------
    adb4pg
    (1 row)
  • SELECT substr('adb4pg', 4);

    返回信息如下:

    substr
    --------
    4pg
    (1 row)
substr(str text, start int, len int)参数三会指定子串的结束位置 >= start and <= end
SELECT substr('adb4pg', 5,6);

返回信息如下:

substr
--------
pg
(1 row)
pg_catalog.substrb(varchar2, integer, integer)varchar2类型的获取子串函数;参数二为start pos,参数三为end pos。
SELECT  substr('adb4pg'::varchar2, 5,6);

返回信息如下:


substr
--------
pg
(1 row)
pg_catalog.substrb(varchar2, integer)varchar2类型的获取子串函数;参数二为start pos,从start pos一直取到字符串结束。
SELECT substr('adb4pg'::varchar2, 4) ;

返回信息如下:

substr
--------
4pg
(1 row)
pg_catalog.lengthb(varchar2)获取varchar2类型字符串占的字节数。若输入为null返回null,输入为空字符,则返回0。
  • SELECT lengthb('adb4pg'::varchar2) ;

    返回信息如下:

    lengthb
    ---------
          6
    (1 row)
  • SELECT lengthb('分析型'::varchar2) ;

    返回信息如下:

    lengthb
    ---------
          9
    (1 row)

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

对于以下的四个Oracle函数,在AnalyticDB PostgreSQL中,无需安装Orafce插件就可以提供兼容支持。

函数描述示例
sinh(float)双曲正弦值。
SELECT sinh(0.1);

返回信息如下:

      sinh
-------------------
0.100166750019844
(1 row)
tanh(float)双曲正切值。
SELECT  tanh(3);

返回信息如下:

      tanh
------------------
0.99505475368673
(1 row)
cosh(float)双曲余弦值。
SELECT cosh(0.2);

返回信息如下:

      cosh
------------------
1.02006675561908
(1 row)
decode(expression, value, return [,value,return]... [, default])在表达式中寻找一个搜索值,搜索到则返回指定的值。如果没有搜索到,返回默认值。

创建表并插入数据:

CREATE TABLE t1(id int, name varchar(20));
INSERT INTO t1 values(1,'alibaba');
INSERT INTO t1 values(2,'adb4pg');
  • SELECT decode(id, 1, 'alibaba', 2, 'adb4pg', 'not found') from t1;

    返回信息如下:

     case
    ---------
    alibaba
    adb4pg
    (2 rows)
  • SELECT decode(id, 3, 'alibaba', 4, 'adb4pg', 'not found') from t1;

    返回信息如下:

      case
    -----------
    not found
    not found
    (2 rows)

数据类型转换对照表

OracleAnalyticDB PostgreSQL
VARCHAR2varchar or text
DATEtimestamp
LONGtext
LONG RAWbytea
CLOBtext
NCLOBtext
BLOBbytea
RAWbytea
ROWIDoid
FLOATdouble precision
DECdecimal
DECIMALdecimal
DOUBLE PRECISIONdouble precision
INTint
INTEGERinteger
REALreal
SMALLINTsmallint
NUMBERnumeric
BINARY_FLOATdouble precision
BINARY_DOUBLEdouble precision
TIMESTAMPtimestamp
XMLTYPExml
BINARY_INTEGERinteger
PLS_INTEGERinteger
TIMESTAMP WITH TIME ZONEtimestamp with time zone
TIMESTAMP WITH LOCAL TIME ZONEtimestamp with time zone

系统函数转换对照表

OracleAnalyticDB PostgreSQL
sysdatecurrent timestamp
trunctrunc或date trunc
dbms_output.put_lineraise语句
decode转成case when或直接使用decode
NVLcoalesce

PL/SQL迁移指导

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

Package

PL/PGSQL不支持Package,需要把Package转换成schema,同时Package里面的所有procedure和function也需要转换成AnalyticDB PostgreSQL的function。

例如:

CREATE OR REPLACE PACKAGE pkg IS 

…

END;

转换成:

CREATE SCHEMA pkg;
  • Package定义的变量

    Procedure/Function的局部变量保持不变,全局变量在AnalyticDB PostgreSQL中可以使用临时表进行保存。

  • Package初始化块

    请删除,若无法删除请使用Function封装,在需要的时候主动调用该Function。

  • Package内定义的Procedure/Function

    Package内定义的Procedure和Function需要转成AnalyticDB PostgreSQL的Function,并把Function定义到Package对应的Schema内。

    例如,有一个Package名为pkg中有如下函数:

    FUNCTION test_func (args int) RETURN int is
    var number := 10;
    BEGIN
    … …
    END;

    转换成如下AnalyticDB PostgreSQL的Function:

    CREATE OR REPLACE FUNCTION pkg. test_func(args int) RETURNS int AS
    $$
    
      … …
    
    $$
     LANGUAGE plpgsql;

Procedure/Function

Oracle中的Procedure和Function,不论属于Package的还是属于全局,都需要转换成AnalyticDB PostgreSQL的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转换的注意事项有以下几点:

  • RETURN关键字转成RETURNS。
  • 函数体使用&dollar;\$ ... &dollar;\$进行封装。
  • 函数语言声明。
  • Subprocedure需要转换成AnalyticDB PostgreSQL的Function。

PL statement

  • FOR语句:
    PL/SQL和PL/PGSQL在带有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;
  • PRAGMA语句

    AnalyticDB PostgreSQL中没有PRAGMA语句,删除该类语句。

  • 事务处理

    AnalyticDB PostgreSQL的Function内部无法使用事务控制语句,如begin,commit,rollback等。

    修改方法如下:

    • 删除函数体内的事务控制语句,把事务控制放在函数体外。
    • 把函数按照commit或rollback拆分成多个。
  • EXECUTE语句

    AnalyticDB PostgreSQL支持类似Oracle的动态SQL语句,不同之处如下:

    • 不支持using语法,可通过把参数拼接到SQL串中来解决。
    • 数据库标识符使用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);
  • Pipe row

    Pipe row函数,可使用AnalyticDB PostgreSQL的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);
  • 异常处理
    • 使用raise抛出异常。
    • Catch异常后,不能rollback事务,只能在udf外做rollback。
    • AnalyticDB PostgreSQL支持的Error,请参见PostgreSQL官网
  • Function中同时有Return和OUT参数

    AnalyticDB PostgreSQL中,Function无法同时存在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中取对应字段的返回值即可。

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

    在如下示例中,变量param2是一个字符串类型。假设param2的值为adb'-'pg。下面的sql_str直接放到AnalyticDB PostgreSQL中使用会将-识别成一个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';
  • 获取两个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;

PL数据类型

  • Record

    使用AnalyticDB PostgreSQL的复合数据类型替换。

    示例:

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

    转换成:

    CREATE TYPE rec AS (a int, b int);
  • Nest table
    • Nest table作为PL变量,可以使用AnalyticDB PostgreSQL的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();
    • 作为Function返回值,可以使用Table Function替换。
  • Associative Array

    无替换类型。

  • Variable-Size Arrays

    与Nest table类似,使用array类型替换。

  • Global variables

    目前AnalyticDB PostgreSQL不支持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为这个表的分布列,由于AnalyticDB PostgreSQL中不允许对于分布列的修改,需要加一个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;

SQL

  • Connect by

    Oracle层次查询,AnalyticDB PostgreSQL没有等价替换的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;
  • Rownum
    1. 限定查询结果集大小,可以使用limit替换。

      示例:

      SELECT * FROM t WHERE rownum < 10;

      转换成:

      SELECT * FROM t LIMIT 10;
    2. 使用row_number() over()生成rownum。

      示例:

      SELECT rownum, * FROM t;

      转换成:

      SELECT row_number() over() AS rownum, * FROM t;
  • Dual表
    1. 去掉Dual。

      示例:

      SELECT sysdate FROM dual;

      转换成:

      SELECT current_timestamp;
    2. 创建一个叫dual的表。
  • Select中的udf

    AnalyticDB PostgreSQL支持在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;
  • (+)多表外链接

    AnalyticDB PostgreSQL不支持(+)语法形式,需要转换成标准的Outer Join语法。

    示例:

    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) ADN 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);
  • Merge Into

    通常情况下Merge Into语法可以通过INSERT ON CONFLICT语法进行适配,但是Merge Into语法的部分功能INSERT ON CONFLICT语法暂时无法实现,需要使用存储过程解决。

    INSERT ON CONFLICT语法更多介绍,请参见使用INSERT ON CONFLICT覆盖写入数据

    存储过程的更多介绍,请参见存储过程

  • Sequence

    示例:

    CREATE SEQUENCE seq1;
    SELECT seq1.nextval FROM dual;

    转换成:

    CREATE SEQUENCE seq1;
    SELECT nextval('seq1');
  • 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。但是在AnalyticDB PostgreSQL中则无法重发重复打开,需要改写成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;