Skip to content
IntoSQL home

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.

Your file
sqlshop_2024_sqlserver.sqlSample file, 11 kB
Your database

18 rows converted, 2 fixes, none rejected

Show

Tables in shop_2024_sqlserver.sqlSQL Server dump · written by SQL Server Management Objects · utf-8

  • orderswritten as ORDERS10 rows1
    11 columns
    IDNUMBER(10) not null
    CUSTOMER_IDNUMBER(10) not null
    STATUSVARCHAR2(10 CHAR) not null
    TOTALNUMBER(19,4) not null
    ITEMSNUMBER(3) not null
    ORDERED_ATTIMESTAMP(0) not null
    SHIPPED_ATTIMESTAMP(7)
    NOTECLOB
    DETAILSCLOB
    ORDER_REFVARCHAR2(36 CHAR) not null
    TOTAL_WITH_TAXNUMBER(12,2)
  • customerswritten as CUSTOMERS8 rows1
    8 columns
    IDNUMBER(10) not null
    NAMEVARCHAR2(100 CHAR) not null
    EMAILVARCHAR2(200 CHAR) not null
    COUNTRYVARCHAR2(2 CHAR) not null
    IS_ACTIVENUMBER(1) not null
    SIGNED_UPDATE
    CREATED_ATTIMESTAMP(0) WITH TIME ZONE not null
    ROW_VERSIONRAW(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

  1. "total_with_tax" of orders is stored in the source; Oracle computes it on read.
  2. "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 ServerIn OracleReport line
bitNUMBER(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, ntextVARCHAR2(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 valuesNULLOracle 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, smallmoneyNUMBER(p,s); money as NUMBER(19,4)NUMBER holds 38 digits, as decimal does, so every value is exact.
datetime, datetime2, smalldatetimeTIMESTAMP(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.
datetimeoffsetTIMESTAMP 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.
timeINTERVAL DAY TO SECOND from midnightOracle has no time-of-day type; an interval from midnight holds every value.
dates that aren't validNULLA 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.
uniqueidentifierVARCHAR2(36)The same identifiers, written in lowercase.
binary, varbinary, imageRAW(n) and BLOBBytes stay bytes.
geometry, geographyBLOB with SQL Server's own bytesSSMS writes spatial values in SQL Server's serialization, which is kept byte for byte.spatial_as_native
rowversion, timestampBLOB, emptySQL Server sets a row version by itself and SSMS scripts no values for it.rowversion_empty
xml, sql_variant, hierarchyid, vectorVARCHAR2 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
IDENTITYGENERATED 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 SEQUENCECREATE 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 columnsvirtual columnsOracle computes generated columns on read only; PERSISTED columns become virtual with the same values.stored_as_virtual
computed columns with functions Oracle lacksa 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 FORno defaultA default that can't be written is left out and reported.default_not_translated
CHECK constraintstranslatedThe same checks, tested as each row loads.
CHECK constraints with functions Oracle lacksnot createdA check that can't be written is left out and reported.check_skipped
CHECK constraints added WITH NOCHECK or disablednot createdSQL Server doesn't apply these checks, so the rows may break them.check_not_enforced
filtered and columnstore indexes, and indexes on CLOB columnsnot createdIntoSQL writes b-tree indexes on whole columns; create the others again after loading.index_skipped
foreign keysafter 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 DEFAULTdroppedOracle's foreign keys act on delete only, with CASCADE or SET NULL.foreign_key_action_unsupported
names over 128 bytesshortenedOracle 12.2 and later names hold 128 bytes; SQL Server's hold 128 characters.name_shortened
foreign keys to tables the dump doesn't createnot createdA key can't name a table that isn't there.foreign_key_skipped
tables on a partition schemeone 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
schemasone 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 NULLNOT 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 functionslisted 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 valuesNULLA converter that doesn't run a database can't evaluate them.expression_value
types IntoSQL doesn't knowCLOBText holds any value; change the type after loading.unknown_type
NUL characters in textkeptVARCHAR2 and CLOB can hold a NUL character, so the value is copied as it is.
text that isn't valid in its encodingthe 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.sql

Foreign 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.