# Oracle轉PostgreSQL【table篇】
###### tags: `PostgreSQL`,`Postgres`,`Oracle`,`轉換資料庫`
將Oracle table、comment、primary key、index,轉換成PostgreSQL能用的語法
文章前半段分項目別,後半段有將這4項合併在一起產出
## 欄位型態對應
以下是建議對應的方式
| Oracle | PostgreSQL | 特別說明 |
| --------- | ---------- | ------- |
| CHAR | VARCHAR | |
| NCHAR | VARCHAR | |
| NVARCHAR2 | VARCHAR | |
| NUMBER | NUMERIC | |
| DATE | DATE | 沒有時分秒|
| DATE | TIMESTAMPTZ</br>等同TIMESTAMP with time zone | 有時分秒,有時區 |
| DATE | TIMESTAMP | 有時分秒,沒時區 |
| TIMESTAMP | TIMESTAMPTZ</br>等同TIMESTAMP with time zone | 有時區 |
| TIMESTAMP | TIMESTAMP | 沒時區 |
| CLOB | TEXT | |
| BLOB | BYTEA | |
| LONG RAW | BYTEA | |
| JSON | JSONB | |
| GUID | UUID | 註1 |
##### 註1: 建立UUID語法
```sql
/* Oracle */
create table uuid_table
(
id NVARCHAR2(64) default RAWTOHEX(SYS_GUID())
);
/* PostgreSQL */
create table uuid_table
(
id UUID DEFAULT uuid_generate_v4 ()
);
```
## 產生create postgreSQL table語法
### 儲存語法用的table
```sql
create table ORACLE_TO_POSTGRESQL
(
schema_name varchar2(100),
table_name varchar2(100),
create_table varchar2(4000),
primary_key varchar2(4000),
index_key varchar2(4000),
comments varchar2(4000)
);
comment on column ORACLE_TO_POSTGRESQL.schema_name is 'schema名稱';
comment on column ORACLE_TO_POSTGRESQL.table_name is 'table名稱';
comment on column ORACLE_TO_POSTGRESQL.create_table is 'Create table語法';
comment on column ORACLE_TO_POSTGRESQL.primary_key is 'Create Primary Key語法';
comment on column ORACLE_TO_POSTGRESQL.index_key is 'Create index語法';
comment on column ORACLE_TO_POSTGRESQL.comments is 'Create 欄位說明語法';
```
### 將Oracle全部table,轉成create postgreSQL table
語法會入到table ORACLE_TO_POSTGRESQL
```sql=
declare
ls_schema_name varchar2(100) := 'DEMO'; /* 產生的table,要在那個schema */
ls_create_table varchar2(4000); /* 產生的內容 */
ls_data_default varchar2(100); /* Default值 */
ls_data_type user_tab_columns.data_type%type; /* 型態 */
begin
FOR c_table in (SELECT table_name, decode(temporary,'Y','GLOBAL TEMPORARY ','') temporary, decode(duration,'SYS$SESSION',' ON COMMIT PRESERVE ROWS','SYS$TRANSACTION',' ON COMMIT DELETE ROWS','') duration FROM user_tables) /* 這個user之下,所有table */
LOOP
ls_create_table := 'create ' || c_table.temporary || 'table ' || ls_schema_name || '.' || c_table.table_name || '(';
/* 將型態做對應 */
FOR c_column in (select column_id, column_name,decode(data_type,'NVARCHAR2',' VARCHAR('||to_char(char_length)||')',
'VARCHAR2',' VARCHAR('||to_char(char_length)||')',
'NCHAR',' VARCHAR('||to_char(char_length)||')',
'CHAR',' VARCHAR('||to_char(char_length)||')',
'NUMBER',case when data_precision > 0 then ' NUMERIC('||to_char(data_precision)||','||to_char( data_scale)||')'
else ' NUMERIC' end,
'DATE',' DATE',
'TIMESTAMP(6)',' TIMESTAMPTZ',
'LONG RAW',' BYTEA',
'BLOB',' BYTEA',
'CLOB',' TEXT',
'JSON',' JSONB',
'對應不到') data_type,
decode(nullable,'N',' not null','Y',' ') nullable,
case when default_length > 0 then data_default end as data_default
from user_tab_columns where table_name = c_table.table_name order by column_id)
LOOP
ls_data_type := c_column.data_type;
/* 如果有時分秒,要用TIMESTAMPTZ */
IF ls_data_type = ' DATE' THEN
/* 判斷欄位資料有沒有時分秒,例如先用『where trunc(欄位) <> 欄位』,檢查有沒有時分秒
select count(*) into ln_is_datetime_column from datetime_column where table_name = c_table.table_name and column_name = c_column.column_name and is_datetime_column = 'Y';
IF ln_is_datetime_column > 0 THEN
ls_data_type := ' TIMESTAMPTZ';
ELSE
ls_data_type := ' DATE';
END IF;
*/
null;
END IF;
/* 自定義特殊欄位要用的日期型態
IF c_column.column_name in ('INSERT_TIME','INSERT_DATE') THEN
ls_data_type := ' TIMESTAMPTZ';
END IF;
*/
ls_data_default := c_column.data_default;
IF ls_data_default = 'null' THEN /* 測試時遇到看不到但取到的值是null */
ls_data_default := '';
END IF;
/* Default值 */
IF ls_data_default is not null THEN
IF trim(upper(ls_data_default)) = 'SYSDATE' THEN
ls_data_default := 'NOW()';
ELSIF trim(upper(ls_data_default)) = 'TRUNC(SYSDATE)' THEN
ls_data_default := 'DATE_TRUNC(''day'',NOW())';
ELSIF trim(upper(ls_data_default)) = 'RAWTOHEX(SYS_GUID())' THEN
ls_data_default := 'UUID_GENERATE_V4()';
ELSIF trim(upper(ls_data_default)) = 'SYS_GUID()' THEN
ls_data_default := 'UUID_GENERATE_V4()';
END IF;
ls_data_default := ' default ' || ls_data_default;
END IF;
ls_create_table := ls_create_table || case when c_column.column_id = 1 then '' else ',' end || chr(13) || ' ' || c_column.column_name || ls_data_type || c_column.nullable || ls_data_default;
END LOOP;
ls_create_table := ls_create_table || chr(13) || ')' || c_table.duration || ';' || chr(13);
insert into ORACLE_TO_POSTGRESQL (schema_name, table_name, create_table) values (ls_schema_name, c_table.table_name, ls_create_table);
END LOOP;
end;
```
### 將Oracle全部comment,轉成create postgreSQL comment
語法會入到table ORACLE_TO_POSTGRESQL
```sql=
declare
ls_schema_name varchar2(100) := 'DEMO'; /* 產生的table,要在那個schema */
ls_comments varchar2(10000); /* 產生的內容 */
begin
FOR c_table in (SELECT table_name FROM user_tables)
LOOP
/* 產生table comment */
select comments into ls_comments from user_tab_comments where table_name = c_table.table_name;
IF ls_comments is not null THEN
ls_comments := 'comment on table ' || ls_schema_name || '.' || c_table.table_name || ' is ''' || ls_comments || ''';' || chr(13);
END IF;
/* 產生column comment */
FOR c_col_comment in (select column_name,comments from user_col_comments where comments is not null and table_name = c_table.table_name)
LOOP
ls_comments := ls_comments || 'comment on column ' || ls_schema_name || '.' || c_table.table_name || '.' || c_col_comment.column_name || ' is ''' || replace(replace(c_col_comment.comments,chr(13),''),'''','''''') || ''';' || chr(13);
END LOOP;
IF lengthb(ls_comments) > 4000 THEN
ls_comments := '/* 備註超過4000字,無法入值,請人工移轉 */';
END IF;
insert into ORACLE_TO_POSTGRESQL (schema_name, table_name, comments) values (ls_schema_name, c_table.table_name, ls_comments);
END LOOP;
end;
```
### 將Oracle全部primary key,轉成create postgreSQL primary key
語法會入到table ORACLE_TO_POSTGRESQL
```sql=
declare
ls_schema_name varchar2(100) := 'DEMO'; /* 產生的table,要在那個schema */
ls_create_primary varchar2(4000); /* 產生的內容 */
begin
FOR c_table in (SELECT table_name FROM user_tables)
LOOP
/* 產生primary */
ls_create_primary := '';
FOR c_primary in (select constraint_name primary_key from user_constraints a where a.constraint_type = 'P' and a.table_name = c_table.table_name)
LOOP
ls_create_primary := 'alter table ' || ls_schema_name || '.' || c_table.table_name || ' add primary key (';
FOR c_primary_column in (select position, column_name from user_cons_columns where constraint_name = c_primary.primary_key order by position)
LOOP
ls_create_primary := ls_create_primary || case when c_primary_column.position = 1 then '' else ', ' end || c_primary_column.column_name;
END LOOP;
ls_create_primary := ls_create_primary || ');' || chr(13);
END LOOP;
insert into ORACLE_TO_POSTGRESQL (schema_name, table_name, primary_key) values (ls_schema_name, c_table.table_name, ls_create_primary);
END LOOP;
end;
```
### 將Oracle全部index,轉成create postgreSQL index
語法會入到table ORACLE_TO_POSTGRESQL
```sql=
declare
ls_schema_name varchar2(100) := 'DEMO'; /* 產生的table,要在那個schema */
ls_create_index varchar2(4000); /* 產生的內容 */
ls_index_column user_ind_columns.column_name%type;
begin
FOR c_table in (SELECT table_name FROM user_tables)
LOOP
/* 產生index */
ls_create_index := '';
/* table欄位有clob,就有這類名稱SYS_IL0001350299C00008$$的index,所以必須排除,自行處理 */
FOR c_index in (select index_name, decode(uniqueness,'UNIQUE','UNIQUE','') uniqueness from user_indexes where table_name = c_table.table_name and index_name not like '%$$' and index_name not in (select constraint_name primary_key from user_constraints a where a.constraint_type = 'P' and a.table_name = user_indexes.table_name) order by index_name)
LOOP
ls_create_index := ls_create_index || 'create ' || c_index.uniqueness || ' index ' || c_index.index_name || ' on ' || ls_schema_name || '.' || c_table.table_name || ' (';
FOR c_index_column in (select column_position, column_name, ' ' || descend descend from user_ind_columns where index_name = c_index.index_name order by column_position)
LOOP
/* 如果有欄位名稱出現'$',要到sys.col$,找真正的欄位名稱 */
IF c_index_column.column_name like '%$' THEN
select c.default$ into ls_index_column from sys.user$ u, sys.obj$ o, sys.col$ c where u.user# = o.owner# and o.obj# = c.obj# and c.name = c_index_column.column_name and u.name = user;
ls_index_column := replace(ls_index_column,'"','');
ELSE
ls_index_column := c_index_column.column_name;
END IF;
ls_create_index := ls_create_index || case when c_index_column.column_position = 1 then '' else ', ' end || ls_index_column || c_index_column.descend;
END LOOP;
ls_create_index := ls_create_index || ');' || chr(13);
END LOOP;
insert into ORACLE_TO_POSTGRESQL (schema_name, table_name, index_key) values (ls_schema_name, c_table.table_name, ls_create_index);
END LOOP;
end;
```
### 合併以上SQL,將Oracle全部table(table、comment、primary key、index),轉成create postgreSQL
語法會入到table ORACLE_TO_POSTGRESQL
```sql=
declare
ls_schema_name varchar2(100) := 'DEMO'; /* 產生的table,要在那個schema */
ls_data_default varchar2(100); /* Default值 */
ls_data_type user_tab_columns.data_type%type; /* 型態 */
ls_create_table varchar2(4000); /* 產生table的內容 */
ls_comments varchar2(10000); /* 產生comment的內容 */
ls_create_primary varchar2(4000); /* 產生primary key的內容 */
ls_create_index varchar2(4000); /* 產生index的內容 */
ls_index_column user_ind_columns.column_name%type; /* index欄位 */
begin
FOR c_table in (SELECT table_name, decode(temporary,'Y','GLOBAL TEMPORARY ','') temporary, decode(duration,'SYS$SESSION',' ON COMMIT PRESERVE ROWS','SYS$TRANSACTION',' ON COMMIT DELETE ROWS','') duration FROM user_tables) /* 這個user之下,所有table */
LOOP
ls_create_table := 'create ' || c_table.temporary || 'table ' || ls_schema_name || '.' || c_table.table_name || '(';
/* 將型態做對應 */
FOR c_column in (select column_id, column_name,decode(data_type,'NVARCHAR2',' VARCHAR('||to_char(char_length)||')',
'VARCHAR2',' VARCHAR('||to_char(char_length)||')',
'NCHAR',' VARCHAR('||to_char(char_length)||')',
'CHAR',' VARCHAR('||to_char(char_length)||')',
'NUMBER',case when data_precision > 0 then ' NUMERIC('||to_char(data_precision)||','||to_char( data_scale)||')'
else ' NUMERIC' end,
'DATE',' DATE',
'TIMESTAMP(6)',' TIMESTAMPTZ',
'LONG RAW',' BYTEA',
'BLOB',' BYTEA',
'CLOB',' TEXT',
'JSON',' JSONB',
'對應不到') data_type,
decode(nullable,'N',' not null','Y',' ') nullable,
case when default_length > 0 then data_default end as data_default
from user_tab_columns where table_name = c_table.table_name order by column_id)
LOOP
ls_data_type := c_column.data_type;
/* 如果有時分秒,要用TIMESTAMPTZ */
IF ls_data_type = ' DATE' THEN
/* 判斷欄位資料有沒有時分秒,例如先用『where trunc(欄位) <> 欄位』,檢查有沒有時分秒
select count(*) into ln_is_datetime_column from datetime_column where table_name = c_table.table_name and column_name = c_column.column_name and is_datetime_column = 'Y';
IF ln_is_datetime_column > 0 THEN
ls_data_type := ' TIMESTAMPTZ';
ELSE
ls_data_type := ' DATE';
END IF;
*/
null;
END IF;
/* 自定義特殊欄位要用的日期型態
IF c_column.column_name in ('INSERT_TIME','INSERT_DATE') THEN
ls_data_type := ' TIMESTAMPTZ';
END IF;
*/
ls_data_default := c_column.data_default;
IF ls_data_default = 'null' THEN /* 測試時遇到看不到但取到的值是null */
ls_data_default := '';
END IF;
/* Default值 */
IF ls_data_default is not null THEN
IF trim(upper(ls_data_default)) = 'SYSDATE' THEN
ls_data_default := 'NOW()';
ELSIF trim(upper(ls_data_default)) = 'TRUNC(SYSDATE)' THEN
ls_data_default := 'DATE_TRUNC(''day'',NOW())';
ELSIF trim(upper(ls_data_default)) = 'RAWTOHEX(SYS_GUID())' THEN
ls_data_default := 'UUID_GENERATE_V4()';
ELSIF trim(upper(ls_data_default)) = 'SYS_GUID()' THEN
ls_data_default := 'UUID_GENERATE_V4()';
END IF;
ls_data_default := ' default ' || ls_data_default;
END IF;
ls_create_table := ls_create_table || case when c_column.column_id = 1 then '' else ',' end || chr(13) || ' ' || c_column.column_name || ls_data_type || c_column.nullable || ls_data_default;
END LOOP;
ls_create_table := ls_create_table || chr(13) || ')' || c_table.duration || ';' || chr(13);
/* 產生table comment */
select comments into ls_comments from user_tab_comments where table_name = c_table.table_name;
IF ls_comments is not null THEN
ls_comments := 'comment on table ' || ls_schema_name || '.' || c_table.table_name || ' is ''' || ls_comments || ''';' || chr(13);
END IF;
/* 產生column comment */
FOR c_col_comment in (select column_name,comments from user_col_comments where comments is not null and table_name = c_table.table_name)
LOOP
ls_comments := ls_comments || 'comment on column ' || ls_schema_name || '.' || c_table.table_name || '.' || c_col_comment.column_name || ' is ''' || replace(replace(c_col_comment.comments,chr(13),''),'''','''''') || ''';' || chr(13);
END LOOP;
IF lengthb(ls_comments) > 4000 THEN
ls_comments := '/* 備註超過4000字,無法入值,請人工移轉 */';
END IF;
/* 產生primary */
ls_create_primary := '';
FOR c_primary in (select constraint_name primary_key from user_constraints a where a.constraint_type = 'P' and a.table_name = c_table.table_name)
LOOP
ls_create_primary := 'alter table ' || ls_schema_name || '.' || c_table.table_name || ' add primary key (';
FOR c_primary_column in (select position, column_name from user_cons_columns where constraint_name = c_primary.primary_key order by position)
LOOP
ls_create_primary := ls_create_primary || case when c_primary_column.position = 1 then '' else ', ' end || c_primary_column.column_name;
END LOOP;
ls_create_primary := ls_create_primary || ');' || chr(13);
END LOOP;
/* 產生index */
ls_create_index := '';
/* table欄位有clob,就有這類名稱SYS_IL0001350299C00008$$的index,所以必須排除,自行處理 */
FOR c_index in (select index_name, decode(uniqueness,'UNIQUE','UNIQUE','') uniqueness from user_indexes where table_name = c_table.table_name and index_name not like '%$$' and index_name not in (select constraint_name primary_key from user_constraints a where a.constraint_type = 'P' and a.table_name = user_indexes.table_name) order by index_name)
LOOP
ls_create_index := ls_create_index || 'create ' || c_index.uniqueness || ' index ' || c_index.index_name || ' on ' || ls_schema_name || '.' || c_table.table_name || ' (';
FOR c_index_column in (select column_position, column_name, ' ' || descend descend from user_ind_columns where index_name = c_index.index_name order by column_position)
LOOP
/* 如果有欄位名稱出現'$',要到sys.col$,找真正的欄位名稱 */
IF c_index_column.column_name like '%$' THEN
select c.default$ into ls_index_column from sys.user$ u, sys.obj$ o, sys.col$ c where u.user# = o.owner# and o.obj# = c.obj# and c.name = c_index_column.column_name and u.name = user;
ls_index_column := replace(ls_index_column,'"','');
ELSE
ls_index_column := c_index_column.column_name;
END IF;
ls_create_index := ls_create_index || case when c_index_column.column_position = 1 then '' else ', ' end || ls_index_column || c_index_column.descend;
END LOOP;
ls_create_index := ls_create_index || ');' || chr(13);
END LOOP;
insert into ORACLE_TO_POSTGRESQL values (ls_schema_name, c_table.table_name, ls_create_table, ls_create_primary, ls_create_index, ls_comments);
END LOOP;
end;
```