AnalyticDB for PostgreSQL は Oracle 構文と互換性があります。本ページでは、Oracle アプリケーションを AnalyticDB for PostgreSQL に移行する際に必要なすべての手順について説明します。具体的には、Ora2Pg を用いた移行範囲の評価方法、互換性モードの設定方法、Orafce 拡張機能を活用した Oracle 互換関数の利用方法、および PL/SQL 構文を PL/pgSQL に変換する方法について解説します。
主な違い一覧
具体的な変換ルールに着手する前に、以下の高レベルの違いを確認してください。これらは最も一般的な移行課題に影響を与えます。
| エリア | Oracle の動作 | AnalyticDB for PostgreSQL の動作 |
|---|---|---|
NULL の連結 |
'abc' || NULL を返します 'abc' |
NULL を返します(PostgreSQL のデフォルト動作)。互換性モードを有効化することで動作を変更できます |
ROWNUM |
疑似列 | LIMIT(行数制限)または row_number() over()(行番号付与)を使用します |
DUAL テーブル |
一部のクエリで必須となる仮想テーブル | FROM DUAL を削除するか、dual テーブルを作成します |
CONNECT BY |
ネイティブな階層クエリ | 直接対応する構文はありません。PL/pgSQL 関数として再記述します |
| パッケージ | CREATE PACKAGE |
パッケージはサポートされていません。スキーマ+関数へ変換します |
PRAGMA |
コンパイル指示子 | サポートされていません。すべての PRAGMA 文を削除します |
| 関数内でのトランザクション制御 | BEGIN、COMMIT、ROLLBACK を関数本体内で使用可能 |
サポートされていません。トランザクション制御は関数外で実行します |
OUTER JOIN (+) |
WHERE a.id = b.id(+) |
標準の LEFT JOIN/RIGHT JOIN 構文を使用します |
| FOR LOOP REVERSE | 2 番目の数値から 1 番目の数値までカウントダウン | 1 番目の数値から 2 番目の数値までカウントダウン。境界値を入れ替えます |
RETURN + OUT 引数を備えた関数 |
両方を同時に使用可能 | サポートされていません。RETURN を追加の OUT 引数に変換します |
| 連想配列 | サポートされています | 対応する機能はありません。設計の見直しが必要です |
SELECT |
SQL ステートメントを含めることができます | SQL ステートメントを含めることはできません。サブクエリとして再記述します |
Ora2Pg を用いたスキーマ変換の自動化
Ora2Pg は、Oracle のテーブル、ビュー、パッケージに関する DDL ステートメントを PostgreSQL 互換構文に変換するオープンソースツールです。
まず移行範囲を評価
完全な変換を実行する前に、どのオブジェクトが自動変換できないかを特定するための移行評価レポートを生成します。
ora2pg -t SHOW_REPORT
このレポートには、すべてのデータベースオブジェクト、その数、および手動修正が必要なオブジェクトが一覧表示されます。移行を開始する前に、このレポートをもとに作業範囲を見積もります。
Ora2Pg が自動処理する項目
-
テーブルおよびビューの DDL
-
パッケージ構造(スキーマへ変換)
-
基本的な PL/SQL から PL/pgSQL への構文変換
手動修正が必要な項目
Ora2Pg の実行後、以下の問題点を確認・修正し、ご利用のインスタンスに対してスクリプトを実行する前に検証を行ってください。
-
変換されたスクリプトが、ご利用のインスタンスのマイナーエンジンバージョンよりも新しい PostgreSQL 構文バージョンをターゲットとしている可能性があります
-
複雑な PL/SQL 構文については、変換ルールが不完全または不正確である可能性があります
-
以下「PL/SQL 変換」セクションに記載されているすべての構文
互換性モードの設定
adb_compatibility_mode パラメーターは、データベース間で動作が異なる構文を AnalyticDB for PostgreSQL がどのように処理するかを制御します。
| パラメーター | 有効な値 | デフォルト値 |
|---|---|---|
adb_compatibility_mode |
postgres、oracle |
postgres |
変更を行う前に、現在の値を確認します。
SHOW adb_compatibility_mode;
インスタンスレベルでこのパラメーターを変更するには、チケットを起票してください。チケットを起票
NULL を含む文字列連結
'abc' || NULL の動作は、互換性モードによって異なります。
-
PostgreSQL モード(デフォルト):
NULLを返します -
Oracle モード:
'abc'を返します(Oracle ではNULLが空文字列として扱われます)
Oracle 互換モードで文字列連結を使用する前に、Laser エンジンを無効化します。
SET laser.enable = off;
定数文字列の型推論
AnalyticDB for PostgreSQL では、CREATE TABLE AS SELECT などのステートメントにおいて、定数文字列が自動的に TEXT 型(UNKNOWN ではなく)として認識されます。この動作は、互換性モードの設定を必要としません。
Orafce 拡張機能の利用
Orafce 拡張機能は、AnalyticDB for PostgreSQL で Oracle 互換関数を変更や変換なしに利用できるようにします。
Orafce はデータベースごとに 1 回インストールします。
CREATE EXTENSION orafce;
Orafce は、Oracle の VARCHAR2 データの型もサポートします。
日時関数
| 関数 | 説明 | 例 |
|---|---|---|
add_months(day date, value int) |
日付に指定月数を加算します | SELECT add_months(current_date, 2); → 2019-08-31 |
last_day(value date) |
当月の最終日を返します | SELECT last_day('2018-06-01'); → 2018-06-30 |
next_day(value date, weekday text) |
次の平日を返します | SELECT next_day(current_date, 'FRIDAY'); |
next_day(value date, weekday integer) |
曜日を整数で指定:1=日曜日、2=月曜日、…、7=土曜日 | SELECT next_day('2019-06-22', 1); → 2019-06-23 |
months_between(date1 date, date2 date) |
2 つの日付間の月数を返します(date1 > date2 の場合、正の値) | SELECT months_between('2019-01-01', '2018-11-01'); → 2 |
タイムスタンプの切り捨ておよび丸め
| 関数 | 説明 | 例 |
|---|---|---|
trunc(value timestamp with time zone, fmt text) |
年(Y)、四半期(Q)、月、日、週、時、分、秒単位で切り捨てます |
SELECT TRUNC(current_date, 'Q'); → 2019-04-01 |
trunc(value timestamp with time zone) |
時、分、秒を切り捨てます | SELECT TRUNC('2019-12-11'::timestamp); → 2019-12-11 00:00:00+08 |
trunc(value date) |
日付を切り捨てます | SELECT TRUNC('2019-12-11'::timestamp, 'Y'); → 2019-01-01 00:00:00+08 |
round(value timestamp with time zone, fmt text) |
指定単位(週、日など)で最も近い値に丸めます | SELECT round('2018-10-06 13:11:11'::timestamp, 'YEAR'); → 2019-01-01 00:00:00+08 |
round(value timestamp with time zone) |
最も近い日に丸めます | SELECT round('2018-10-06 13:11:11'::timestamp); → 2018-10-07 00:00:00+08 |
round(value date, fmt text) |
丸められた日付を返します | SELECT round(TO_DATE('27-OCT-00','DD-MON-YY'), 'YEAR'); → 2001-01-01 |
round(value date) |
丸められた日付を返します | SELECT round(TO_DATE('27-FEB-00','DD-MON-YY')); → 2000-02-27 |
NULL 処理関数
| 関数 | 説明 | 例 |
|---|---|---|
nvl(anyelement, anyelement) |
第 1 引数が NULL の場合、第 2 引数を返します。それ以外の場合は第 1 引数を返します。両引数は同じ型である必要があります。 | SELECT nvl(null, 1); → 1 |
nvl2(anyelement, anyelement, anyelement) |
第 1 引数が NULL の場合、第 3 引数を返します。それ以外の場合は第 2 引数を返します | SELECT nvl2(null, 1, 2); → 2 |
lnnvl(bool) |
引数が NULL または false の場合、true を返します。引数が true の場合、false を返します |
SELECT lnnvl(null); → t |
nanvl(float4, float4)/nanvl(numeric, numeric) |
第 1 引数が NaN の場合、第 2 引数を返します。それ以外の場合は第 1 引数を返します |
SELECT nanvl('NaN', 1.1); → 1.1 |
文字列関数
| 関数 | 説明 |
|---|---|
instr(str text, patt text, start int, nth int) |
文字列 str 内のパターン patt の n 番目の出現位置を、start から開始して返します。見つからない場合は 0 を返します。 |
instr(str text, patt text, start int) |
start |
instr(str text, patt text) |
文字列の先頭から検索します |
substr(str text, start int) |
start |
substr(str text, start int, len int) |
部分文字列を返します。len は start 以上かつ文字列長以下である必要があります |
pg_catalog.substrb(varchar2, integer, integer) |
VARCHAR2 文字列の指定位置から終了位置までの部分文字列を返します |
pg_catalog.substrb(varchar2, integer) |
VARCHAR2 文字列の指定位置から末尾までの部分文字列を返します |
pg_catalog.lengthb(varchar2) |
VARCHAR2 文字列のバイト数を返します。入力が NULL の場合は NULL、空文字列の場合は 0 を返します |
lpad(string char, length int, fill char) |
文字列を指定長に左詰めします。 説明
PostgreSQL では |
lpad(string char, length int) |
スペースで左詰めします |
concat(text, text) |
2 つの文字列を結合します |
concat(text/anyarray, text/anyarray) |
同一または異なる型の値を結合します |
nlssort(text, text) |
ロケール固有の順序(例:'en_US.UTF-8'、'C')でデータを並べ替えます |
plvstr.rvrs(str text, start int, end int) |
start から end |
plvstr.rvrs(str text, start int) |
start から文字列末尾までの文字を反転します |
plvstr.rvrs(str text) |
文字列全体を反転します |
集計関数
| 関数 | 説明 | 例 |
|---|---|---|
listagg(text) |
値を 1 つの文字列に連結します | SELECT listagg(t) FROM (VALUES('abc'), ('def')) as l(t); → abcdef |
listagg(text, text) |
区切り文字を指定して連結します | SELECT listagg(t, '.') FROM (VALUES('abc'), ('def')) as l(t); → abc.def |
bitand(bigint, bigint) |
2 つの整数値に対するビット演算 AND を実行します | SELECT bitand(2, 6); → 2 |
診断関数
| 関数 | 説明 | 例 |
|---|---|---|
dump("any") |
型コード、バイト長、内部表現を返します | SELECT dump('adb4pg'); → Typ=705 Len=7: 97,100,98,52,112,103,0 |
dump("any", integer) |
出力形式を指定します:10(10 進数)、16(16 進数)。2 を指定するとエラーになります。 |
SELECT dump('adb4pg', 16); |
正規表現関数
以下のすべての正規表現関数は、flags 引数を受け入れます。有効な値は次のとおりです:'i'(大文字小文字を区別しない)、'c'(大文字小文字を区別する)、'n'(. が改行にもマッチ)、'm'(マルチライン)、'x'(空白を無視)。
| 関数 | 説明 |
|---|---|
regexp_count(string, pattern) |
pattern の出現回数を返します。一致しない場合は 0 を返します |
regexp_count(string, pattern, startPos) |
startPos |
regexp_count(string, pattern, startPos, flags) |
カスタムマッチングフラグでカウントします |
regexp_instr(string, pattern) |
パターンの開始位置を返します。一致しない場合は 0 を返します |
regexp_instr(string, pattern, startPos, occurrence, return_opt, flags, group) |
完全なシグネチャ:return_opt=0 は一致の開始位置を返し、return_opt=1 は一致後の位置を返します。group はキャプチャグループを指定します(0 = 全体一致) |
regexp_like(string, pattern) |
任意の部分文字列がパターンに一致する場合、true を返します |
regexp_like(string, pattern, flags) |
カスタムマッチングフラグで true を返します |
regexp_substr(string, pattern) |
最初に一致した部分文字列を返します |
regexp_substr(string, pattern, startPos, occurrence, flags) |
startPos |
Orafce を使用せずに利用可能な関数
以下の Oracle 互換関数は、Orafce のインストールを必要とせず、AnalyticDB for PostgreSQL でネイティブに利用可能です。
| 関数 | 説明 | 例 |
|---|---|---|
sinh(float) |
双曲線正弦 | SELECT sinh(0.1); → 0.100166750019844 |
tanh(float) |
双曲線正接 | SELECT tanh(3); → 0.99505475368673 |
cosh(float) |
双曲線余弦 | SELECT cosh(0.2); → 1.02006675561908 |
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');
-- decode:id=1 の場合 'alibaba'、id=2 の場合 'adb4pg' を返します
SELECT decode(id, 1, 'alibaba', 2, 'adb4pg', 'not found') FROM t1;
データ型のマッピング
| Oracle の型 | AnalyticDB for PostgreSQL の型 |
|---|---|
VARCHAR2 |
varchar または 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 |
INTEGER |
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 |
関数のマッピング
| Oracle 関数 | AnalyticDB for PostgreSQL における同等の関数 |
|---|---|
sysdate |
current_timestamp |
trunc |
trunc または date_trunc |
dbms_output.put_line |
RAISE ステートメント |
decode |
CASE WHEN または decode |
NVL |
coalesce |
PL/SQL におけるデータの変換
PL/SQL(Procedural Language/SQL)は、AnalyticDB for PostgreSQL では PL/pgSQL に対応します。以下では、各 PL/SQL 構文の変換ルールについて説明します。
パッケージ
PL/pgSQL ではパッケージはサポートされていません。各パッケージをスキーマに変換し、パッケージ内のすべてのプロシージャおよび関数を、そのスキーマ内のスタンドアロン関数に変換します。
Oracle:
CREATE OR REPLACE PACKAGE pkg IS
...
END;
AnalyticDB for PostgreSQL:
-- パッケージはスキーマになります
CREATE SCHEMA pkg;
パッケージ内容の変換ルール:
-
プロシージャおよび関数内のローカル変数:変更は不要です
-
グローバル変数:一時テーブルに格納します(下記「グローバル変数」を参照)
-
パッケージ初期化ブロック:削除します。初期化ロジックを削除できない場合は、関数にカプセル化し、必要に応じて明示的に呼び出します。
-
プロシージャおよび関数:対応するスキーマ内の関数に変換します。各関数にはスキーマ名をプレフィックスとして付与します。
例 — パッケージ内の関数:
Oracle:
FUNCTION test_func (args int) RETURN int is
var number := 10;
BEGIN
...
END;
AnalyticDB for PostgreSQL:
-- RETURN → RETURNS;IS/AS → AS $$...$$;LANGUAGE 句を追加
CREATE OR REPLACE FUNCTION pkg.test_func(args int) RETURNS int AS
$$
...
$$
LANGUAGE plpgsql;
プロシージャおよび関数
Oracle のプロシージャおよび関数を PL/pgSQL 関数に変換します。主な変更点は以下のとおりです。
-
関数シグネチャ内の
RETURN→RETURNS -
関数本体を
$$...$$で囲みます -
LANGUAGE plpgsql句を追加します -
サブプロシージャをスタンドアロン関数に変換します
例:
Oracle:
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;
AnalyticDB for PostgreSQL:
-- 変更点:varchar2 → varchar;RETURN → RETURNS;IS → AS;本体を $$...$$ で囲みます
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;
PL ステートメント
REVERSE を伴う FOR LOOP
PL/SQL と PL/pgSQL では、REVERSE キーワードの動作が異なります。
-
PL/SQL:
FOR i IN REVERSE 1..3は 3 から 1 へカウントダウンします -
PL/pgSQL:
FOR i IN REVERSE 1..3は 1 から 3 へカウントダウンします(逆方向)
変換時にループ境界値を入れ替えます。
Oracle:
FOR i IN REVERSE 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(i));
END LOOP;
AnalyticDB for PostgreSQL:
-- 境界値を入れ替え:1..3 → 3..1;DBMS_OUTPUT.PUT_LINE → RAISE
FOR i IN REVERSE 3..1 LOOP
RAISE '%', i;
END LOOP;
PRAGMA 文
PRAGMA 文はサポートされていません。すべての PRAGMA 文を削除します。
トランザクション制御
AnalyticDB for PostgreSQL の関数では、関数本体内で BEGIN、COMMIT、ROLLBACK を使用できません。以下のいずれかの戦略を適用します。
-
関数本体からトランザクション制御文を削除し、関数呼び出しの外側で実行します。
-
COMMITまたはROLLBACKの境界ごとに、関数を個別の関数に分割します。
EXECUTE(動的 SQL)
AnalyticDB for PostgreSQL では動的 SQL がサポートされますが、Oracle との違いがあります。
-
USING構文はサポートされていません。パラメーターを SQL 文字列に直接連結します。 -
データベース識別子は
quote_identで、文字列値はquote_literalで囲みます。
Oracle:
EXECUTE 'UPDATE employees_temp SET commission_pct = :x' USING a_null;
AnalyticDB for PostgreSQL:
-- USING を quote_literal を使った文字列連結に置き換え
EXECUTE 'UPDATE employees_temp SET commission_pct = ' || quote_literal(a_null);
PIPE ROW 関数
PIPE ROW 関数は、RETURNS SETOF <type> および RETURN NEXT を使用したテーブル関数に置き換えます。
Oracle:
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);
AnalyticDB for PostgreSQL:
-- RECORD 型 → CREATE TYPE;PIPE ROW → RETURN NEXT;PIPELINED → RETURNS SETOF
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ステートメントを使用します。 -
例外がキャッチされた後、関数内ではトランザクションをロールバックできません。ロールバックはユーザー定義関数の外側でのみ許可されます。
サポートされるエラーコードについては、「PostgreSQL エラーコード リファレンス」をご参照ください。
RETURN および OUT 引数を備えた関数
関数は、RETURN 引数と OUT 引数を同時に使用できません。RETURN 引数を追加の OUT 引数に変換し、SELECT * FROM test_func(...) INTO rec を使用して戻り値を取得します。
Oracle:
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;
AnalyticDB for PostgreSQL:
-- RETURN 値を OUT 引数(out_name)に変換
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;
動的 SQL 文字列連結におけるシングルクォート
文字列変数にシングルクォートが含まれている場合(例:adb'-'pg)、直接連結すると、ハイフンが演算子と解釈されて構文解析エラーが発生します。quote_literal を使用して値を安全に埋め込みます。
Oracle:
sql_str := 'SELECT * FROM test1 WHERE col1 = ' || param1 || ' AND col2 = ''' || param2 || ''' AND col3 = 3';
AnalyticDB for PostgreSQL:
-- quote_literal により param2 が安全にラップされ、埋め込まれたクォートが処理されます
sql_str := 'SELECT * FROM test1 WHERE col1 = ' || param1 || ' AND col2 = ' || quote_literal(param2) || ' AND col3 = 3';
2 つのタイムスタンプ間の日数
Oracle:
SELECT to_date('2019-06-30 16:16:16') - to_date('2019-06-29 15:15:15') + 1 INTO v_days FROM dual;
AnalyticDB for PostgreSQL:
-- extract() を使用して間隔から整数日数を取得
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 型
Oracle の RECORD 型は、複合型に変換します。
Oracle:
TYPE rec IS RECORD (a int, b int);
AnalyticDB for PostgreSQL:
CREATE TYPE rec AS (a int, b int);
ネストテーブルおよび可変サイズ配列
PL 変数として、NESTED TABLE および VARRAY は、PostgreSQL の ARRAY 型に対応します。
Oracle:
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;
AnalyticDB for PostgreSQL:
-- TABLE OF → varchar[];配列インデックス:names(i) → names[i];FIRST..LAST → 1..array_length
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 f1();
ネストテーブルが関数の戻り値として使用される場合は、テーブル関数(RETURNS SETOF <type>)を使用します。
連想配列
AnalyticDB for PostgreSQL には Oracle の連想配列に対応する機能はありません。この構文は設計の見直しが必要です。
グローバル変数
AnalyticDB for PostgreSQL ではグローバル変数はサポートされていません。パッケージレベルのグローバル変数は一時テーブルに格納し、アクセサ関数を定義します。
-- グローバル変数を一時テーブルに格納
-- 注:id 列は分散キーであり、変更できません
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;
グローバル変数を変更するには:
-- 呼び出し元関数内で tmp_rec レコードを宣言
SELECT * FROM set_variable('g_err_code', 'error'::varchar) INTO tmp_rec;
グローバル変数を読み取るには:
SELECT * FROM get_variable() INTO tmp_rec;
error_code := tmp_rec.g_err_code;
SQL 変換
CONNECT BY
階層クエリのための CONNECT BY 句は、AnalyticDB for PostgreSQL には直接対応する SQL 構文がありません。階層を反復的に走査する PL/pgSQL 関数として再記述します。
Oracle:
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;
AnalyticDB for PostgreSQL(反復的走査関数):
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;
-- nocycle が false の場合、既に走査済みの行を削除
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;
-- 現在の結果を result および prev テーブルに昇格
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;
FOR rec IN EXECUTE 'SELECT * FROM ' || res_tbl LOOP
RETURN NEXT rec;
END LOOP;
RETURN;
END
$$
LANGUAGE plpgsql;
ROWNUM
結果件数の制限:
Oracle:
SELECT * FROM t WHERE rownum < 10;
AnalyticDB for PostgreSQL:
SELECT * FROM t LIMIT 10;
行番号の生成:
Oracle:
SELECT rownum, * FROM t;
AnalyticDB for PostgreSQL:
SELECT row_number() OVER() AS rownum, * FROM t;
DUAL テーブル
オプション 1 — `FROM DUAL` を削除:
Oracle:
SELECT sysdate FROM dual;
AnalyticDB for PostgreSQL:
SELECT current_timestamp;
オプション 2 — `dual` テーブルを作成:
CREATE TABLE dual (dummy varchar(1));
INSERT INTO dual VALUES ('X');
SELECT ステートメント内のユーザー定義関数
AnalyticDB for PostgreSQL では、SQL ステートメントを含むユーザー定義関数を SELECT 内で呼び出すことはできません。実行するとセグメントエラーが発生します。
ERROR: function cannot execute on segment because it accesses relation "public.t2"
このような関数は、SQL 式またはサブクエリに変換します。
Oracle:
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;
AnalyticDB for PostgreSQL:
-- 関数ロジックを JOIN としてインライン化
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.b = t2.a;
OUTER JOIN (+)
2 テーブルの外部結合:
Oracle:
SELECT * FROM a, b WHERE a.id = b.id(+);
AnalyticDB for PostgreSQL:
SELECT * FROM a LEFT JOIN b ON a.id = b.id;
3 テーブルの外部結合:
まず 2 つのテーブルを CTE(共通テーブル式)で結合し、その後 RIGHT OUTER JOIN と coalesce を適用します。
Oracle:
SELECT * FROM test1 t1, test2 t2, test3 t3
WHERE t1.col1(+) BETWEEN NVL(t2.col1, t3.col1) AND NVL(t3.col1, t2.col1);
AnalyticDB for PostgreSQL:
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 に置き換えます。INSERT ON CONFLICT で対応できない場合は、ストアドプロシージャを使用します。
シーケンス
Oracle:
CREATE SEQUENCE seq1;
SELECT seq1.nextval FROM dual;
AnalyticDB for PostgreSQL:
CREATE SEQUENCE seq1;
SELECT nextval('seq1');
カーソル
基本的なカーソル走査 はサポートされています。OPEN/FETCH/CLOSE パターンを使用します。
Oracle:
FUNCTION test_func() IS
CURSOR data_cursor IS SELECT * FROM test1;
BEGIN
FOR i IN data_cursor LOOP
-- process i
END LOOP;
END;
AnalyticDB for PostgreSQL:
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;
-- process i
END LOOP;
CLOSE data_cursor;
END;
$body$
LANGUAGE PLPGSQL;
再帰関数内での同名カーソル はサポートされていません。代わりに FOR i IN <query> を使用します。
Oracle:
FUNCTION test_func(level IN number) IS
CURSOR data_cursor IS SELECT * FROM test1;
BEGIN
IF level > 5 THEN RETURN; END IF;
FOR i IN data_cursor LOOP
-- process i
test_func(level + 1);
END LOOP;
END;
AnalyticDB for PostgreSQL:
-- 同名カーソルを削除;再帰をサポートするためにインラインクエリを使用
CREATE OR REPLACE FUNCTION test_func(level int) RETURNS void
AS $body$
DECLARE
i record;
BEGIN
IF level > 5 THEN
RETURN;
END IF;
FOR i IN SELECT * FROM test1 LOOP
-- process i
PERFORM test_func(level + 1);
END LOOP;
END;
$body$
LANGUAGE PLPGSQL;
よくある落とし穴
これらのランタイム動作の違いは、移行後のバグの最も頻繁な原因となります。変換済みコードのテストを実施する前に、必ず確認してください。
| 落とし穴 | Oracle の動作 | AnalyticDB for PostgreSQL の動作 | 対応策 |
|---|---|---|---|
| 文字列連結における NULL | 'abc' || NULL → 'abc' |
NULL |
Oracle 互換モードに切り替えるか、coalesce |
| FOR LOOP REVERSE の方向 | 2 番目の数値から 1 番目の数値へカウントダウン | 1 番目の数値から 2 番目の数値へカウントダウン | ループ境界値を入れ替えます |
| 関数内でのトランザクション制御 | COMMIT/ROLLBACK が関数本体内で許可されています |
サポートされていません | 関数外に移動するか、関数を分割します |
USING |
EXECUTE '...' USING var |
USING はサポートされていません |
quote_literal/quote_ident |
RETURN および OUT |
両方とも許可されています | サポートされていません | RETURN を OUT 引数に変換します |
SELECT |
テーブルをクエリできます | テーブルをクエリできません | サブクエリまたは JOIN として再記述します |
| 連想配列 | サポートされています | 対応する機能はありません | 設計の見直しが必要です |
| 再帰における同名カーソル | サポートされています | サポートされていません | FOR i IN <query> を使用します |