ora2pgを使用したOracleからPostgreSQLへの移行中に、異なるデータベース間でのデータ型の不一致の問題が発生しました。 デフォルトでは、すべての列が正しく変換されるわけではなく、Oracleブール値がないためにあいまいさが生じます。一部の列は数値として、一部は論理値として転送する必要があります。 同時に、Hibernateはデータ型についてすべてを知っており、リファレンス回路を作成できます。
最終的な転送プロセスは次のとおりでした:ora2pgによるテーブル構造の作成、参照スキームに従って構造の修正、データの転送、blobおよびBooleanの変換、PostgreSQLにない関数(nvl、nvl2、regexp_substr)の追加、インデックス、ビューなどの残りの構造の作成。
catの下では、sqlの変換中に蓄積された半自動移行用のsqlスクリプト。
準備する
データ変換のユーティリティとして、ora2pgが使用されました。 使用プロセスは、
記事で非常によく説明されてい
ます 。
ora2pgでプロジェクトを作成し、プロジェクトを設定して、スキームを生成します。
$./export_schema.sh
PostgreSQLの「./schema/tables/table.sql」ファイルに「ora_schema」スキーマとテーブルを作成します。
Hibernateを切り替えて作成モードにし、別の「hb_schema」参照回路を作成します。 プロジェクトでビューを使用する場合、異なるスキームのテーブルの数は収束しません。 Hibernateは、ビューの代わりに完全なテーブルを生成します。これを考慮する必要があります。
列タイプの修正
変更する列を調べます
select hb.table_name as table , hb.column_name as column, hb.data_type as hb_type, ora.data_type as ora_type from information_schema.columns hb join information_schema.columns ora on hb.table_name = ora.table_name and hb.column_name = ora.column_name where hb.table_schema = 'hb_schema' and ora.table_schema= 'ora_schema' and ora.data_type != hb.data_type ORDER BY hb.table_name, hb.column_name;
単純なケースの代替品
select 'ALTER TABLE ora_schema.' || ora.table_name || ' ALTER COLUMN ' || ora.column_name || ' TYPE ' || hb.data_type || case when hb.data_type = 'numeric' then '('|| hb.numeric_precision ||','|| hb.numeric_scale||')' else '' end || '; ' from information_schema.columns as hb INNER JOIN information_schema.columns as ora on hb.table_name = ora.table_name and hb.column_name = ora.column_name where hb.table_schema = 'hb_schema' and ora.table_schema= 'ora_schema' and ora.data_type != hb.data_type and (hb.data_type = 'integer' or hb.data_type = 'numeric' or hb.data_type = 'bigint' or hb.data_type = 'date') ORDER BY hb.table_name, hb.column_name;
データを転送する
データを転送する前に、booleanおよびbytea(oid)列にのみ型の不一致が残っていること、および数値を持つすべての列が正しい精度を持ち、偶発的な丸めを避けるための「倍精度」ではないことを確認します。
デフォルトでは、コピープロセスは1つのストリームに移動します。 構成を目的の番号に変更します
JOBS 4 ORACLE_COPIES 4
データをコピーするプロセスを開始します
ora2pg -t COPY -o data.sql -b ./data -c ./config/ora2pg.conf
処理ブール
デフォルト値がある場合は削除する必要があり、タイプを変更してデフォルト値を返す
処理OID(bytea)
byteaをoidに変換する手順を作成します
CREATE OR REPLACE FUNCTION ora_schema.bytea_to_oid(p_blob bytea) RETURNS oid AS $BODY$ declare v_oid oid; v_int integer; begin if octet_length(p_blob)=0 then v_oid:=null; else select lo_create(0) into v_oid; select lo_open(v_oid, x'20000'::int | x'40000'::int) into v_int; select lowrite (0, p_blob) into v_int; end if; return v_oid; END;$BODY$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION ora_schema.bytea_to_oid(bytea) OWNER TO postgres;
一時的な列を作成する
select 'ALTER TABLE ora_schema.' || ora.table_name || ' ADD COLUMN ' || hb.column_name || '_oid oid; ' from information_schema.columns as hb JOIN information_schema.columns as ora on hb.table_name = ora.table_name and hb.column_name = ora.column_name where hb.table_schema = 'hb_schema' and ora.table_schema= 'ora_schema' and hb.udt_name != ora.udt_name and hb.data_type = 'oid' ORDER BY hb.table_name, hb.column_name;
データを転送する
select 'update ' || ora.table_name || ' set ' || hb.column_name || '_oid = bytea_to_oid(' || hb.column_name || ');' from information_schema.columns as hb JOIN information_schema.columns as ora on hb.table_name = ora.table_name and hb.column_name = ora.column_name where hb.table_schema = 'hb_schema' and ora.table_schema= 'ora_schema' and hb.udt_name != ora.udt_name and hb.data_type = 'oid' ORDER BY hb.table_name, hb.column_name;
古い列を削除する
select 'ALTER TABLE ora_schema.' || ora.table_name || ' DROP COLUMN ' || hb.column_name || '; ' from information_schema.columns as hb INNER JOIN information_schema.columns as ora on hb.table_name = ora.table_name and hb.column_name = ora.column_name where hb.table_schema = 'hb_schema' and ora.table_schema= 'ora_schema' and hb.udt_name != ora.udt_name and hb.data_type = 'oid' ORDER BY hb.table_name, hb.column_name;
時間列の名前を変更する
select 'ALTER TABLE ora_schema.' || ora.table_name || ' RENAME COLUMN ' || hb.column_name ||'_oid to '|| hb.column_name||'; ' from information_schema.columns as hb INNER JOIN information_schema.columns as ora on hb.table_name = ora.table_name and hb.column_name ||'_oid' = ora.column_name where hb.table_schema = 'hb_schema' and ora.table_schema= 'ora_schema' and hb.data_type = 'oid' ORDER BY hb.table_name, hb.column_name;
PostgreSQLにない機能の追加
コードを書き換える必要がないように、PostgreSQLには存在しないが、Oracleにあり、プロジェクトで使用されるコードを作成するだけです。
last_day(日付)
create function last_day(dt date) returns date LANGUAGE SQL AS $$ select cast (date_trunc('month',dt)+interval '1 month' - interval '1 day' as date) $$;
nvl(日付、日付)
create function nvl(var1 date, var2 date) returns date LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl(整数、整数)
create function nvl(var1 integer, var2 integer) returns integer LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl(数値、数値)
create function nvl(var1 numeric, var2 numeric) returns numeric LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl(テキスト、テキスト)
create function nvl(var1 text, var2 text) returns text LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl(タイムゾーン付きタイムスタンプ、タイムゾーン付きタイムスタンプ)
create function nvl(var1 timestamp with time zone, var2 timestamp with time zone) returns timestamp with time zone LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl(タイムスタンプ、タイムスタンプ)
create function nvl(var1 timestamp without time zone, var2 timestamp without time zone) returns timestamp without time zone LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl(varchar、varchar)
create function nvl(var1 character varying, var2 character varying) returns character varying LANGUAGE plpgsql AS $$ begin return case when var1 is null then var2 else var1 end; end; $$;
nvl2(日付、日付、日付)
create function nvl2(var1 date, var2 date, var3 date) returns date LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
nvl2(整数、整数、整数)
create function nvl2(var1 integer, var2 integer, var3 integer) returns integer LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
nvl2(数値、数値、数値)
create function nvl2(var1 numeric, var2 numeric, var3 numeric) returns numeric LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
nvl2(テキスト、テキスト、テキスト)
create function nvl2(var1 text, var2 text, var3 text) returns text LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
nvl(タイムゾーン付きタイムスタンプ、タイムゾーン付きタイムスタンプ)
create function nvl2(var1 timestamp with time zone, var2 timestamp with time zone, var3 timestamp with time zone) returns timestamp with time zone LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
nvl2(タイムスタンプ、タイムスタンプ、タイムスタンプ)
create function nvl2(var1 timestamp without time zone, var2 timestamp without time zone, var3 timestamp without time zone) returns timestamp without time zone LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
nvl2(varchar、varchar、varchar)
create function nvl2(var1 character varying, var2 character varying, var3 character varying) returns character varying LANGUAGE plpgsql AS $$ begin return case when var1 is not null then var2 else var3 end; end; $$;
regexp_substr(テキスト、テキスト)
create function regexp_substr(str text, pattern text) returns text LANGUAGE SQL AS $$ SELECT (regexp_matches(str, pattern))[1] $$;
regexp_substr(varchar、varchar)
create function regexp_substr(str character varying, pattern character varying) returns text LANGUAGE SQL AS $$ SELECT (regexp_matches(str, pattern))[1] $$;
trunc(日付、varchar)
create function trunc(dt date, formatstr character varying) returns date LANGUAGE plpgsql AS $$ begin return date_trunc(formatstr,dt); end; $$;
trunc(タイムゾーン付きのタイムスタンプ、varchar)
create function trunc(dt timestamp with time zone, formatstr character varying) returns date LANGUAGE plpgsql AS $$ begin return date_trunc(formatstr,dt); end; $$;
trunc(タイムスタンプ、varchar)
create function trunc(dt timestamp without time zone, formatstr character varying) returns date LANGUAGE plpgsql AS $$ begin return date_trunc(formatstr,dt); end; $$;
テキストフィールド
Oracleには無制限のテキストフィールドはありませんが、代わりにLobが使用されます。 PostgreSQLには特別なタイプ-テキストがあります。 両方のベースを使用できるようにするには、テキストフィールドの注釈を次のようにする必要があります
@Column(name = "script", nullable = true) @Type(type = "org.hibernate.type.MaterializedClobType") public String scriptText;
Postgresでは、追加された機能を知っている自作の方言も使用します。
public class PostgresDialect extends PostgreSQL9Dialect { public PostgresDialect() { super(); this.registerFunction("nvl", new StandardSQLFunction("nvl")); } public SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { Object descriptor; switch (sqlCode) { case 2005: descriptor = LongVarcharTypeDescriptor.INSTANCE; break; default: descriptor = super.getSqlTypeDescriptorOverride(sqlCode); } return (SqlTypeDescriptor) descriptor; } }
シーケンス
OracleとPostgressのシーケンスとは異なるnextval構文があります。
オラクル
SELECT my_seq.nextval;
ポストグレス
select nextval('my_seq');
一般的な見方をします。 これを行うには、PostgresとOracleで関数を作成し、この関数を使用するようにどこでも関数を書き換えます。
オラクル
create or replace function seq_nextval(p_sequence_name varchar) return integer as l_statement varchar(4000); l_value integer; begin l_statement := 'select '||upper(p_sequence_name)||'.nextval from dual'; execute immediate l_statement into l_value; return l_value; end;
ポストグレス
create function seq_nextval(p_sequence_name text) returns bigint as $$ select nextval(p_sequence_name); $$ language sql;
使用する
select seq_nextval('my_seq')
Oracleではこの関数を作成できますが、値を選択することはできないため、Postgres nextval( 'my_seq')関数を直接使用することはできません。
Transformers.ALIAS_TO_ENTITY_MAP
Transformers.ALIAS_TO_ENTITY_MAPを使用する場合、戻り値の型を指定する必要があります。 Oracleはデフォルトで大文字のキー、PotgresSqlは小文字、キーの不一致は手動でのみ修正されます。
シスデート
SysdateはPotgresSqlにはなく、current_timestampは両方のデータベースで機能します。 それと交換
部分文字列
Javaとは異なり、データベースでは、インデックスは1で始まり、サブストリング( 'str'、0、2)ではなく最初の2文字を取得するためにサブストリング( 'str'、1、2)を正しく指定します。 しかし、Oracleは2番目のオプションを認めています。 修正する必要があり、0インデックスを使用しません
一時テーブル
PostgreSQLでは、Oracleとは異なり、一時テーブルは毎回作成され、セッション内で有効になります。 Oracleでは、一時テーブルの内容のみが何らかのフレームワークに存在し、テーブル自体は常に作成されます。
これにより、次の問題が発生します。
1)各セッションを作成するとき、一時テーブルを作成する必要があります。 (この瞬間は、新しいセッションを作成するときに初期化sql-blockを実行するアプリケーションサーバーの機能を使用して簡略化できます(接続プール→詳細設定→初期化SQL)
2)休止状態のエンティティの検証は、構成されたスキームのフレームワーク内で実行されます(スキームを指定しない場合、使用可能なすべてのスキームで検証が実行されます)。 なぜなら Postgreの一時テーブルは別のスキームで作成されるため、検証は失敗します。
(同じ構造の実際のテーブルを作成することで回避できます。ネイティブクエリの場合、作業は通常のテーブルではなく一時テーブルで行われます)。
3)ネイティブクエリを介してのみ、このような一時テーブルを操作することができます。 Hibernateは、生成されたすべてのクエリにスキーマ名を追加します(ネイティブクエリを介して一時テーブルですべての作業を行うか、一時テーブルを破棄して、一意のキーでデータを区別する通常のテーブルを使用する必要があります。どちらの方法でも機能を書き換える必要があります)。
Oracleのように、Postgressで一時テーブルを引き続き使用するには、テーブル関数を使用し、テーブル関数に基づいてビューを作成します。 詳細は
こちら一時テーブルを作成する機能
create_permanent_temp_table create or replace function create_permanent_temp_table( p_table_name varchar, p_schema varchar default null) returns void as $$ declare
一時テーブルを削除する機能
drop_permanent_temp_table create or replace function drop_permanent_temp_table( p_table_name varchar, p_schema varchar default null) returns void as $$ declare
使用する
通常の一時テーブルを作成する
CREATE temp TABLE filter_table ( id BIGINT NOT NULL, id_str VARCHAR(255), key VARCHAR(255) NOT NULL, fd DATE, id_long BIGINT, sd DATE, CONSTRAINT filter_table_pkey PRIMARY KEY (key, id) ) ON COMMIT PRESERVE ROWS;
生成関数を実行します:
select create_permanent_temp_table('filter_table', 'schema_name');