将 SQL Server 转换为 Oracle
拖入 SSMS 的"生成脚本"写出的包含架构和数据的脚本(.sql 或 .sql.gz,UTF-16 或 UTF-8),即可下载包含相同表、键、索引和数据的 Oracle 脚本。Oracle 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。
已转换 18 行,2 处修正,无拒绝
shop_2024_sqlserver.sql 中的表SQL Server 转储 · 由 SQL Server Management Objects 写出 · utf-8
orders写作 ORDERS10 行1
11 列 ID NUMBER(10) not null CUSTOMER_ID NUMBER(10) not null STATUS VARCHAR2(10 CHAR) not null TOTAL NUMBER(19,4) not null ITEMS NUMBER(3) not null ORDERED_AT TIMESTAMP(0) not null SHIPPED_AT TIMESTAMP(7) NOTE CLOB DETAILS CLOB ORDER_REF VARCHAR2(36 CHAR) not null TOTAL_WITH_TAX NUMBER(12,2) customers写作 CUSTOMERS8 行1
8 列 ID NUMBER(10) not null NAME VARCHAR2(100 CHAR) not null EMAIL VARCHAR2(200 CHAR) not null COUNTRY VARCHAR2(2 CHAR) not null IS_ACTIVE NUMBER(1) not null SIGNED_UP DATE CREATED_AT TIMESTAMP(0) WITH TIME ZONE not null ROW_VERSION RAW(8)
未转换
- 视图 paid_orders
- 触发器 orders_no_negative
每种数据库都用自己的语言编写这些对象。请为 Oracle 重写它们。
-- Written by IntoSQL from a SQL Server 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 "ORDERS" (
"ID" NUMBER(10) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1),
"CUSTOMER_ID" NUMBER(10) NOT NULL,
"STATUS" VARCHAR2(10 CHAR) DEFAULT 'pending' NOT NULL,
"TOTAL" NUMBER(19,4) NOT NULL,
"ITEMS" NUMBER(3) DEFAULT 1 NOT NULL,
"ORDERED_AT" TIMESTAMP(0) NOT NULL,
"SHIPPED_AT" TIMESTAMP(7),
"NOTE" CLOB,
"DETAILS" CLOB,
"ORDER_REF" VARCHAR2(36 CHAR) NOT NULL,
"TOTAL_WITH_TAX" NUMBER(12,2) GENERATED ALWAYS AS (CAST(CAST("TOTAL" * (1.16) AS NUMBER(12,2)) AS NUMBER(12,2))) VIRTUAL,
CONSTRAINT "CK_ORDERS_ITEMS" CHECK ("ITEMS" >= 0),
CONSTRAINT "CHECK2" CHECK ("STATUS" = 'cancelled' OR "STATUS" = 'shipped' OR "STATUS" = 'paid' OR "STATUS" = 'pending'),
CONSTRAINT "CHECK3" CHECK ("TOTAL" >= (0))
);
CREATE TABLE "CUSTOMERS" (
"ID" NUMBER(10) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1),
"NAME" VARCHAR2(100 CHAR) NOT NULL,
"EMAIL" VARCHAR2(200 CHAR) NOT NULL,
"COUNTRY" VARCHAR2(2 CHAR) DEFAULT 'US' NOT NULL,
"IS_ACTIVE" NUMBER(1) DEFAULT 1 NOT NULL,
"SIGNED_UP" DATE,
"CREATED_AT" TIMESTAMP(0) WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
"ROW_VERSION" RAW(8),
CONSTRAINT "CK_CUSTOMERS_IS_ACTIVE" CHECK ("IS_ACTIVE" IN (0, 1))
);
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
1,
'Ana Martínez',
'ana@example.com',
'MX',
1,
DATE '2023-01-15',
TIMESTAMP '2023-01-15 04:24:00.0000000 -06:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
2,
'Zoë Müller',
'zoe@example.de',
'DE',
1,
DATE '2023-02-03',
TIMESTAMP '2023-02-03 09:11:42.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
3,
'José Peña',
'jose@example.es',
'ES',
0,
DATE '2023-03-21',
TIMESTAMP '2023-03-21 20:05:17.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
4,
'李雷',
'lilei@example.cn',
'CN',
1,
DATE '2023-05-09',
TIMESTAMP '2023-05-09 10:40:00.0000000 +08:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
5,
'Noah O''Brien',
'noah@example.ie',
'IE',
1,
NULL,
TIMESTAMP '2023-06-30 15:00:00.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
6,
'Amélie Laurent',
'amelie@example.fr',
'FR',
1,
DATE '2024-01-02',
TIMESTAMP '2024-01-02 10:30:00.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
7,
'Ravi Patel',
'ravi@example.in',
'IN',
0,
DATE '2024-02-29',
TIMESTAMP '2024-03-01 05:29:59.0000000 +05:30'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
8,
'Sofia Rossi 🌻',
'sofia@example.it',
'IT',
1,
DATE '2024-04-12',
TIMESTAMP '2024-04-12 14:12:12.0000000 +02:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
1,
1,
'shipped',
129.9000,
3,
TIMESTAMP '2024-03-01 10:00:00.0000000',
TIMESTAMP '2024-03-02 16:30:00.1234567',
NULL,
'{"gift": false}',
'0b7a4c2e-3f1d-4e8a-9c55-1a2b3c4d5e6f'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
2,
2,
'paid',
54.0000,
1,
TIMESTAMP '2024-03-02 11:15:00.0000000',
NULL,
'Gift wrap 🎁',
'{"gift": true, "wrap": "red"}',
'1c8b5d3f-4a2e-4f9b-8d66-2b3c4d5e6f70'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
3,
1,
'cancelled',
12.5000,
1,
TIMESTAMP '2024-03-03 09:45:00.0000000',
NULL,
'Customer changed their mind',
NULL,
'2d9c6e4a-5b3f-4a0c-9e77-3c4d5e6f7081'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
4,
4,
'shipped',
980.0000,
12,
TIMESTAMP '2024-03-05 02:10:00.0000000',
TIMESTAMP '2024-03-07 08:00:00.0000000',
'请小心轻放',
'{"fragile": true}',
'3e0d7f5b-6c4a-4b1d-8f88-4d5e6f708192'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
5,
5,
'pending',
45.7500,
2,
TIMESTAMP '2024-03-08 17:20:00.0000000',
NULL,
'Leave at the door; ring twice',
NULL,
'4f1e806c-7d5b-4c2e-9a99-5e6f708192a3'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
6,
6,
'shipped',
310.4000,
5,
TIMESTAMP '2024-03-10 13:00:00.0000000',
TIMESTAMP '2024-03-11 09:00:00.0000000',
'Fragile: "glass"',
'{"fragile": true}',
'50f2917d-8e6c-4d3f-8baa-6f708192a3b4'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
7,
8,
'paid',
76.2000,
2,
TIMESTAMP '2024-03-11 18:45:00.0000000',
NULL,
NULL,
NULL,
'6103a28e-9f7d-4e40-9cbb-708192a3b4c5'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
8,
2,
'shipped',
19.9900,
1,
TIMESTAMP '2024-03-12 08:00:00.0000000',
TIMESTAMP '9999-12-31 23:59:59.9999999',
'Shipped date set to the maximum by an old import',
NULL,
'7214b39f-a08e-4f51-8dcc-8192a3b4c5d6'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
9,
3,
'shipped',
64.0000,
2,
TIMESTAMP '2024-03-14 15:30:00.0000000',
TIMESTAMP '2024-03-15 10:00:00.0000000',
'Two parcels',
'{"parcels": [1, 2]}',
'8325c4a0-b19f-4062-9edd-92a3b4c5d6e7'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
10,
7,
'paid',
1499.0000,
1,
TIMESTAMP '2024-03-15 21:05:00.0000000',
NULL,
'C:\Users\ravi\invoice.pdf',
NULL,
'9436d5b1-c2a0-4173-8fee-a3b4c5d6e7f8'
);
COMMIT;
END;
/
-- Keys, indexes, foreign keys, counters and triggers, after the rows.
ALTER TABLE "ORDERS" ADD CONSTRAINT "PK_ORDERS" PRIMARY KEY ("ID");
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "PK_CUSTOMERS" PRIMARY KEY ("ID");
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "UQ_CUSTOMERS_EMAIL" UNIQUE ("EMAIL");
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 "FK_ORDERS" FOREIGN KEY ("CUSTOMER_ID") REFERENCES "CUSTOMERS" ("ID") ENABLE NOVALIDATE;
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" ENABLE VALIDATE CONSTRAINT "FK_ORDERS"';
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Foreign key ' || '"FK_ORDERS"' || ' 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 "ORDERS";
IF next_id < 1 THEN
next_id := 1;
END IF;
EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" MODIFY ("ID" GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH ' || next_id || '))';
END;
/
DECLARE
next_id NUMBER;
BEGIN
SELECT NVL(MAX("ID"), 0) + 1 INTO next_id FROM "CUSTOMERS";
IF next_id < 1 THEN
next_id := 1;
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';
DROP FUNCTION "INTOSQL_CLOB";
DROP FUNCTION "INTOSQL_BLOB";
COMMIT;
EXIT;
修正
- orders 的 "total_with_tax" 在源数据中是存储列;Oracle 在读取时计算它。
- customers 的 "row_version" 是 SQL Server 自动设置的行版本;转储中没有它的值,因此改为 RAW(8),并保持为空。
需要检查
- 源数据中有 6 个文本列比较时不区分大小写;Oracle 精确比较,因此唯一键和搜索的行为可能不同。 在需要的列上使用不区分大小写的排序规则或列类型。
- 2 个视图、触发器、例程或事件未转换;报告逐一列出了它们。 请在 Oracle 中手动重新创建它们。
- orders 上的检查约束 check1 无法为 Oracle 写出(function isjson),因此未创建。 如果它重要,请手动编写该检查约束。
- orders 上的索引 idx_orders_pending 使用了无法为 Oracle 写出的表达式(filtered index),因此未创建。 如果查询需要,请手动创建该索引。
- orders 中 "order_ref" 的默认值无法为 Oracle 写出(function uuid),因此该列没有默认值。 如果新行需要,请手动添加默认值。
文件在这台电脑上转换,永远不会上传。
其他示例:
从 SQL Server 到 Oracle 的变化
每一行是转换器应用的一条规则,以及该规则更改你的数据时在报告中写入的记录。
bit
NUMBER(1),并带有限定 0 和 1 的 CHECK
Oracle 21c 没有 boolean 列类型;1 和 0 在每个版本上都能保留这些值。
char(n), varchar(n), nchar(n), nvarchar(n)
VARCHAR2(n CHAR),按字符计数
长度按字符计算,与 SQL Server 的 nvarchar 相同。
varchar(max), nvarchar(max), text, ntext
每个值都不超过 4000 字节时为 VARCHAR2(4000 CHAR),否则为 CLOB
VARCHAR2 默认容纳 4000 字节,而 Oracle 无法为 CLOB 建立索引,因此值能容纳时文本保持为 VARCHAR2,否则为 CLOB。
报告记录: text_as_lob
empty strings and empty binary values
NULL
Oracle 将空字符串存储为 NULL,因此原为 ' 的值读回时为 NULL。报告会按列统计它们。
报告记录: empty_string_null
decimal(p,s), money, smallmoney
NUMBER(p,s);money 为 NUMBER(19,4)
NUMBER 与 decimal 一样可容纳 38 位数字,因此每个值都是精确的。
datetime, datetime2, smalldatetime
TIMESTAMP(p)
datetime 是挂钟读数,与 Oracle 的 TIMESTAMP 相同,后者最多保留九位秒的小数。date 变为 DATE,它也包含时间,但始终为午夜。
datetimeoffset
TIMESTAMP WITH TIME ZONE,保留每个值的偏移量
Oracle 保留 -12:00 到 +14:00 的偏移量;偏移量超出 -12:00 的值以 UTC 写出,仍是同一时刻。
time
从午夜起的 INTERVAL DAY TO SECOND
Oracle 没有表示一天中时间的类型;从午夜起的 interval 可容纳每个值。
dates that aren't valid
NULL
不是日期的值变为 NULL,报告会统计其数量。
报告记录: date_out_of_range
json (SQL Server 2025)
CLOB
JSON 值相同,Oracle 的 JSON 函数可以读取。
uniqueidentifier
VARCHAR2(36)
标识符相同,以小写写出。
binary, varbinary, image
RAW(n) 和 BLOB
字节仍然是字节。
geometry, geography
BLOB,存放 SQL Server 自己的字节
SSMS 以 SQL Server 的序列化格式写出空间值,这些字节会逐字节保留。
报告记录: spatial_as_native
rowversion, timestamp
BLOB,空
行版本由 SQL Server 自动设置,SSMS 不会为其写出值。
报告记录: rowversion_empty
xml, sql_variant, hierarchyid, vector
VARCHAR2 或 CLOB,存放每个值的文本
Oracle 没有对应的类型;xml 和 sql_variant 保留每个值的文本,hierarchyid 保留其路径,例如 /1/2/。
报告记录: type_replaced
collations that ignore case (_CI_)
精确比较
除非另行指定,Oracle 精确比较文本,因此在 SQL Server 中不区分大小写的列现在会区分 Ana 和 ana。
报告记录: case_insensitive_collation
IDENTITY
GENERATED BY DEFAULT ON NULL AS IDENTITY,从最大的 id 之后继续
复制的 id 按原样导入,计数器从最大的 id 之后继续。
CREATE SEQUENCE
CREATE SEQUENCE,选项相同
Oracle 序列的工作方式相同。SSMS 不会写出序列当前到达的位置,因此它从创建时的值开始。
computed columns
虚拟列
Oracle 只在读取时计算生成列;PERSISTED 列变为值相同的虚拟列。
报告记录: stored_as_virtual
computed columns with functions Oracle lacks
普通列
无法写出的公式会留下一个普通列和一条报告记录;SSMS 不会为计算列写出值。
报告记录: generated_not_translated
defaults with functions Oracle lacks, and NEXT VALUE FOR
无默认值
无法写出的默认值会被省略,并在报告中列出。
报告记录: default_not_translated
CHECK constraints
已转换
检查约束相同,在导入每一行时检查。
CHECK constraints with functions Oracle lacks
不创建
无法写出的检查约束会被省略,并在报告中列出。
报告记录: check_skipped
CHECK constraints added WITH NOCHECK or disabled
不创建
SQL Server 不执行这些检查约束,因此行可能违反它们。
报告记录: check_not_enforced
filtered and columnstore indexes, and indexes on CLOB columns
不创建
IntoSQL 在整列上写出 b-tree 索引;其他索引请在导入后重新创建。
报告记录: 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
names over 128 bytes
缩短
Oracle 12.2 及更高版本的名称最多 128 字节;SQL Server 的名称最多 128 个字符。
报告记录: name_shortened
foreign keys to tables the dump doesn't create
不创建
外键无法引用不存在的表。
报告记录: foreign_key_skipped
tables on a partition scheme
单个表,包含每个分区的行
行保持不变;如有需要,请在 Oracle 中重新对该表分区。
报告记录: partitions_dropped
temporal tables (SYSTEM_VERSIONING)
两个表:当前行和历史记录
表及其历史表作为普通表复制;Oracle 不会自动保留历史记录。
报告记录: system_versioning_dropped
schemas
单个 schema,每个表名前加上其 schema 名
脚本会导入到运行它的用户的 schema 中,因此其他 schema 的表名前会加上其 schema 名;dbo 中的表保留原名。
报告记录: schemas_merged
NOT NULL columns with values that become NULL
去除 NOT NULL
值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。
报告记录: not_null_relaxed
views, functions, procedures, triggers, synonyms, types and partition functions
在报告中列出
它们的代码用 T-SQL 编写,因此会列出来,由你重新创建。
报告记录: not_converted_objects
scripts without CREATE TABLE (data only)
根据值推断类型
只含数据的脚本只有行,因此类型来自值。
报告记录: no_schema
expressions in INSERT values
NULL
不运行数据库的转换器无法对它们求值。
报告记录: expression_value
types IntoSQL doesn't know
CLOB
文本可以容纳任何值;请在导入后更改类型。
报告记录: unknown_type
NUL characters in text
保留
VARCHAR2 和 CLOB 可以包含 NUL 字符,因此值按原样复制。
text that isn't valid in its encoding
错误的字节变为替换字符
脚本是 UTF-8,每个值都必须是有效文本。
报告记录: invalid_utf8_replaced
text decoded twice (ñ)
按原样复制
脚本中存储的正是这些字符,因此按原样复制;报告会指出它们。
报告记录: mojibake_suspected
| 在 SQL Server 中 | 在 Oracle 中 | 报告记录 |
|---|---|---|
| bit | NUMBER(1),并带有限定 0 和 1 的 CHECKOracle 21c 没有 boolean 列类型;1 和 0 在每个版本上都能保留这些值。 | |
| char(n), varchar(n), nchar(n), nvarchar(n) | VARCHAR2(n CHAR),按字符计数长度按字符计算,与 SQL Server 的 nvarchar 相同。 | |
| varchar(max), nvarchar(max), text, ntext | 每个值都不超过 4000 字节时为 VARCHAR2(4000 CHAR),否则为 CLOBVARCHAR2 默认容纳 4000 字节,而 Oracle 无法为 CLOB 建立索引,因此值能容纳时文本保持为 VARCHAR2,否则为 CLOB。 | text_as_lob |
| empty strings and empty binary values | NULLOracle 将空字符串存储为 NULL,因此原为 ' 的值读回时为 NULL。报告会按列统计它们。 | empty_string_null |
| decimal(p,s), money, smallmoney | NUMBER(p,s);money 为 NUMBER(19,4)NUMBER 与 decimal 一样可容纳 38 位数字,因此每个值都是精确的。 | |
| datetime, datetime2, smalldatetime | TIMESTAMP(p)datetime 是挂钟读数,与 Oracle 的 TIMESTAMP 相同,后者最多保留九位秒的小数。date 变为 DATE,它也包含时间,但始终为午夜。 | |
| datetimeoffset | TIMESTAMP WITH TIME ZONE,保留每个值的偏移量Oracle 保留 -12:00 到 +14:00 的偏移量;偏移量超出 -12:00 的值以 UTC 写出,仍是同一时刻。 | |
| time | 从午夜起的 INTERVAL DAY TO SECONDOracle 没有表示一天中时间的类型;从午夜起的 interval 可容纳每个值。 | |
| dates that aren't valid | NULL不是日期的值变为 NULL,报告会统计其数量。 | date_out_of_range |
| json (SQL Server 2025) | CLOBJSON 值相同,Oracle 的 JSON 函数可以读取。 | |
| uniqueidentifier | VARCHAR2(36)标识符相同,以小写写出。 | |
| binary, varbinary, image | RAW(n) 和 BLOB字节仍然是字节。 | |
| geometry, geography | BLOB,存放 SQL Server 自己的字节SSMS 以 SQL Server 的序列化格式写出空间值,这些字节会逐字节保留。 | spatial_as_native |
| rowversion, timestamp | BLOB,空行版本由 SQL Server 自动设置,SSMS 不会为其写出值。 | rowversion_empty |
| xml, sql_variant, hierarchyid, vector | VARCHAR2 或 CLOB,存放每个值的文本Oracle 没有对应的类型;xml 和 sql_variant 保留每个值的文本,hierarchyid 保留其路径,例如 /1/2/。 | type_replaced |
| collations that ignore case (_CI_) | 精确比较除非另行指定,Oracle 精确比较文本,因此在 SQL Server 中不区分大小写的列现在会区分 Ana 和 ana。 | case_insensitive_collation |
| IDENTITY | GENERATED BY DEFAULT ON NULL AS IDENTITY,从最大的 id 之后继续复制的 id 按原样导入,计数器从最大的 id 之后继续。 | |
| CREATE SEQUENCE | CREATE SEQUENCE,选项相同Oracle 序列的工作方式相同。SSMS 不会写出序列当前到达的位置,因此它从创建时的值开始。 | |
| computed columns | 虚拟列Oracle 只在读取时计算生成列;PERSISTED 列变为值相同的虚拟列。 | stored_as_virtual |
| computed columns with functions Oracle lacks | 普通列无法写出的公式会留下一个普通列和一条报告记录;SSMS 不会为计算列写出值。 | generated_not_translated |
| defaults with functions Oracle lacks, and NEXT VALUE FOR | 无默认值无法写出的默认值会被省略,并在报告中列出。 | default_not_translated |
| CHECK constraints | 已转换检查约束相同,在导入每一行时检查。 | |
| CHECK constraints with functions Oracle lacks | 不创建无法写出的检查约束会被省略,并在报告中列出。 | check_skipped |
| CHECK constraints added WITH NOCHECK or disabled | 不创建SQL Server 不执行这些检查约束,因此行可能违反它们。 | check_not_enforced |
| filtered and columnstore indexes, and indexes on CLOB columns | 不创建IntoSQL 在整列上写出 b-tree 索引;其他索引请在导入后重新创建。 | 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 |
| names over 128 bytes | 缩短Oracle 12.2 及更高版本的名称最多 128 字节;SQL Server 的名称最多 128 个字符。 | name_shortened |
| foreign keys to tables the dump doesn't create | 不创建外键无法引用不存在的表。 | foreign_key_skipped |
| tables on a partition scheme | 单个表,包含每个分区的行行保持不变;如有需要,请在 Oracle 中重新对该表分区。 | partitions_dropped |
| temporal tables (SYSTEM_VERSIONING) | 两个表:当前行和历史记录表及其历史表作为普通表复制;Oracle 不会自动保留历史记录。 | system_versioning_dropped |
| schemas | 单个 schema,每个表名前加上其 schema 名脚本会导入到运行它的用户的 schema 中,因此其他 schema 的表名前会加上其 schema 名;dbo 中的表保留原名。 | schemas_merged |
| NOT NULL columns with values that become NULL | 去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。 | not_null_relaxed |
| views, functions, procedures, triggers, synonyms, types and partition functions | 在报告中列出它们的代码用 T-SQL 编写,因此会列出来,由你重新创建。 | not_converted_objects |
| scripts without CREATE TABLE (data only) | 根据值推断类型只含数据的脚本只有行,因此类型来自值。 | no_schema |
| expressions in INSERT values | NULL不运行数据库的转换器无法对它们求值。 | expression_value |
| types IntoSQL doesn't know | CLOB文本可以容纳任何值;请在导入后更改类型。 | unknown_type |
| NUL characters in text | 保留VARCHAR2 和 CLOB 可以包含 NUL 字符,因此值按原样复制。 | |
| text that isn't valid in its encoding | 错误的字节变为替换字符脚本是 UTF-8,每个值都必须是有效文本。 | invalid_utf8_replaced |
| text decoded twice (ñ) | 按原样复制脚本中存储的正是这些字符,因此按原样复制;报告会指出它们。 | mojibake_suspected |
将脚本导入 Oracle
脚本先创建表、复制行,再添加键、索引和外键,因此导入速度快,并会在遇到第一个错误时停止。
NLS_LANG=.AL32UTF8 sqlplus -S -L user@//localhost/mydb @sql_server.oracle.sql外键以 ENABLE NOVALIDATE 添加,因此新行会立即检查,之后再验证。如果某个旧行指向不存在的行,脚本会说明是哪个约束,并让它保持未验证状态,而不是中途失败。
对于较大的转储,请在数据库所在的机器上运行 SQL*Plus。脚本每 100 行提交一次。运行脚本的用户需要 CREATE TABLE、CREATE SEQUENCE、CREATE TRIGGER 和 CREATE PROCEDURE 权限,以及其表空间上的配额。
如何转储 SQL Server 以获得最佳结果
转换器读取转储中的内容。以下选项能让转储保留更多数据:
- 在 SSMS 中右键单击数据库,依次选择"任务"、"生成脚本",然后选择表。dbatools、mssql-scripter 以及其他基于同一库的工具也会写出相同的脚本。
- 在"高级"中,将"要编写脚本的数据的类型"设为"架构和数据"。只含架构的脚本中,表没有行。
- 以 SSMS 默认的 Unicode 或 UTF-8 保存:两者的读取方式相同,也支持 gzip。
哪些内容不会转换
视图、函数、存储过程、触发器、同义词、规则和安全策略都是用 T-SQL 编写的。它们会按名称在报告中列出,绝不会被悄悄丢弃,以便你为 Oracle 重写。
关于 SQL Server 转 Oracle 的问题
这里没有你要的答案?请通过联系页面给我们留言,会有专人回复。 联系页面
我的转储会被上传吗?
不会。转换器在你的浏览器中运行,分块从磁盘读取转储,因此转储不会离开你的电脑。桌面应用和命令行也在你的电脑上转换。
转储可以多大?
转换器从不把整个转储放进内存:它按范围读取文件两次,边读边写出脚本。几 GB 的转储可以在浏览器、桌面应用和命令行中转换。
它能读取 SSMS 保存的 UTF-16 文件吗?
能。SSMS 默认以 UTF-16 保存脚本,而转换器同样能读取 UTF-16 和 UTF-8,无论是否经过 gzip 压缩,批处理之间的 GO 也能识别。
脚本可以来自哪些 SQL Server 版本?
由 SSMS、dbatools 或 mssql-scripter 从 SQL Server 2017 及更高版本写出的脚本。每个测试脚本都来自 SQL Server 2017、2022 和 2025,并与其源数据逐个单元格比较。
IDENTITY 值会保留吗?
会。IDENTITY 列变为标识列,每一行都保留其 id,下一个 id 从最大的 id 之后继续。序列以相同的选项创建。
money、datetimeoffset 和其他 SQL Server 类型会怎样处理?
bit 变为 NUMBER(1),datetime2 变为保留每一位的 TIMESTAMP,datetimeoffset 变为带偏移量的 TIMESTAMP WITH TIME ZONE,nvarchar(max) 在其值放不进 VARCHAR2 时变为 CLOB。空字符串按 Oracle 的存储方式变为 NULL,报告会统计其数量。
存储过程、触发器和视图怎么处理?
它们会按名称在报告中列出,但不会转换,因为每种数据库都用自己的语言编写它们。
脚本可以导入哪些 Oracle 版本?
Oracle 19c 及更高版本。每个测试脚本都能在 21c 和 23ai 上导入,如有问题会在第一个错误处停止。