跳到正文
IntoSQL 首页

将 PostgreSQL 转换为 Oracle

拖入由 pg_dump 或 pg_dumpall 生成的纯文本转储(.sql 或 .sql.gz),即可下载包含相同表、键、索引和数据的 Oracle 脚本。Oracle 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。

你的文件
sqlshop_2024_postgres.sql示例文件,6.2 kB
你的数据库

已转换 18 行,2 处修正,无拒绝

显示

shop_2024_postgres.sql 中的表PostgreSQL 18.6 转储 · 由 pg_dump 写出 · utf8mb4

  • customers写作 CUSTOMERS8 行1
    8 列
    IDNUMBER(10) not null
    NAMEVARCHAR2(4000 CHAR) not null
    EMAILVARCHAR2(750 CHAR) not null
    COUNTRYVARCHAR2(2 CHAR) not null
    IS_ACTIVENUMBER(1) not null
    TAGSCLOB not null
    SIGNED_UPDATE
    CREATED_ATTIMESTAMP(6) WITH TIME ZONE not null
  • orders写作 ORDERS10 行1
    10 列
    IDNUMBER(10) not null
    CUSTOMER_IDNUMBER(10) not null
    STATUSVARCHAR2(9 CHAR) not null
    TOTALNUMBER(10,2) not null
    ITEMSNUMBER(5) not null
    ORDERED_ATTIMESTAMP(6) not null
    SHIPPED_ATTIMESTAMP(6)
    NOTEVARCHAR2(4000 CHAR)
    DETAILSCLOB
    DELIVERYVARCHAR2(100 CHAR)

未转换

  • 视图 paid_orders

每种数据库都用自己的语言编写这些对象。请为 Oracle 重写它们。

-- Written by IntoSQL from a PostgreSQL dump. Load it with: NLS_LANG=.AL32UTF8 sqlplus -S -L <user>/<password>@<database> @<file>
WHENEVER SQLERROR EXIT FAILURE ROLLBACK;
SET DEFINE OFF;
SET SQLBLANKLINES ON;
SET FEEDBACK OFF;
SET SERVEROUTPUT ON;
CREATE OR REPLACE FUNCTION "INTOSQL_CLOB" (p SYS.ODCIVARCHAR2LIST) RETURN CLOB IS
    r CLOB;
BEGIN
    DBMS_LOB.CREATETEMPORARY(r, TRUE);
    FOR i IN 1 .. p.COUNT LOOP
        IF p(i) IS NOT NULL THEN
            DBMS_LOB.WRITEAPPEND(r, LENGTH2(p(i)), p(i));
        END IF;
    END LOOP;
    RETURN r;
END;
/
CREATE OR REPLACE FUNCTION "INTOSQL_BLOB" (p SYS.ODCIVARCHAR2LIST) RETURN BLOB IS
    r BLOB;
    piece RAW(2000);
BEGIN
    DBMS_LOB.CREATETEMPORARY(r, TRUE);
    FOR i IN 1 .. p.COUNT LOOP
        piece := HEXTORAW(p(i));
        DBMS_LOB.WRITEAPPEND(r, UTL_RAW.LENGTH(piece), piece);
    END LOOP;
    RETURN r;
END;
/

CREATE TABLE "CUSTOMERS" (
    "ID" NUMBER(10) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1001),
    "NAME" VARCHAR2(4000 CHAR) NOT NULL,
    "EMAIL" VARCHAR2(750 CHAR) NOT NULL,
    "COUNTRY" VARCHAR2(2 CHAR) DEFAULT 'US' NOT NULL,
    "IS_ACTIVE" NUMBER(1) DEFAULT 1 NOT NULL,
    "TAGS" CLOB NOT NULL,
    "SIGNED_UP" DATE,
    "CREATED_AT" TIMESTAMP(6) WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
    CONSTRAINT "CK_CUSTOMERS_IS_ACTIVE" CHECK ("IS_ACTIVE" IN (0, 1))
);

