Oracle應用遷移到AnalyticDB for PostgreSQL指導

AnalyticDB for PostgreSQL(簡稱:ADB for PG)對Oracle語法有着較好的兼容,本文介紹如何將Oracle應用遷移到AnalyticDB for PostgreSQL。html

1 PL/SQL

PL/SQL(Procedural Language/SQL)是一種過程化的SQL語言,是Oracle對SQL語句的拓展,使得SQL的使用能夠具備通常編程語言的特色,所以,能夠用來實現複雜的業務邏輯。PL/SQL對應了ADB for PG中的PL/PGSQLsql

1.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定義的變量oracle

    procedure/function的局部變量保持不變,全局變量在ADB for PG中可使用臨時表進行保存。詳見1.4.5節。
  2. Package初始化塊app

    若是能夠刪掉,就刪掉,刪不掉的話,可使用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;

1.2 Procedure/function

對於oracle的procedure和function,不管是package的仍是全局的,都轉換成adb for pg 的function。
例如:ide

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轉換的關鍵點:oop

  1. RETURN 關鍵字轉成RETURNS
  2. 函數體使用$$ ... $$封裝起來
  3. 函數語言聲明
  4. Subprocedure須要轉換成ADB for PG的function

1.3 PL statement

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

1.3.2 PRAGMA語句

ADB for PG 無PRAGMA語句,刪除。

1.3.3 事務處理

ADB for PG 的function 內部沒法使用事務控制語句,如begin,commit,rollback等。
修改方法:

  1. 刪除函數體內的事務控制語句,把事務控制放在函數體外;
  2. 把函數按照commit/rollback 拆分紅多個。

1.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);

1.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);

1.3.6 異常處理

  1. 使用raise拋出異常
  2. Catch異常後,不能rollback事務,只能在udf外作rollback
  3. ADB for PG支持的error,能夠參考: https://www.postgresql.org/docs/8.3/errcodes-appendix.html

1.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中取對應字段的返回值便可。

1.4 PL數據類型

1.4.1 Record

使用ADB for PG的複合數據類型替換
示例:

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

改寫成:

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

1.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節。

1.4.3 Associative Array

無替換類型。

1.4.4 Variable-Size Arrays

與nest table 同樣,使用array類型替換。

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

1.5 SQL

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

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

1.5.3 Dual表

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

轉換成:

select current_timestamp;
  1. 建立一個叫dual的表。

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

1.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);

1.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);

2 系統函數轉換對照表

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

3 數據類型轉換對照表

oracle ADB for PG
sysdate current timestamp
trunc trunc/ date trunc
dbms_output.put_line raise 語句
decode 轉成case when/直接使用decode
NVL coalesce
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



本文做者:陸封

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索