Convert SQL Server to Oracle
Drop the script SSMS's Generate Scripts writes with schema and data, as .sql or .sql.gz, UTF-16 or UTF-8, and download a script for Oracle with the same tables, keys, indexes and data. Every value Oracle can't hold as it is, and every object it doesn't carry, is listed in the report.
18 rows converted, 2 fixes, none rejected
Tables in shop_2024_sqlserver.sqlSQL Server dump · written by SQL Server Management Objects · utf-8
orderswritten as ORDERS10 rows1
11 columns 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) customerswritten as CUSTOMERS8 rows1
8 columns 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)
Not converted
- View paid_orders
- Trigger orders_no_negative
Each database writes these in its own language. Rewrite them for 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;
Fixes
- "total_with_tax" of orders is stored in the source; Oracle computes it on read.
- "row_version" of customers is a row version SQL Server sets by itself; the dump has no values for it, so it is RAW(8) and empty.
Needs a look
- 6 text columns compare ignoring case in the source; Oracle compares exactly, so unique keys and searches may behave differently. Use a case-insensitive collation or column type on the columns that need it.
- 2 views, triggers, routines or events aren't converted; the report lists each one. Recreate them by hand in Oracle.
- The check check1 on orders can't be written for Oracle (function isjson), so it isn't created. Write the check by hand if it matters.
- The index idx_orders_pending on orders uses an expression that can't be written for Oracle (filtered index), so it isn't created. Create the index by hand if queries need it.
- The default of "order_ref" in orders can't be written for Oracle (function uuid), so the column has no default. Add the default by hand if new rows need it.
Your file is converted on this computer and never uploaded.
Other samples:
What changes from SQL Server to Oracle
Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.
bit
NUMBER(1) with a CHECK of 0 and 1
Oracle 21c has no boolean column type; 1 and 0 keep the values on every version.
char(n), varchar(n), nchar(n), nvarchar(n)
VARCHAR2(n CHAR), counting characters
Lengths count characters as SQL Server's nvarchar does.
varchar(max), nvarchar(max), text, ntext
VARCHAR2(4000 CHAR) when every value fits 4000 bytes, else CLOB
A VARCHAR2 holds 4000 bytes by default and Oracle can't index a CLOB, so text keeps VARCHAR2 when its values fit and is a CLOB when they don't.
Report line: text_as_lob
empty strings and empty binary values
NULL
Oracle stores an empty string as NULL, so a value that was ' reads back as NULL. The report counts them per column.
Report line: empty_string_null
decimal(p,s), money, smallmoney
NUMBER(p,s); money as NUMBER(19,4)
NUMBER holds 38 digits, as decimal does, so every value is exact.
datetime, datetime2, smalldatetime
TIMESTAMP(p)
A datetime is a wall-clock reading, like Oracle's TIMESTAMP, which keeps up to nine digits of a second. date becomes DATE, which also holds a time of day that stays at midnight.
datetimeoffset
TIMESTAMP WITH TIME ZONE, with each value's offset
Oracle keeps the offsets from -12:00 to +14:00; a value with an offset beyond -12:00 is written in UTC, the same instant.
time
INTERVAL DAY TO SECOND from midnight
Oracle has no time-of-day type; an interval from midnight holds every value.
dates that aren't valid
NULL
A value that isn't a date becomes NULL and the report counts it.
Report line: date_out_of_range
json (SQL Server 2025)
CLOB
The same JSON values, which Oracle's JSON functions read.
uniqueidentifier
VARCHAR2(36)
The same identifiers, written in lowercase.
binary, varbinary, image
RAW(n) and BLOB
Bytes stay bytes.
geometry, geography
BLOB with SQL Server's own bytes
SSMS writes spatial values in SQL Server's serialization, which is kept byte for byte.
Report line: spatial_as_native
rowversion, timestamp
BLOB, empty
SQL Server sets a row version by itself and SSMS scripts no values for it.
Report line: rowversion_empty
xml, sql_variant, hierarchyid, vector
VARCHAR2 or CLOB with each value's text
Oracle has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/.
Report line: type_replaced
collations that ignore case (_CI_)
exact comparison
Oracle compares text exactly unless told otherwise, so columns that ignored case in SQL Server now tell Ana and ana apart.
Report line: case_insensitive_collation
IDENTITY
GENERATED BY DEFAULT ON NULL AS IDENTITY, continuing after the highest id
The copied ids load as they are, and the counter continues after the highest one.
CREATE SEQUENCE
CREATE SEQUENCE with the same options
Oracle sequences work the same way. SSMS doesn't script where a sequence has reached, so it starts where it was created.
computed columns
virtual columns
Oracle computes generated columns on read only; PERSISTED columns become virtual with the same values.
Report line: stored_as_virtual
computed columns with functions Oracle lacks
a plain column
A formula that can't be written leaves a plain column and a report line; SSMS scripts no values for computed columns.
Report line: generated_not_translated
defaults with functions Oracle lacks, and NEXT VALUE FOR
no default
A default that can't be written is left out and reported.
Report line: default_not_translated
CHECK constraints
translated
The same checks, tested as each row loads.
CHECK constraints with functions Oracle lacks
not created
A check that can't be written is left out and reported.
Report line: check_skipped
CHECK constraints added WITH NOCHECK or disabled
not created
SQL Server doesn't apply these checks, so the rows may break them.
Report line: check_not_enforced
filtered and columnstore indexes, and indexes on CLOB columns
not created
IntoSQL writes b-tree indexes on whole columns; create the others again after loading.
Report line: index_skipped
foreign keys
after the data ENABLE NOVALIDATE, then validated with a warning on orphans
The key checks new rows at once and is validated last; rows without a parent leave it unvalidated with a warning instead of stopping the load.
ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT
dropped
Oracle's foreign keys act on delete only, with CASCADE or SET NULL.
Report line: foreign_key_action_unsupported
names over 128 bytes
shortened
Oracle 12.2 and later names hold 128 bytes; SQL Server's hold 128 characters.
Report line: name_shortened
foreign keys to tables the dump doesn't create
not created
A key can't name a table that isn't there.
Report line: foreign_key_skipped
tables on a partition scheme
one table with every partition's rows
The rows are the same; partition the table again in Oracle if it needs it.
Report line: partitions_dropped
temporal tables (SYSTEM_VERSIONING)
two tables: the current rows and the history
The table and its history table are copied as plain tables; Oracle doesn't keep the history by itself.
Report line: system_versioning_dropped
schemas
one schema, tables named after their schema first
The script loads into the schema of the user who runs it, so tables of different schemas get their schema's name in front; tables in dbo keep their names.
Report line: schemas_merged
NOT NULL columns with values that become NULL
NOT NULL dropped
A column whose values had to become NULL can't keep NOT NULL, or the load would stop.
Report line: not_null_relaxed
views, functions, procedures, triggers, synonyms, types and partition functions
listed in the report
Their code is written in T-SQL, so they are listed for you to recreate.
Report line: not_converted_objects
scripts without CREATE TABLE (data only)
types read from the values
A data-only script has only rows, so the types come from the values.
Report line: no_schema
expressions in INSERT values
NULL
A converter that doesn't run a database can't evaluate them.
Report line: expression_value
types IntoSQL doesn't know
CLOB
Text holds any value; change the type after loading.
Report line: unknown_type
NUL characters in text
kept
VARCHAR2 and CLOB can hold a NUL character, so the value is copied as it is.
text that isn't valid in its encoding
the bad bytes as the replacement character
The script is UTF-8 and every value has to be valid text.
Report line: invalid_utf8_replaced
text decoded twice (ñ)
copied as it is
The script holds exactly these characters, so they are copied; the report points them out.
Report line: mojibake_suspected
| In SQL Server | In Oracle | Report line |
|---|---|---|
| bit | NUMBER(1) with a CHECK of 0 and 1Oracle 21c has no boolean column type; 1 and 0 keep the values on every version. | |
| char(n), varchar(n), nchar(n), nvarchar(n) | VARCHAR2(n CHAR), counting charactersLengths count characters as SQL Server's nvarchar does. | |
| varchar(max), nvarchar(max), text, ntext | VARCHAR2(4000 CHAR) when every value fits 4000 bytes, else CLOBA VARCHAR2 holds 4000 bytes by default and Oracle can't index a CLOB, so text keeps VARCHAR2 when its values fit and is a CLOB when they don't. | text_as_lob |
| empty strings and empty binary values | NULLOracle stores an empty string as NULL, so a value that was ' reads back as NULL. The report counts them per column. | empty_string_null |
| decimal(p,s), money, smallmoney | NUMBER(p,s); money as NUMBER(19,4)NUMBER holds 38 digits, as decimal does, so every value is exact. | |
| datetime, datetime2, smalldatetime | TIMESTAMP(p)A datetime is a wall-clock reading, like Oracle's TIMESTAMP, which keeps up to nine digits of a second. date becomes DATE, which also holds a time of day that stays at midnight. | |
| datetimeoffset | TIMESTAMP WITH TIME ZONE, with each value's offsetOracle keeps the offsets from -12:00 to +14:00; a value with an offset beyond -12:00 is written in UTC, the same instant. | |
| time | INTERVAL DAY TO SECOND from midnightOracle has no time-of-day type; an interval from midnight holds every value. | |
| dates that aren't valid | NULLA value that isn't a date becomes NULL and the report counts it. | date_out_of_range |
| json (SQL Server 2025) | CLOBThe same JSON values, which Oracle's JSON functions read. | |
| uniqueidentifier | VARCHAR2(36)The same identifiers, written in lowercase. | |
| binary, varbinary, image | RAW(n) and BLOBBytes stay bytes. | |
| geometry, geography | BLOB with SQL Server's own bytesSSMS writes spatial values in SQL Server's serialization, which is kept byte for byte. | spatial_as_native |
| rowversion, timestamp | BLOB, emptySQL Server sets a row version by itself and SSMS scripts no values for it. | rowversion_empty |
| xml, sql_variant, hierarchyid, vector | VARCHAR2 or CLOB with each value's textOracle has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/. | type_replaced |
| collations that ignore case (_CI_) | exact comparisonOracle compares text exactly unless told otherwise, so columns that ignored case in SQL Server now tell Ana and ana apart. | case_insensitive_collation |
| IDENTITY | GENERATED BY DEFAULT ON NULL AS IDENTITY, continuing after the highest idThe copied ids load as they are, and the counter continues after the highest one. | |
| CREATE SEQUENCE | CREATE SEQUENCE with the same optionsOracle sequences work the same way. SSMS doesn't script where a sequence has reached, so it starts where it was created. | |
| computed columns | virtual columnsOracle computes generated columns on read only; PERSISTED columns become virtual with the same values. | stored_as_virtual |
| computed columns with functions Oracle lacks | a plain columnA formula that can't be written leaves a plain column and a report line; SSMS scripts no values for computed columns. | generated_not_translated |
| defaults with functions Oracle lacks, and NEXT VALUE FOR | no defaultA default that can't be written is left out and reported. | default_not_translated |
| CHECK constraints | translatedThe same checks, tested as each row loads. | |
| CHECK constraints with functions Oracle lacks | not createdA check that can't be written is left out and reported. | check_skipped |
| CHECK constraints added WITH NOCHECK or disabled | not createdSQL Server doesn't apply these checks, so the rows may break them. | check_not_enforced |
| filtered and columnstore indexes, and indexes on CLOB columns | not createdIntoSQL writes b-tree indexes on whole columns; create the others again after loading. | index_skipped |
| foreign keys | after the data ENABLE NOVALIDATE, then validated with a warning on orphansThe key checks new rows at once and is validated last; rows without a parent leave it unvalidated with a warning instead of stopping the load. | |
| ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT | droppedOracle's foreign keys act on delete only, with CASCADE or SET NULL. | foreign_key_action_unsupported |
| names over 128 bytes | shortenedOracle 12.2 and later names hold 128 bytes; SQL Server's hold 128 characters. | name_shortened |
| foreign keys to tables the dump doesn't create | not createdA key can't name a table that isn't there. | foreign_key_skipped |
| tables on a partition scheme | one table with every partition's rowsThe rows are the same; partition the table again in Oracle if it needs it. | partitions_dropped |
| temporal tables (SYSTEM_VERSIONING) | two tables: the current rows and the historyThe table and its history table are copied as plain tables; Oracle doesn't keep the history by itself. | system_versioning_dropped |
| schemas | one schema, tables named after their schema firstThe script loads into the schema of the user who runs it, so tables of different schemas get their schema's name in front; tables in dbo keep their names. | schemas_merged |
| NOT NULL columns with values that become NULL | NOT NULL droppedA column whose values had to become NULL can't keep NOT NULL, or the load would stop. | not_null_relaxed |
| views, functions, procedures, triggers, synonyms, types and partition functions | listed in the reportTheir code is written in T-SQL, so they are listed for you to recreate. | not_converted_objects |
| scripts without CREATE TABLE (data only) | types read from the valuesA data-only script has only rows, so the types come from the values. | no_schema |
| expressions in INSERT values | NULLA converter that doesn't run a database can't evaluate them. | expression_value |
| types IntoSQL doesn't know | CLOBText holds any value; change the type after loading. | unknown_type |
| NUL characters in text | keptVARCHAR2 and CLOB can hold a NUL character, so the value is copied as it is. | |
| text that isn't valid in its encoding | the bad bytes as the replacement characterThe script is UTF-8 and every value has to be valid text. | invalid_utf8_replaced |
| text decoded twice (ñ) | copied as it isThe script holds exactly these characters, so they are copied; the report points them out. | mojibake_suspected |
Load the script into Oracle
The script creates the tables, copies the rows, then adds keys, indexes and foreign keys, so it loads fast and stops at the first error.
NLS_LANG=.AL32UTF8 sqlplus -S -L user@//localhost/mydb @sql_server.oracle.sqlForeign keys are added ENABLE NOVALIDATE, so new rows are checked at once, and then validated. If an old row points to a row that doesn't exist, the script says which constraint and leaves it unvalidated, instead of failing half way.
For a large dump, run SQL*Plus on the same machine as the database. The script commits every 100 rows. The user that runs it needs CREATE TABLE, CREATE SEQUENCE, CREATE TRIGGER and CREATE PROCEDURE, and quota on its tablespace.
Dump SQL Server for the best result
The converter reads what the dump holds. These options keep more of your data in it:
- In SSMS, right-click the database, Tasks, Generate Scripts, and pick the tables. dbatools, mssql-scripter and other tools built on the same library write the same script.
- Under Advanced, set Types of data to script to Schema and data. A script of the schema alone has tables without rows.
- Save it as Unicode, SSMS's default, or as UTF-8: both are read the same way, and gzip works too.
What isn't converted
Views, functions, procedures, triggers, synonyms, rules and security policies are written in T-SQL. They are listed in the report by name, never dropped silently, so you can rewrite them for Oracle.
Questions about SQL Server to Oracle
Not answered here? Write to us from the contact page and a person replies. Contact page
Is my dump uploaded?
No. The converter runs in your browser and reads the dump from your disk in pieces, so it never leaves your computer. The desktop app and the command line convert on your computer too.
How large can the dump be?
The converter never holds the whole dump in memory: it reads the file twice, by ranges, and writes the script as it goes. Multi-gigabyte dumps convert in the browser, in the desktop app and with the command line.
Does it read the UTF-16 file SSMS saves?
Yes. SSMS saves scripts as UTF-16 by default, and the converter reads UTF-16 and UTF-8 alike, gzipped or not, with GO between batches.
Which SQL Server versions can the script come from?
Scripts that SSMS, dbatools or mssql-scripter write from SQL Server 2017 on. Every test script comes from SQL Server 2017, 2022 and 2025 and is compared with its source cell by cell.
Are IDENTITY values kept?
Yes. IDENTITY columns become identity columns, every row keeps its id, and the next id continues after the highest one. Sequences are created with the same options.
What happens to money, datetimeoffset and other SQL Server types?
bit becomes NUMBER(1), datetime2 TIMESTAMP with every digit, datetimeoffset TIMESTAMP WITH TIME ZONE with its offset, and nvarchar(max) CLOB when its values don't fit a VARCHAR2. Empty strings become NULL, as Oracle stores them, and the report counts them.
What about procedures, triggers and views?
They are listed in the report by name and not converted, because each database writes them in its own language.
Which Oracle versions does the script load on?
Oracle 19c and later. Every test script loads on 21c and 23ai, stopping at the first error if anything goes wrong.