CREATE TABLE "ORDERS" (
    "ID" NUMBER(10) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 5001),
    "CUSTOMER_ID" NUMBER(10) NOT NULL,
    "STATUS" VARCHAR2(9 CHAR) DEFAULT 'pending' NOT NULL,
    "TOTAL" NUMBER(10,2) NOT NULL,
    "ITEMS" NUMBER(5) DEFAULT 1 NOT NULL,
    "ORDERED_AT" TIMESTAMP(6) NOT NULL,
    "SHIPPED_AT" TIMESTAMP(6),
    "NOTE" VARCHAR2(4000 CHAR),
    "DETAILS" CLOB,
    "DELIVERY" VARCHAR2(100 CHAR),
    CONSTRAINT "CK_ORDERS_STATUS" CHECK ("STATUS" IN ('pending', 'paid', 'shipped', 'cancelled')),
    CONSTRAINT "ORDERS_TOTAL_CHECK" CHECK ("TOTAL" >= (0))
);

BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
1,
'Ana Martínez',
'ana@example.com',
'MX',
1,
'["vip"]',
DATE '2023-01-15',
TIMESTAMP '2023-01-15 10:24:00 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
2,
'Zoë Müller',
'zoe@example.de',
'DE',
1,
'[]',
DATE '2023-02-03',
TIMESTAMP '2023-02-03 08:11:42 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
3,
'José Peña',
'jose@example.es',
'ES',
0,
'["wholesale","late payer"]',
DATE '2023-03-21',
TIMESTAMP '2023-03-21 19:05:17 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
4,
'李雷',
'lilei@example.cn',
'CN',
1,
'[]',
DATE '2023-05-09',
TIMESTAMP '2023-05-09 02:40:00 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
5,
'Noah O''Brien',
'noah@example.ie',
'IE',
1,
'[]',
NULL,
TIMESTAMP '2023-06-30 14:00:00 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
6,
'Amélie Laurent',
'amelie@example.fr',
'FR',
1,
'["vip"]',
DATE '2024-01-02',
TIMESTAMP '2024-01-02 09:30:00 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
7,
'Ravi Patel',
'ravi@example.in',
'IN',
0,
'[]',
DATE '2024-02-29',
TIMESTAMP '2024-02-29 23:59:59 +00:00'
);
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"TAGS",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
8,
'Sofia Rossi 🌻',
'sofia@example.it',
'IT',
1,
'[]',
DATE '2024-04-12',
TIMESTAMP '2024-04-12 12:12:12 +00:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
1,
1,
'shipped',
129.90,
3,
TIMESTAMP '2024-03-01 10:00:00',
TIMESTAMP '2024-03-02 16:30:00',
NULL,
'{"gift": false}',
'1 day 06:30:00'
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
2,
2,
'paid',
54.00,
1,
TIMESTAMP '2024-03-02 11:15:00',
NULL,
'Gift wrap 🎁',
'{"gift": true, "wrap": "red"}',
NULL
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
3,
1,
'cancelled',
12.50,
1,
TIMESTAMP '2024-03-03 09:45:00',
NULL,
'Customer changed their mind',
NULL,
NULL
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
4,
4,
'shipped',
980.00,
12,
TIMESTAMP '2024-03-05 02:10:00',
TIMESTAMP '2024-03-07 08:00:00',
'请小心轻放',
'{"fragile": true}',
'2 days 05:50:00'
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
5,
5,
'pending',
45.75,
2,
TIMESTAMP '2024-03-08 17:20:00',
NULL,
'Leave at the door; ring twice',
NULL,
NULL
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
6,
6,
'shipped',
310.40,
5,
TIMESTAMP '2024-03-10 13:00:00',
TIMESTAMP '2024-03-11 09:00:00',
'Fragile: "glass"',
'{"fragile": true}',
'20:00:00'
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
7,
8,
'paid',
76.20,
2,
TIMESTAMP '2024-03-11 18:45:00',
NULL,
NULL,
NULL,
NULL
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
8,
2,
'shipped',
19.99,
1,
TIMESTAMP '2024-03-12 08:00:00',
NULL,
'Shipped date set to infinity by an old import',
NULL,
NULL
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
9,
3,
'shipped',
64.00,
2,
TIMESTAMP '2024-03-14 15:30:00',
TIMESTAMP '2024-03-15 10:00:00',
'Two parcels',
'{"parcels": [1, 2]}',
'18:30:00'
);
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"DELIVERY"
) VALUES (
10,
7,
'paid',
1499.00,
1,
TIMESTAMP '2024-03-15 21:05:00',
NULL,
'C:\Users\ravi\invoice.pdf',
NULL,
NULL
);
COMMIT;
END;
/

