将 Oracle 转换为 PostgreSQL
拖入 SQL Developer 的 Database Export 或 SQLcl 写出的 .sql 文件(包含表和 Insert 语句),即可下载包含相同表、键、索引和数据的 PostgreSQL 脚本。PostgreSQL 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。
已转换 18 行,无修正,无拒绝
shop_2024_oracle.sql 中的表Oracle 转储 · 由 SQL Developer 写出 · utf-8
customers8 行
7 列 id bigint not null name varchar(100) not null email varchar(150) not null country varchar(2) not null is_active smallint not null signed_up timestamp(0) created_at timestamp(0) not null orders10 行
9 列 id bigint not null customer_id bigint not null invoice bigint status varchar(10) not null total numeric(10,2) not null items integer not null ordered_at timestamp(0) not null shipped_at timestamp(0) note varchar(255)
-- Written by IntoSQL from a Oracle dump. Load it with: psql -v ON_ERROR_STOP=1 -f <file>
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET synchronous_commit = off;
SET maintenance_work_mem = '512MB';
CREATE SEQUENCE invoice_numbers INCREMENT BY 1 MINVALUE 1 START WITH 1020;
CREATE TABLE customers (
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
name varchar(100) NOT NULL,
email varchar(150) NOT NULL,
country varchar(2) DEFAULT 'US' NOT NULL,
is_active smallint DEFAULT 1 NOT NULL,
signed_up timestamp(0),
created_at timestamp(0) DEFAULT LOCALTIMESTAMP NOT NULL,
CONSTRAINT customers_check1 CHECK (is_active IN (0, 1))
);
CREATE TABLE orders (
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
customer_id bigint NOT NULL,
invoice bigint DEFAULT (nextval('invoice_numbers')),
status varchar(10) DEFAULT 'pending' NOT NULL,
total numeric(10,2) NOT NULL,
items integer DEFAULT 1 NOT NULL,
ordered_at timestamp(0) NOT NULL,
shipped_at timestamp(0),
note varchar(255),
CONSTRAINT orders_check1 CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))
);
COPY customers (id, name, email, country, is_active, signed_up, created_at) FROM stdin;
1 Ana Martínez ana@example.com MX 1 2023-01-15 00:00:00 2023-01-15 10:24:00.000000
2 Zoë Müller zoe@example.de DE 1 2023-02-03 00:00:00 2023-02-03 08:11:42.000000
3 José Peña jose@example.es ES 0 2023-03-21 00:00:00 2023-03-21 19:05:17.000000
4 李雷 lilei@example.cn CN 1 2023-05-09 00:00:00 2023-05-09 02:40:00.000000
5 Noah O'Brien noah@example.ie IE 1 \N 2023-06-30 14:00:00.000000
6 Amélie Laurent amelie@example.fr FR 1 2024-01-02 00:00:00 2024-01-02 09:30:00.000000
7 Ravi Patel ravi@example.in IN 0 2024-02-29 00:00:00 2024-02-29 23:59:59.000000
8 Sofia Rossi 🌻 sofia@example.it IT 1 2024-04-12 00:00:00 2024-04-12 12:12:12.000000
\.
COPY orders (id, customer_id, invoice, status, total, items, ordered_at, shipped_at, note) FROM stdin;
1 1 1000 shipped 129.9 3 2024-03-01 10:00:00 2024-03-02 16:30:00 \N
2 2 1001 paid 54 1 2024-03-02 11:15:00 \N Gift wrap 🎁
3 1 1002 cancelled 12.5 1 2024-03-03 09:45:00 \N Customer changed their mind
4 4 1003 shipped 980 12 2024-03-05 02:10:00 2024-03-07 08:00:00 请小心轻放
5 5 1004 pending 45.75 2 2024-03-08 17:20:00 \N Leave at the door; ring twice
6 6 1005 shipped 310.4 5 2024-03-10 13:00:00 2024-03-11 09:00:00 Fragile: "glass"
7 8 1006 paid 76.2 2 2024-03-11 18:45:00 \N \N
8 2 1007 paid 19.99 1 2024-03-12 08:00:00 \N Paid at the counter
9 3 1008 shipped 64 2 2024-03-14 15:30:00 \N Shipping date never recorded
10 7 1009 paid 1499 1 2024-03-15 21:05:00 \N C:\\Users\\ravi\\invoice.pdf
\.
-- Keys, indexes and constraints, after the rows so each builds once.
CREATE UNIQUE INDEX customers_customers_pk ON customers (id);
CREATE UNIQUE INDEX customers_customers_email_uk ON customers (email);
CREATE UNIQUE INDEX orders_orders_pk ON orders (id);
CREATE INDEX orders_orders_status_date_ix ON orders (status, ordered_at);
ALTER TABLE customers ADD CONSTRAINT customers_customers_pk PRIMARY KEY USING INDEX customers_customers_pk;
ALTER TABLE customers ADD CONSTRAINT customers_customers_email_uk UNIQUE USING INDEX customers_customers_email_uk;
ALTER TABLE orders ADD CONSTRAINT orders_orders_pk PRIMARY KEY USING INDEX orders_orders_pk;
ALTER TABLE orders ADD CONSTRAINT orders_customer_fk FOREIGN KEY (customer_id) REFERENCES customers (id) NOT VALID;
DO $f$ BEGIN
ALTER TABLE orders VALIDATE CONSTRAINT orders_customer_fk;
EXCEPTION WHEN foreign_key_violation THEN
RAISE WARNING 'foreign key % has rows without a parent: it stays NOT VALID, and new rows are checked', 'orders_customer_fk';
END $f$;
SELECT setval(pg_get_serial_sequence('customers', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM customers)::numeric, 0) + 1, 1), 9223372036854775807)::bigint, false);
COMMENT ON TABLE customers IS 'People who bought at least once';
SELECT setval(pg_get_serial_sequence('orders', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM orders)::numeric, 0) + 1, 1), 9223372036854775807)::bigint, false);
修正
没有需要修正的内容。
文件在这台电脑上转换,永远不会上传。
其他示例:
从 Oracle 到 PostgreSQL 的变化
每一行是转换器应用的一条规则,以及该规则更改你的数据时在报告中写入的记录。
BOOLEAN (23ai)
boolean
TRUE 和 FALSE 仍为 true 和 false。
VARCHAR2(n BYTE|CHAR), NVARCHAR2(n), CHAR(n), NCHAR(n), CLOB, NCLOB, LONG
varchar(n) 和 text
无论 Oracle 按字节还是按字符计数,长度都按字符计算,因此每个值都能容纳。
NUMBER, NUMBER(p,s), FLOAT(b)
numeric(p,s),或 bigint
NUMBER 保留每一位数字;没有精度的 NUMBER 若所有值都是不超过 18 位的整数,则为 64 位整数;负的小数位数会在小数点前保留同样多的位数。
DATE, TIMESTAMP(p)
timestamp(p)
Oracle 的 DATE 包含精确到秒的时间,因此是日期和时间。在美式或德式会话中导出时,日期不含时间和世纪(DD-MON-RR);请在 ISO 会话(SYYYY-MM-DD HH24:MI:SS)中导出以保留它们。
TIMESTAMP(7) to TIMESTAMP(9)
秒保留六位小数,第七位截断
PostgreSQL 保留到微秒;第七位(100 纳秒)直接截断,从不四舍五入,因此没有值会进到下一秒或下一天。
报告记录: fraction_cut
TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE
timestamptz,时刻相同
timestamptz 保留时刻;PostgreSQL 以会话的时区显示,而不是每个值原有的偏移量。
JSON (21c), and CLOB holding JSON
jsonb
JSON 值相同;导出写为 'null' 的 SQL NULL 仍为 NULL。
BLOB, RAW(n), LONG RAW
bytea
字节仍然是字节。
ROWID, UROWID, XMLTYPE, SDO_GEOMETRY
text,XMLTYPE 为 xml
Oracle 自有的类型没有对应类型;其值保留文本,包括 ROWID 值,尽管它们离开 Oracle 就没有意义。
报告记录: type_replaced
collations that ignore case (BINARY_CI)
精确比较
PostgreSQL 精确比较文本,因此在 Oracle 中不区分大小写的列现在会区分 Ana 和 ana。
报告记录: case_insensitive_collation
identity columns, and a sequence a BEFORE INSERT trigger assigns
GENERATED BY DEFAULT AS IDENTITY,从最大的 id 之后继续
复制的 id 按原样导入,计数器从最大的 id 之后继续。由触发器从序列取值填充的列(12c 之前的做法)也会成为标识列。
CREATE SEQUENCE
CREATE SEQUENCE,选项相同
导出会把序列当前到达的位置写为其 START WITH,因此序列从那里继续。
virtual columns (GENERATED ALWAYS AS ... VIRTUAL)
生成列,STORED
PostgreSQL 13 到 17 只支持存储的生成列;公式相同,|| 的写法让 NULL 像在 Oracle 中一样视为空。
报告记录: virtual_as_stored
virtual columns with functions IntoSQL doesn't write, and the || operator
普通列
无法为 PostgreSQL 写出的公式会留下一个普通列和一条报告记录;导出为虚拟列写出的值会被省略,PostgreSQL 也没有可计算的值。
报告记录: generated_not_translated
defaults with functions IntoSQL doesn't write (SYS_GUID(), ||)
无默认值
无法为 PostgreSQL 写出的默认值会被省略,并在报告中列出。
报告记录: default_not_translated
CHECK constraints
已转换
检查约束相同,在导入每一行时检查。
CHECK constraints with functions IntoSQL doesn't write
不创建
无法为 PostgreSQL 写出的检查约束会被省略,并在报告中列出。
报告记录: check_skipped
CHECK constraints that are DISABLE or ENABLE NOVALIDATE
不创建
Oracle 不对已有的行执行这些检查约束,因此行可能违反它们。
报告记录: check_not_enforced
function-based, bitmap and domain indexes
不创建
IntoSQL 在列上写出 b-tree 索引;其他索引请在导入后重新创建。
报告记录: index_skipped
foreign keys
外键,在行之后创建
行可以按任意顺序导入,之后由外键检查。
foreign keys to tables the export doesn't create, and DISABLE or NOVALIDATE ones
不创建
外键无法引用不存在的表,而 Oracle 不检查的外键可能存在没有父行的行。
报告记录: foreign_key_skipped
partitioned tables
单个表,包含每个分区的行
行保持不变;如有需要,请在 PostgreSQL 中重新对该表分区。
报告记录: partitions_dropped
NOT NULL columns with values that become NULL
去除 NOT NULL
值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。
报告记录: not_null_relaxed
views, synonyms, types, packages, procedures, functions and other triggers
在报告中列出
它们的代码用 PL/SQL 编写,因此会列出来,由你重新创建。
报告记录: not_converted_objects
exports without CREATE TABLE (data only)
根据值推断类型
只含数据的导出只有行,因此类型来自值。
报告记录: no_schema
expressions in INSERT values other than the ones exports write
NULL
不运行数据库的转换器无法对它们求值;导出写出的 to_date、to_timestamp、TO_CLOB、HEXTORAW 等会被读取。
报告记录: expression_value
types IntoSQL doesn't know, and object types
text
文本可以容纳任何值;请在导入后更改类型。
报告记录: unknown_type
dates before year 1 and time zones given as region names
NULL
不是日期的值变为 NULL,报告会统计其数量。
报告记录: date_out_of_range
NaN and Infinity in BINARY_FLOAT and BINARY_DOUBLE
保留 NaN 和 Infinity
PostgreSQL 的浮点类型可以存储 NaN 和 infinity。
NUL characters in text
删除
PostgreSQL 的 text 不能存储 NUL 字符。该字符会被删除,报告会统计受影响的值。
报告记录: nul_removed
text that isn't valid in its encoding
错误的字节变为替换字符
PostgreSQL 文本必须是有效的 UTF-8。
报告记录: invalid_utf8_replaced
text decoded twice (ñ)
按原样复制
脚本中存储的正是这些字符,因此按原样复制;报告会指出它们。
报告记录: mojibake_suspected
| 在 Oracle 中 | 在 PostgreSQL 中 | 报告记录 |
|---|---|---|
| BOOLEAN (23ai) | booleanTRUE 和 FALSE 仍为 true 和 false。 | |
| VARCHAR2(n BYTE|CHAR), NVARCHAR2(n), CHAR(n), NCHAR(n), CLOB, NCLOB, LONG | varchar(n) 和 text无论 Oracle 按字节还是按字符计数,长度都按字符计算,因此每个值都能容纳。 | |
| NUMBER, NUMBER(p,s), FLOAT(b) | numeric(p,s),或 bigintNUMBER 保留每一位数字;没有精度的 NUMBER 若所有值都是不超过 18 位的整数,则为 64 位整数;负的小数位数会在小数点前保留同样多的位数。 | |
| DATE, TIMESTAMP(p) | timestamp(p)Oracle 的 DATE 包含精确到秒的时间,因此是日期和时间。在美式或德式会话中导出时,日期不含时间和世纪(DD-MON-RR);请在 ISO 会话(SYYYY-MM-DD HH24:MI:SS)中导出以保留它们。 | |
| TIMESTAMP(7) to TIMESTAMP(9) | 秒保留六位小数,第七位截断PostgreSQL 保留到微秒;第七位(100 纳秒)直接截断,从不四舍五入,因此没有值会进到下一秒或下一天。 | fraction_cut |
| TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE | timestamptz,时刻相同timestamptz 保留时刻;PostgreSQL 以会话的时区显示,而不是每个值原有的偏移量。 | |
| JSON (21c), and CLOB holding JSON | jsonbJSON 值相同;导出写为 'null' 的 SQL NULL 仍为 NULL。 | |
| BLOB, RAW(n), LONG RAW | bytea字节仍然是字节。 | |
| ROWID, UROWID, XMLTYPE, SDO_GEOMETRY | text,XMLTYPE 为 xmlOracle 自有的类型没有对应类型;其值保留文本,包括 ROWID 值,尽管它们离开 Oracle 就没有意义。 | type_replaced |
| collations that ignore case (BINARY_CI) | 精确比较PostgreSQL 精确比较文本,因此在 Oracle 中不区分大小写的列现在会区分 Ana 和 ana。 | case_insensitive_collation |
| identity columns, and a sequence a BEFORE INSERT trigger assigns | GENERATED BY DEFAULT AS IDENTITY,从最大的 id 之后继续复制的 id 按原样导入,计数器从最大的 id 之后继续。由触发器从序列取值填充的列(12c 之前的做法)也会成为标识列。 | |
| CREATE SEQUENCE | CREATE SEQUENCE,选项相同导出会把序列当前到达的位置写为其 START WITH,因此序列从那里继续。 | |
| virtual columns (GENERATED ALWAYS AS ... VIRTUAL) | 生成列,STOREDPostgreSQL 13 到 17 只支持存储的生成列;公式相同,|| 的写法让 NULL 像在 Oracle 中一样视为空。 | virtual_as_stored |
| virtual columns with functions IntoSQL doesn't write, and the || operator | 普通列无法为 PostgreSQL 写出的公式会留下一个普通列和一条报告记录;导出为虚拟列写出的值会被省略,PostgreSQL 也没有可计算的值。 | generated_not_translated |
| defaults with functions IntoSQL doesn't write (SYS_GUID(), ||) | 无默认值无法为 PostgreSQL 写出的默认值会被省略,并在报告中列出。 | default_not_translated |
| CHECK constraints | 已转换检查约束相同,在导入每一行时检查。 | |
| CHECK constraints with functions IntoSQL doesn't write | 不创建无法为 PostgreSQL 写出的检查约束会被省略,并在报告中列出。 | check_skipped |
| CHECK constraints that are DISABLE or ENABLE NOVALIDATE | 不创建Oracle 不对已有的行执行这些检查约束,因此行可能违反它们。 | check_not_enforced |
| function-based, bitmap and domain indexes | 不创建IntoSQL 在列上写出 b-tree 索引;其他索引请在导入后重新创建。 | index_skipped |
| foreign keys | 外键,在行之后创建行可以按任意顺序导入,之后由外键检查。 | |
| foreign keys to tables the export doesn't create, and DISABLE or NOVALIDATE ones | 不创建外键无法引用不存在的表,而 Oracle 不检查的外键可能存在没有父行的行。 | foreign_key_skipped |
| partitioned tables | 单个表,包含每个分区的行行保持不变;如有需要,请在 PostgreSQL 中重新对该表分区。 | partitions_dropped |
| NOT NULL columns with values that become NULL | 去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。 | not_null_relaxed |
| views, synonyms, types, packages, procedures, functions and other triggers | 在报告中列出它们的代码用 PL/SQL 编写,因此会列出来,由你重新创建。 | not_converted_objects |
| exports without CREATE TABLE (data only) | 根据值推断类型只含数据的导出只有行,因此类型来自值。 | no_schema |
| expressions in INSERT values other than the ones exports write | NULL不运行数据库的转换器无法对它们求值;导出写出的 to_date、to_timestamp、TO_CLOB、HEXTORAW 等会被读取。 | expression_value |
| types IntoSQL doesn't know, and object types | text文本可以容纳任何值;请在导入后更改类型。 | unknown_type |
| dates before year 1 and time zones given as region names | NULL不是日期的值变为 NULL,报告会统计其数量。 | date_out_of_range |
| NaN and Infinity in BINARY_FLOAT and BINARY_DOUBLE | 保留 NaN 和 InfinityPostgreSQL 的浮点类型可以存储 NaN 和 infinity。 | |
| NUL characters in text | 删除PostgreSQL 的 text 不能存储 NUL 字符。该字符会被删除,报告会统计受影响的值。 | nul_removed |
| text that isn't valid in its encoding | 错误的字节变为替换字符PostgreSQL 文本必须是有效的 UTF-8。 | invalid_utf8_replaced |
| text decoded twice (ñ) | 按原样复制脚本中存储的正是这些字符,因此按原样复制;报告会指出它们。 | mojibake_suspected |
将脚本导入 PostgreSQL
脚本先创建表、复制行,再添加键、索引和外键,因此导入速度快,并会在遇到第一个错误时停止。
psql -v ON_ERROR_STOP=1 -d mydb -f oracle.postgresql.sql外键先以 NOT VALID 添加,然后再验证。如果某行指向不存在的行,脚本会告诉你是哪个约束,而不是中途失败。
对于较大的转储,请先创建数据库,然后在服务器所在的机器上用 psql 运行脚本。
IntoSQL Pro 可以通过桌面应用或命令行将文件直接导入 PostgreSQL,无需生成脚本。
如何转储 Oracle 以获得最佳结果
转换器读取转储中的内容。以下选项能让转储保留更多数据:
- 在 SQL Developer 中选择 Tools、Database Export,并选择 DDL 和 Insert 数据格式。SQLcl 使用 SET SQLFORMAT INSERT 也会写出相同内容。
- 导出前请执行 ALTER SESSION SET NLS_DATE_FORMAT = 'SYYYY-MM-DD HH24:MI:SS':默认格式写出的日期不含时间和世纪。
- exp 或 expdp 生成的 .dmp 文件是二进制的:请将其导入 Oracle 数据库(例如 Docker 中的 Oracle Database Free),再用 SQL Developer 导出。
哪些内容不会转换
视图、包、存储过程、函数、触发器、类型和同义词都是用 PL/SQL 编写的。它们会按名称在报告中列出,绝不会被悄悄丢弃,以便你为 PostgreSQL 重写。
关于 Oracle 转 PostgreSQL 的问题
这里没有你要的答案?请通过联系页面给我们留言,会有专人回复。 联系页面
我的转储会被上传吗?
不会。转换器在你的浏览器中运行,分块从磁盘读取转储,因此转储不会离开你的电脑。桌面应用和命令行也在你的电脑上转换。
转储可以多大?
转换器从不把整个转储放进内存:它按范围读取文件两次,边读边写出脚本。几 GB 的转储可以在浏览器、桌面应用和命令行中转换。
可以转换 exp 或 expdp 生成的 .dmp 文件吗?
不能直接转换:Data Pump 和原始的 Export 写出的是只有 Oracle 能读取的二进制格式。请将其导入任意 Oracle 数据库(Oracle Database Free 可在 Docker 中运行),再用 SQL Developer 的 Database Export 或 SQLcl 导出。转换器收到 .dmp 文件时会给出提示。
导出可以来自哪些 Oracle 版本?
由 SQL Developer 和 SQLcl 从 Oracle 11g 及更高版本写出、带有 DBMS_METADATA 表定义的导出。每个测试导出都来自 Oracle 21c 和 23ai,涵盖美式、德式和 ISO 会话,并与其源数据逐个单元格比较。
NUMBER、DATE 和其他 Oracle 类型会怎样?
NUMBER 变为 numeric,所有值都是整数时为 bigint;DATE 变为 timestamp,保留其时间;CLOB 变为 text,BLOB 和 RAW 变为 bytea,BINARY_DOUBLE 变为 double precision 并保留 NaN 和 Infinity,interval 变为 interval。
标识列和序列会保留吗?
是的。标识列仍为标识列,并从最大的 id 之后继续,由触发器从序列取值填充的列也是如此。序列从导出记录的到达位置继续。
PL/SQL 包、存储过程和触发器怎么处理?
它们会按名称在报告中列出,但不会转换,因为每种数据库都用自己的语言编写它们。
脚本可以导入哪些 PostgreSQL 版本?
PostgreSQL 13 到 18。同一个脚本在每个版本上都能导入,如有问题会在第一个错误处停止。