-- Keys, indexes, foreign keys, counters and triggers, after the rows.
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "UQ_CUSTOMERS_CUSTOMERS_EMAIL_KEY" UNIQUE ("EMAIL");
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "PK_CUSTOMERS" PRIMARY KEY ("ID");
ALTER TABLE "ORDERS" ADD CONSTRAINT "PK_ORDERS" PRIMARY KEY ("ID");
CREATE INDEX "IX_ORDERS_IDX_ORDERS_CUSTOMER" ON "ORDERS" ("CUSTOMER_ID");
CREATE INDEX "IX_ORDERS_IDX_ORDERS_STATUS_DATE" ON "ORDERS" ("STATUS", "ORDERED_AT");
ALTER TABLE "ORDERS" ADD CONSTRAINT "ORDERS_CUSTOMER_ID_FKEY" FOREIGN KEY ("CUSTOMER_ID") REFERENCES "CUSTOMERS" ("ID") ENABLE NOVALIDATE;
BEGIN
    EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" ENABLE VALIDATE CONSTRAINT "ORDERS_CUSTOMER_ID_FKEY"';
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Foreign key ' || '"ORDERS_CUSTOMER_ID_FKEY"' || ' has rows without a parent: it stays unvalidated, and new rows are checked.');
END;
/
DECLARE
    next_id NUMBER;
BEGIN
    SELECT NVL(MAX("ID"), 0) + 1 INTO next_id FROM "CUSTOMERS";
    IF next_id < 1001 THEN
        next_id := 1001;
    END IF;
    EXECUTE IMMEDIATE 'ALTER TABLE "CUSTOMERS" MODIFY ("ID" GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH ' || next_id || '))';
END;
/
COMMENT ON TABLE "CUSTOMERS" IS 'People who bought at least once';
DECLARE
    next_id NUMBER;
BEGIN
    SELECT NVL(MAX("ID"), 0) + 1 INTO next_id FROM "ORDERS";
    IF next_id < 5001 THEN
        next_id := 5001;
    END IF;
    EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" MODIFY ("ID" GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH ' || next_id || '))';
END;
/
DROP FUNCTION "INTOSQL_CLOB";
DROP FUNCTION "INTOSQL_BLOB";
COMMIT;
EXIT;

修正

  1. customers 的 "tags" 是 text[],Oracle 没有该类型,因此为 CLOB,值保持不变。
  2. orders 的 "delivery" 是 interval,Oracle 没有该类型,因此为 VARCHAR2(100 CHAR),值保持不变。

需要检查

  • 1 个视图、触发器、例程或事件未转换;报告逐一列出了它们。 请在 Oracle 中手动重新创建它们。
  • customers 中 "tags" 的默认值无法为 Oracle 写出(a value its type can't hold),因此该列没有默认值。 如果新行需要,请手动添加默认值。
  • orders 上的索引 idx_orders_details 使用了无法为 Oracle 写出的表达式(a gin index),因此未创建。 如果查询需要,请手动创建该索引。
  • orders 的 "shipped_at" 中有 1 个值是 Oracle 无法存储的日期,例如 infinity,因此设为 NULL。 Oracle 存储 1 到 9999 年,不支持无穷值;如果这些日期很重要,请在源数据中替换它们。

文件在这台电脑上转换,永远不会上传。

其他示例:

从 PostgreSQL 到 Oracle 的变化

每一行是转换器应用的一条规则,以及该规则更改你的数据时在报告中写入的记录。

  • boolean

    NUMBER(1),并带有限定 0 和 1 的 CHECK

    Oracle 21c 没有布尔列类型;1 和 0 在每个版本上都能表示 true 和 false。

  • varchar(n), char(n)

    VARCHAR2(n CHAR),按字符计数

    长度像 PostgreSQL 一样按字符计数。

  • text

    所有值都不超过 4000 字节时为 VARCHAR2(4000 CHAR)(键中为 750 个字符),否则为 CLOB

    VARCHAR2 默认容纳 4000 字节,而 Oracle 无法为 CLOB 建立索引,因此值能容纳时文本保持为 VARCHAR2,否则为 CLOB。

    报告记录: text_as_lob

  • empty strings and empty bytea values

    NULL

    Oracle 将空字符串存储为 NULL,因此原为 ' 的值读回时为 NULL。报告会按列统计它们。

    报告记录: empty_string_null

  • citext

    精确比较

    除非另有指定,Oracle 会像 PostgreSQL 的文本一样精确比较文本;citext 列不再忽略大小写。

    报告记录: case_insensitive_collation

  • numeric(p,s) past 38 digits; numeric without a precision

    值不超过 38 位时为 NUMBER,否则为 VARCHAR2

    Oracle 的 NUMBER 在任何小数位数下都可容纳 38 位数字。更宽的数字保存为文本,使每一位都保持精确。

    报告记录: decimal_as_text

  • money

    NUMBER(19,2)

    Oracle 没有 money 类型。金额保存为数字,不带服务器区域设置添加的货币符号。

    报告记录: type_replaced

  • interval

    VARCHAR2(100 CHAR),存放 PostgreSQL 的文本

    interval 混合了月与天和秒,Oracle 将它们存在两种不同的类型中;文本保留每个部分。

    报告记录: type_replaced

  • arrays (integer[], text[]...)

    存放 JSON 数组的 CLOB

    Oracle 没有数组。JSON 数组保留每个元素和 NULL,JSON_TABLE 可以读取它。

    报告记录: type_replaced

  • ranges, geometric types, tsvector, xml, hstore, PostGIS, composite and extension types

    VARCHAR2 或 CLOB,存放 PostgreSQL 的文本

    Oracle 没有对应的类型,因此每个值保留 PostgreSQL 写出的文本。

    报告记录: type_replaced

  • json, jsonb

    CLOB

    Oracle 的 JSON 函数在每个版本上都能从文本读取 JSON,而 CLOB 能保存任意大小的值。

  • timestamp

    TIMESTAMP

    不带时区的 timestamp 是挂钟读数,与 Oracle 的 TIMESTAMP 相同。date 变为 DATE,它也包含一天中的时刻,保持为午夜。

  • timestamp with time zone

    UTC 的 TIMESTAMP WITH TIME ZONE

    每个值以 UTC 写入,无论转储写入的偏移量是多少,都是同一时刻。

  • time

    从午夜起的 INTERVAL DAY TO SECOND

    Oracle 没有表示一天中时刻的类型;从午夜起算的 interval 能容纳每个值,包括 24:00:00。

  • infinity, -infinity, BC dates and years past 9999

    NULL

    脚本写入 1 到 9999 年,而 Oracle 没有无穷值。这些值变为 NULL,报告会统计其数量。

    报告记录: date_out_of_range

  • NaN and Infinity in numeric

    NULL

    Oracle 的 NUMBER 没有 NaN 或 Infinity,因此这些值变为 NULL,报告会统计其数量。

    报告记录: invalid_value_null

  • NOT NULL columns with values that become NULL

    去除 NOT NULL

    值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。

    报告记录: not_null_relaxed

  • enum types

    VARCHAR2,并带有限定其标签的 CHECK

    带检查约束的文本保留相同的值。

  • enum columns holding values outside their labels

    不加检查约束的 VARCHAR2

    标签之外的值会使检查失败,因此不创建检查约束。

    报告记录: enum_check_skipped

  • uuid, inet, cidr, macaddr

    VARCHAR2

    Oracle 没有这些类型;文本保留每个值。

  • bytea

    BLOB

    字节仍然是字节。

  • bit(n), bit varying

    不超过 63 位时为 NUMBER,值为各位组成的数;更长则为文本数字

    位串存储为其各位组成的数字。

  • oid, xid, cid

    NUMBER(10),并带有不能为负数的 CHECK

    NUMBER 可容纳每个值。

  • serial, identity

    GENERATED BY DEFAULT ON NULL AS IDENTITY,从最大的 id 之后继续

    复制过来的 id 按原样导入,计数器从最大的 id 或序列的下一个值之后继续,取两者中较大者。

  • a second serial or identity column in a table

    普通整数

    Oracle 每个表只有一个标识列计数;其他列保留其值,不带计数器。

    报告记录: counter_dropped

  • sequences not owned by a column

    CREATE SEQUENCE,从它已达到的值开始

    Oracle 序列的工作方式相同。

  • generated columns

    虚拟列

    Oracle 只在读取时计算生成列;STORED 列变为值相同的虚拟列。

    报告记录: stored_as_virtual

  • generated columns with functions Oracle lacks

    带有这些值的普通列

    无法为 Oracle 写出的公式会留下一个带有转储中值的普通列和一条报告记录。

    报告记录: generated_not_translated

  • defaults with functions Oracle lacks, and defaults from a sequence

    无默认值

    无法为 Oracle 写出的默认值会被省略,并在报告中列出。

    报告记录: default_not_translated

  • CHECK constraints

    已转换,内联在 CREATE TABLE 中

    内联的检查约束在每行导入时检验。

  • CHECK constraints with functions Oracle lacks

    不创建

    无法为 Oracle 写出的检查约束会被省略,并在报告中列出。

    报告记录: check_skipped

  • CHECK ... NOT VALID

    不创建

    PostgreSQL 从未检查之前已有的行,因此它们可能违反该约束。

    报告记录: check_not_valid

  • gin, gist, brin and hash indexes, partial and expression indexes, and indexes on CLOB columns

    不创建

    IntoSQL 为 Oracle 在列上写出 b-tree 索引,而 Oracle 无法为 CLOB 建立索引;其他索引请在导入后重新创建。

    报告记录: index_skipped

  • foreign keys

    在数据之后以 ENABLE NOVALIDATE 添加,然后验证,有孤立行时给出警告

    外键立即检查新行,最后再验证;有孤立行时外键保持未验证状态并给出警告,而不是中止导入。

  • ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT

    去除

    Oracle 的外键只在删除时执行操作,可用 CASCADE 或 SET NULL。

    报告记录: foreign_key_action_unsupported

  • foreign keys to tables the dump doesn't create

    不创建

    外键无法引用不存在的表。

    报告记录: foreign_key_skipped

  • names over 128 bytes

    缩短

    Oracle 12.2 及更高版本的名称最多 128 字节,多于 PostgreSQL 的 63 字节。

    报告记录: name_shortened

  • partitioned tables

    单个表,包含每个分区的行

    行保持不变;如有需要,请在 Oracle 中重新对该表分区。

    报告记录: partitions_dropped

  • schemas

    单个 schema,每个表名前加上其 schema 名

    脚本导入到运行它的用户的 schema 中,因此来自不同 schema 的表名前会加上各自的 schema 名;public 中的表保留原名。

    报告记录: schemas_merged

  • views, functions, procedures, triggers, rules, policies, extensions and types

    在报告中列出

    它们的代码用 PostgreSQL 的语言编写,因此会列出来,由你重新创建。

    报告记录: not_converted_objects

  • dumps without CREATE TABLE (--data-only)

    根据值推断类型

    只含数据的转储只有行,因此类型来自值。

    报告记录: no_schema

  • expressions in INSERT values

    NULL

    不运行数据库的转换器无法对它们求值。

    报告记录: expression_value

  • types IntoSQL doesn't know

    CLOB

    文本可以容纳任何值;请在导入后更改类型。

    报告记录: unknown_type

  • text that isn't valid in its encoding

    错误的字节变为替换字符

    脚本为 UTF-8,使用 NLS_LANG=.AL32UTF8 导入。

    报告记录: invalid_utf8_replaced

  • text decoded twice (ñ)

    按原样复制

    转储中存储的正是这些字符,因此按原样复制;报告会指出它们。

    报告记录: mojibake_suspected

在 PostgreSQL 中在 Oracle 中报告记录
booleanNUMBER(1),并带有限定 0 和 1 的 CHECKOracle 21c 没有布尔列类型;1 和 0 在每个版本上都能表示 true 和 false。
varchar(n), char(n)VARCHAR2(n CHAR),按字符计数长度像 PostgreSQL 一样按字符计数。
text所有值都不超过 4000 字节时为 VARCHAR2(4000 CHAR)(键中为 750 个字符),否则为 CLOBVARCHAR2 默认容纳 4000 字节,而 Oracle 无法为 CLOB 建立索引,因此值能容纳时文本保持为 VARCHAR2,否则为 CLOB。text_as_lob
empty strings and empty bytea valuesNULLOracle 将空字符串存储为 NULL,因此原为 ' 的值读回时为 NULL。报告会按列统计它们。empty_string_null
citext精确比较除非另有指定,Oracle 会像 PostgreSQL 的文本一样精确比较文本;citext 列不再忽略大小写。case_insensitive_collation
numeric(p,s) past 38 digits; numeric without a precision值不超过 38 位时为 NUMBER,否则为 VARCHAR2Oracle 的 NUMBER 在任何小数位数下都可容纳 38 位数字。更宽的数字保存为文本,使每一位都保持精确。decimal_as_text
moneyNUMBER(19,2)Oracle 没有 money 类型。金额保存为数字,不带服务器区域设置添加的货币符号。type_replaced
intervalVARCHAR2(100 CHAR),存放 PostgreSQL 的文本interval 混合了月与天和秒,Oracle 将它们存在两种不同的类型中;文本保留每个部分。type_replaced
arrays (integer[], text[]...)存放 JSON 数组的 CLOBOracle 没有数组。JSON 数组保留每个元素和 NULL,JSON_TABLE 可以读取它。type_replaced
ranges, geometric types, tsvector, xml, hstore, PostGIS, composite and extension typesVARCHAR2 或 CLOB,存放 PostgreSQL 的文本Oracle 没有对应的类型,因此每个值保留 PostgreSQL 写出的文本。type_replaced
json, jsonbCLOBOracle 的 JSON 函数在每个版本上都能从文本读取 JSON,而 CLOB 能保存任意大小的值。
timestampTIMESTAMP不带时区的 timestamp 是挂钟读数,与 Oracle 的 TIMESTAMP 相同。date 变为 DATE,它也包含一天中的时刻,保持为午夜。
timestamp with time zoneUTC 的 TIMESTAMP WITH TIME ZONE每个值以 UTC 写入,无论转储写入的偏移量是多少,都是同一时刻。
time从午夜起的 INTERVAL DAY TO SECONDOracle 没有表示一天中时刻的类型;从午夜起算的 interval 能容纳每个值,包括 24:00:00。
infinity, -infinity, BC dates and years past 9999NULL脚本写入 1 到 9999 年,而 Oracle 没有无穷值。这些值变为 NULL,报告会统计其数量。date_out_of_range
NaN and Infinity in numericNULLOracle 的 NUMBER 没有 NaN 或 Infinity,因此这些值变为 NULL,报告会统计其数量。invalid_value_null
NOT NULL columns with values that become NULL去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。not_null_relaxed
enum typesVARCHAR2,并带有限定其标签的 CHECK带检查约束的文本保留相同的值。
enum columns holding values outside their labels不加检查约束的 VARCHAR2标签之外的值会使检查失败,因此不创建检查约束。enum_check_skipped
uuid, inet, cidr, macaddrVARCHAR2Oracle 没有这些类型;文本保留每个值。
byteaBLOB字节仍然是字节。
bit(n), bit varying不超过 63 位时为 NUMBER,值为各位组成的数;更长则为文本数字位串存储为其各位组成的数字。
oid, xid, cidNUMBER(10),并带有不能为负数的 CHECKNUMBER 可容纳每个值。
serial, identityGENERATED BY DEFAULT ON NULL AS IDENTITY,从最大的 id 之后继续复制过来的 id 按原样导入,计数器从最大的 id 或序列的下一个值之后继续,取两者中较大者。
a second serial or identity column in a table普通整数Oracle 每个表只有一个标识列计数;其他列保留其值,不带计数器。counter_dropped
sequences not owned by a columnCREATE SEQUENCE,从它已达到的值开始Oracle 序列的工作方式相同。
generated columns虚拟列Oracle 只在读取时计算生成列;STORED 列变为值相同的虚拟列。stored_as_virtual
generated columns with functions Oracle lacks带有这些值的普通列无法为 Oracle 写出的公式会留下一个带有转储中值的普通列和一条报告记录。generated_not_translated
defaults with functions Oracle lacks, and defaults from a sequence无默认值无法为 Oracle 写出的默认值会被省略,并在报告中列出。default_not_translated
CHECK constraints已转换,内联在 CREATE TABLE 中内联的检查约束在每行导入时检验。
CHECK constraints with functions Oracle lacks不创建无法为 Oracle 写出的检查约束会被省略,并在报告中列出。check_skipped
CHECK ... NOT VALID不创建PostgreSQL 从未检查之前已有的行,因此它们可能违反该约束。check_not_valid
gin, gist, brin and hash indexes, partial and expression indexes, and indexes on CLOB columns不创建IntoSQL 为 Oracle 在列上写出 b-tree 索引,而 Oracle 无法为 CLOB 建立索引;其他索引请在导入后重新创建。index_skipped
foreign keys在数据之后以 ENABLE NOVALIDATE 添加,然后验证,有孤立行时给出警告外键立即检查新行,最后再验证;有孤立行时外键保持未验证状态并给出警告,而不是中止导入。
ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT去除Oracle 的外键只在删除时执行操作,可用 CASCADE 或 SET NULL。foreign_key_action_unsupported
foreign keys to tables the dump doesn't create不创建外键无法引用不存在的表。foreign_key_skipped
names over 128 bytes缩短Oracle 12.2 及更高版本的名称最多 128 字节,多于 PostgreSQL 的 63 字节。name_shortened
partitioned tables单个表,包含每个分区的行行保持不变;如有需要,请在 Oracle 中重新对该表分区。partitions_dropped
schemas单个 schema,每个表名前加上其 schema 名脚本导入到运行它的用户的 schema 中,因此来自不同 schema 的表名前会加上各自的 schema 名;public 中的表保留原名。schemas_merged
views, functions, procedures, triggers, rules, policies, extensions and types在报告中列出它们的代码用 PostgreSQL 的语言编写,因此会列出来,由你重新创建。not_converted_objects
dumps without CREATE TABLE (--data-only)根据值推断类型只含数据的转储只有行,因此类型来自值。no_schema
expressions in INSERT valuesNULL不运行数据库的转换器无法对它们求值。expression_value
types IntoSQL doesn't knowCLOB文本可以容纳任何值;请在导入后更改类型。unknown_type
text that isn't valid in its encoding错误的字节变为替换字符脚本为 UTF-8,使用 NLS_LANG=.AL32UTF8 导入。invalid_utf8_replaced
text decoded twice (ñ)按原样复制转储中存储的正是这些字符,因此按原样复制;报告会指出它们。mojibake_suspected

将脚本导入 Oracle

脚本先创建表、复制行,再添加键、索引和外键,因此导入速度快,并会在遇到第一个错误时停止。

NLS_LANG=.AL32UTF8 sqlplus -S -L user@//localhost/mydb @postgresql.oracle.sql

外键以 ENABLE NOVALIDATE 添加,因此新行会立即检查,之后再验证。如果某个旧行指向不存在的行,脚本会说明是哪个约束,并让它保持未验证状态,而不是中途失败。

对于较大的转储,请在数据库所在的机器上运行 SQL*Plus。脚本每 100 行提交一次。运行脚本的用户需要 CREATE TABLE、CREATE SEQUENCE、CREATE TRIGGER 和 CREATE PROCEDURE 权限,以及其表空间上的配额。

如何转储 PostgreSQL 以获得最佳结果

转换器读取转储中的内容。以下选项能让转储保留更多数据:

  • --format=plain 是 pg_dump 的默认格式,写出 SQL。custom 或 directory 归档(-Fc、-Fd)可用 pg_restore -f dump.sql 转为 SQL,无需数据库服务器。
  • --no-owner --no-privileges 会省略角色和授权,转换器本来也会跳过它们,因此转储更小。
  • pg_dumpall 将每个数据库转储到一个文件中;每个数据库的表名前会加上该数据库名,因此不会冲突。

哪些内容不会转换

视图、物化视图、函数、存储过程、触发器、规则、行安全策略、扩展和类型都是用 PostgreSQL 自己的语言编写的。它们会按名称在报告中列出,绝不会被悄悄丢弃,以便你为 Oracle 重写。

关于 PostgreSQL 转 Oracle 的问题

这里没有你要的答案?请通过联系页面给我们留言,会有专人回复。 联系页面

我的转储会被上传吗?

不会。转换器在你的浏览器中运行,分块从磁盘读取转储,因此转储不会离开你的电脑。桌面应用和命令行也在你的电脑上转换。

转储可以多大?

转换器从不把整个转储放进内存:它按范围读取文件两次,边读边写出脚本。几 GB 的转储可以在浏览器、桌面应用和命令行中转换。

它能读取 pg_dump -Fc 归档吗?

不能直接读取:custom 归档是二进制格式。pg_restore -f dump.sql dump.backup 会将其转为转换器读取的纯 SQL,无需连接服务器。转换器收到归档时会给出提示。

转储可以来自哪些 PostgreSQL 版本?

由 PostgreSQL 10 及更高版本的 pg_dump 和 pg_dumpall 写出的转储,行可以是 COPY 或 INSERT。每个测试转储都来自 PostgreSQL 13 和 18,并与其源数据逐个单元格比较。

serial 和标识列的值会保留吗?

会。每个表的 serial 或标识列变为标识列,每行保留其 id,下一个 id 从最大的 id 之后继续。不属于任何列的序列变为 Oracle 序列,从它们已达到的值开始。

数组、enum 和其他 PostgreSQL 类型会怎样处理?

数组变为 JSON 数组,布尔值变为 NUMBER(1),带时区的时间戳变为 TIMESTAMP WITH TIME ZONE,文本在值能容纳时为 VARCHAR2,否则为 CLOB。空字符串变为 NULL,因为 Oracle 就是这样存储它们的,报告会统计其数量。

函数、触发器和视图怎么处理?

它们会按名称在报告中列出,但不会转换,因为每种数据库都用自己的语言编写它们。

脚本可以导入哪些 Oracle 版本?

Oracle 19c 及更高版本。每个测试转储都能在 21c 和 23ai 上导入,如有问题会在第一个错误处停止。