Move an Oracle export to an older or newer Oracle
Drop the .sql file SQL Developer's Database Export or SQLcl writes, with its tables and Insert statements, and download a script that loads on Oracle 19c and later. NUMBER, DATE, TIMESTAMP, LOBs, identities and sequences stay as they are, XMLTYPE and JSON keep their text in a CLOB, and every change is listed in the report.
18 rows converted, no fixes, none rejected
Tables in shop_2024_oracle.sqlOracle dump · written by SQL Developer · utf-8
customerswritten as CUSTOMERS8 rows
7 columns ID NUMBER(10,0) not null NAME VARCHAR2(100 CHAR) not null EMAIL VARCHAR2(150 CHAR) not null COUNTRY CHAR(2 BYTE) not null IS_ACTIVE NUMBER(1,0) not null SIGNED_UP DATE CREATED_AT TIMESTAMP(0) not null orderswritten as ORDERS10 rows
9 columns ID NUMBER(10,0) not null CUSTOMER_ID NUMBER(10,0) not null INVOICE NUMBER(10,0) STATUS VARCHAR2(10 CHAR) not null TOTAL NUMBER(10,2) not null ITEMS NUMBER(5,0) not null ORDERED_AT DATE not null SHIPPED_AT DATE NOTE VARCHAR2(255 CHAR)
-- Written by IntoSQL from a Oracle 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 SEQUENCE "INVOICE_NUMBERS" START WITH 1020 INCREMENT BY 1 MINVALUE 1;
CREATE TABLE "CUSTOMERS" (
"ID" NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1),
"NAME" VARCHAR2(100 CHAR) NOT NULL,
"EMAIL" VARCHAR2(150 CHAR) NOT NULL,
"COUNTRY" CHAR(2 BYTE) DEFAULT 'US' NOT NULL,
"IS_ACTIVE" NUMBER(1,0) DEFAULT 1 NOT NULL,
"SIGNED_UP" DATE,
"CREATED_AT" TIMESTAMP(0) DEFAULT LOCALTIMESTAMP NOT NULL,
CONSTRAINT "CHECK1" CHECK ("IS_ACTIVE" IN (0, 1))
);
CREATE TABLE "ORDERS" (
"ID" NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1),
"CUSTOMER_ID" NUMBER(10,0) NOT NULL,
"INVOICE" NUMBER(10,0) DEFAULT ("INVOICE_NUMBERS".NEXTVAL),
"STATUS" VARCHAR2(10 CHAR) DEFAULT 'pending' NOT NULL,
"TOTAL" NUMBER(10,2) NOT NULL,
"ITEMS" NUMBER(5,0) DEFAULT 1 NOT NULL,
"ORDERED_AT" DATE NOT NULL,
"SHIPPED_AT" DATE,
"NOTE" VARCHAR2(255 CHAR),
CONSTRAINT "CHECK1_2" CHECK ("STATUS" IN ('pending', 'paid', 'shipped', 'cancelled'))
);
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
1,
'Ana Martínez',
'ana@example.com',
'MX',
1,
TIMESTAMP '2023-01-15 00:00:00',
TIMESTAMP '2023-01-15 10:24:00.000000000'
);
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,
TIMESTAMP '2023-02-03 00:00:00',
TIMESTAMP '2023-02-03 08:11:42.000000000'
);
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,
TIMESTAMP '2023-03-21 00:00:00',
TIMESTAMP '2023-03-21 19:05:17.000000000'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
4,
'李雷',
'lilei@example.cn',
'CN',
1,
TIMESTAMP '2023-05-09 00:00:00',
TIMESTAMP '2023-05-09 02:40:00.000000000'
);
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 14:00:00.000000000'
);
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,
TIMESTAMP '2024-01-02 00:00:00',
TIMESTAMP '2024-01-02 09:30:00.000000000'
);
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,
TIMESTAMP '2024-02-29 00:00:00',
TIMESTAMP '2024-02-29 23:59:59.000000000'
);
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,
TIMESTAMP '2024-04-12 00:00:00',
TIMESTAMP '2024-04-12 12:12:12.000000000'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
1,
1,
1000,
'shipped',
129.9,
3,
TIMESTAMP '2024-03-01 10:00:00',
TIMESTAMP '2024-03-02 16:30:00',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
2,
2,
1001,
'paid',
54,
1,
TIMESTAMP '2024-03-02 11:15:00',
NULL,
'Gift wrap 🎁'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
3,
1,
1002,
'cancelled',
12.5,
1,
TIMESTAMP '2024-03-03 09:45:00',
NULL,
'Customer changed their mind'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
4,
4,
1003,
'shipped',
980,
12,
TIMESTAMP '2024-03-05 02:10:00',
TIMESTAMP '2024-03-07 08:00:00',
'请小心轻放'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
5,
5,
1004,
'pending',
45.75,
2,
TIMESTAMP '2024-03-08 17:20:00',
NULL,
'Leave at the door; ring twice'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
6,
6,
1005,
'shipped',
310.4,
5,
TIMESTAMP '2024-03-10 13:00:00',
TIMESTAMP '2024-03-11 09:00:00',
'Fragile: "glass"'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
7,
8,
1006,
'paid',
76.2,
2,
TIMESTAMP '2024-03-11 18:45:00',
NULL,
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
8,
2,
1007,
'paid',
19.99,
1,
TIMESTAMP '2024-03-12 08:00:00',
NULL,
'Paid at the counter'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
9,
3,
1008,
'shipped',
64,
2,
TIMESTAMP '2024-03-14 15:30:00',
NULL,
'Shipping date never recorded'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"INVOICE",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE"
) VALUES (
10,
7,
1009,
'paid',
1499,
1,
TIMESTAMP '2024-03-15 21:05:00',
NULL,
'C:\Users\ravi\invoice.pdf'
);
COMMIT;
END;
/
-- Keys, indexes, foreign keys, counters and triggers, after the rows.
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "PK_CUSTOMERS" PRIMARY KEY ("ID");
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "UQ_CUSTOMERS_CUSTOMERS_EMAIL_UK" UNIQUE ("EMAIL");
ALTER TABLE "ORDERS" ADD CONSTRAINT "PK_ORDERS" PRIMARY KEY ("ID");
CREATE INDEX "IX_ORDERS_ORDERS_STATUS_DATE_IX" ON "ORDERS" ("STATUS", "ORDERED_AT");
ALTER TABLE "ORDERS" ADD CONSTRAINT "ORDERS_CUSTOMER_FK" FOREIGN KEY ("CUSTOMER_ID") REFERENCES "CUSTOMERS" ("ID") ENABLE NOVALIDATE;
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" ENABLE VALIDATE CONSTRAINT "ORDERS_CUSTOMER_FK"';
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Foreign key ' || '"ORDERS_CUSTOMER_FK"' || ' 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 < 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';
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;
/
DROP FUNCTION "INTOSQL_CLOB";
DROP FUNCTION "INTOSQL_BLOB";
COMMIT;
EXIT;
Fixes
Nothing needed fixing.
Your file is converted on this computer and never uploaded.
Other samples:
What changes between the file and the script
Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.
BOOLEAN (23ai)
BOOLEAN on 23ai, else NUMBER(1) with a CHECK of 0 and 1
TRUE and FALSE stay true and false.
VARCHAR2(n BYTE|CHAR), NVARCHAR2(n), CHAR(n), NCHAR(n), CLOB, NCLOB, LONG
the same types, with their BYTE or CHAR lengths
The same text types; LONG becomes CLOB, which every version since 8 prefers.
VARCHAR2 and NVARCHAR2 past what the target's own types hold
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
NUMBER, NUMBER(p,s), FLOAT(b)
the same NUMBER types
The same NUMBER and FLOAT types, with every digit.
INTERVAL YEAR TO MONTH, INTERVAL DAY TO SECOND
the same INTERVAL types
The same intervals.
DATE, TIMESTAMP(p)
DATE and TIMESTAMP(p), as declared
The same types, so every date keeps its time to the second.
TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE
the same types
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.
JSON (21c), and CLOB holding JSON
JSON on 21c and later, else CLOB
The same JSON values; a SQL NULL the export wrote as 'null' stays NULL.
BLOB, RAW(n), LONG RAW
the same types; LONG RAW as BLOB
Bytes stay bytes.
ROWID, UROWID, XMLTYPE, SDO_GEOMETRY
CLOB with their text
XMLTYPE, ROWID and UROWID keep their text in a CLOB, so the script loads on every version from 19c.
Report line: type_replaced
collations that ignore case (BINARY_CI)
exact comparison
Column collations aren't written, so a column that ignored case (BINARY_CI) now tells Ana and ana apart.
Report line: case_insensitive_collation
identity columns, and a sequence a BEFORE INSERT trigger assigns
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. A column a trigger fills from a sequence (the way before 12c) is an identity too.
identity columns the target can't count
a plain integer
Oracle counts one identity column per table; any other keeps its values without a counter.
Report line: counter_dropped
CREATE SEQUENCE
CREATE SEQUENCE with the same options
The export writes where a sequence has reached as its START WITH, so it continues from there.
virtual columns (GENERATED ALWAYS AS ... VIRTUAL)
virtual columns
The same virtual columns.
Report line: stored_as_virtual
virtual columns with functions IntoSQL doesn't write, and the || operator
a plain column
A formula that can't be written for Oracle leaves a plain column and a report line; the values the export wrote for virtual columns are left out, and Oracle has none to compute.
Report line: generated_not_translated
defaults with functions IntoSQL doesn't write (SYS_GUID(), ||)
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 IntoSQL doesn't write
not created
A check that can't be written is left out and reported.
Report line: check_skipped
CHECK constraints that are DISABLE or ENABLE NOVALIDATE
not created
Oracle doesn't apply these checks to the rows already there, so the rows may break them.
Report line: check_not_enforced
function-based, bitmap and domain indexes
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 DELETE CASCADE that cascades in a cycle or along a second path
dropped
Oracle's foreign keys act on delete only, with CASCADE or SET NULL.
Report line: foreign_key_action_unsupported
foreign keys to tables the export doesn't create, and DISABLE or NOVALIDATE ones
not created
A key can't name a table that isn't there, and one Oracle doesn't check may have rows without a parent.
Report line: foreign_key_skipped
names longer than Oracle 12.1 allows
shortened
Oracle names hold 128 bytes from 12.2; a longer name is shortened.
Report line: name_shortened
partitioned tables
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
several schemas in one export
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; an export of one schema keeps its tables' 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, synonyms, types, packages, procedures, functions and other triggers
listed in the report
Their code is written in PL/SQL, so they are listed for you to recreate.
Report line: not_converted_objects
exports without CREATE TABLE (data only)
types read from the values
A data-only export has only rows, so the types come from the values.
Report line: no_schema
expressions in INSERT values other than the ones exports write
NULL
A converter that doesn't run a database can't evaluate them; to_date, to_timestamp, TO_CLOB, HEXTORAW and the others exports write are read.
Report line: expression_value
types IntoSQL doesn't know, and object types
CLOB
Text holds any value; change the type after loading.
Report line: unknown_type
dates before year 1 and time zones given as region names
NULL
A value that isn't a date becomes NULL and the report counts it.
Report line: date_out_of_range
NaN and Infinity in BINARY_FLOAT and BINARY_DOUBLE
NaN and Infinity kept
Oracle's floating-point types hold NaN and infinity.
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 the file | In the script | Report line |
|---|---|---|
| BOOLEAN (23ai) | BOOLEAN on 23ai, else NUMBER(1) with a CHECK of 0 and 1TRUE and FALSE stay true and false. | |
| VARCHAR2(n BYTE|CHAR), NVARCHAR2(n), CHAR(n), NCHAR(n), CLOB, NCLOB, LONG | the same types, with their BYTE or CHAR lengthsThe same text types; LONG becomes CLOB, which every version since 8 prefers. | |
| VARCHAR2 and NVARCHAR2 past what the target's own types hold | 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 |
| NUMBER, NUMBER(p,s), FLOAT(b) | the same NUMBER typesThe same NUMBER and FLOAT types, with every digit. | |
| INTERVAL YEAR TO MONTH, INTERVAL DAY TO SECOND | the same INTERVAL typesThe same intervals. | |
| DATE, TIMESTAMP(p) | DATE and TIMESTAMP(p), as declaredThe same types, so every date keeps its time to the second. | |
| TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE | the same typesOracle keeps the offsets from -12:00 to +14:00; a value with an offset beyond -12:00 is written in UTC, the same instant. | |
| JSON (21c), and CLOB holding JSON | JSON on 21c and later, else CLOBThe same JSON values; a SQL NULL the export wrote as 'null' stays NULL. | |
| BLOB, RAW(n), LONG RAW | the same types; LONG RAW as BLOBBytes stay bytes. | |
| ROWID, UROWID, XMLTYPE, SDO_GEOMETRY | CLOB with their textXMLTYPE, ROWID and UROWID keep their text in a CLOB, so the script loads on every version from 19c. | type_replaced |
| collations that ignore case (BINARY_CI) | exact comparisonColumn collations aren't written, so a column that ignored case (BINARY_CI) now tells Ana and ana apart. | case_insensitive_collation |
| identity columns, and a sequence a BEFORE INSERT trigger assigns | 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. A column a trigger fills from a sequence (the way before 12c) is an identity too. | |
| identity columns the target can't count | a plain integerOracle counts one identity column per table; any other keeps its values without a counter. | counter_dropped |
| CREATE SEQUENCE | CREATE SEQUENCE with the same optionsThe export writes where a sequence has reached as its START WITH, so it continues from there. | |
| virtual columns (GENERATED ALWAYS AS ... VIRTUAL) | virtual columnsThe same virtual columns. | stored_as_virtual |
| virtual columns with functions IntoSQL doesn't write, and the || operator | a plain columnA formula that can't be written for Oracle leaves a plain column and a report line; the values the export wrote for virtual columns are left out, and Oracle has none to compute. | generated_not_translated |
| defaults with functions IntoSQL doesn't write (SYS_GUID(), ||) | 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 IntoSQL doesn't write | not createdA check that can't be written is left out and reported. | check_skipped |
| CHECK constraints that are DISABLE or ENABLE NOVALIDATE | not createdOracle doesn't apply these checks to the rows already there, so the rows may break them. | check_not_enforced |
| function-based, bitmap and domain indexes | 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 DELETE CASCADE that cascades in a cycle or along a second path | droppedOracle's foreign keys act on delete only, with CASCADE or SET NULL. | foreign_key_action_unsupported |
| foreign keys to tables the export doesn't create, and DISABLE or NOVALIDATE ones | not createdA key can't name a table that isn't there, and one Oracle doesn't check may have rows without a parent. | foreign_key_skipped |
| names longer than Oracle 12.1 allows | shortenedOracle names hold 128 bytes from 12.2; a longer name is shortened. | name_shortened |
| partitioned tables | one table with every partition's rowsThe rows are the same; partition the table again in Oracle if it needs it. | partitions_dropped |
| several schemas in one export | 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; an export of one schema keeps its tables' 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, synonyms, types, packages, procedures, functions and other triggers | listed in the reportTheir code is written in PL/SQL, so they are listed for you to recreate. | not_converted_objects |
| exports without CREATE TABLE (data only) | types read from the valuesA data-only export has only rows, so the types come from the values. | no_schema |
| expressions in INSERT values other than the ones exports write | NULLA converter that doesn't run a database can't evaluate them; to_date, to_timestamp, TO_CLOB, HEXTORAW and the others exports write are read. | expression_value |
| types IntoSQL doesn't know, and object types | CLOBText holds any value; change the type after loading. | unknown_type |
| dates before year 1 and time zones given as region names | NULLA value that isn't a date becomes NULL and the report counts it. | date_out_of_range |
| NaN and Infinity in BINARY_FLOAT and BINARY_DOUBLE | NaN and Infinity keptOracle's floating-point types hold NaN and infinity. | |
| 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 @oracle.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 Oracle for the best result
The converter reads what the dump holds. These options keep more of your data in it:
- In SQL Developer, choose Tools, Database Export, with DDL and the Insert data format. SQLcl writes the same with SET SQLFORMAT INSERT.
- Run ALTER SESSION SET NLS_DATE_FORMAT = 'SYYYY-MM-DD HH24:MI:SS' before exporting: the default format writes dates without their time or century.
- A .dmp file from exp or expdp is binary: import it into an Oracle database, Oracle Database Free in Docker for one, and export it with SQL Developer.
What isn't converted
Views, triggers, procedures, functions and other objects written in Oracle's own language are listed in the report by name, never dropped silently, so you can check each one against the Oracle version you load into before recreating it.
Questions about moving Oracle between versions
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.
Can I convert a .dmp file from exp or expdp?
Not directly: Data Pump and the original Export write a binary format only Oracle reads. Import it into any Oracle database, Oracle Database Free runs in Docker, and export it with SQL Developer's Database Export or SQLcl. The converter says so when it gets a .dmp file.
Which Oracle versions can the export come from?
Exports from SQL Developer and SQLcl, with DBMS_METADATA's table definitions, from Oracle 11g on. Every test export comes from Oracle 21c and 23ai, in American, German and ISO sessions, and is compared with its source cell by cell.
What happens to NUMBER, DATE and the other Oracle types?
They stay as they are: NUMBER with its precision, DATE, TIMESTAMP, intervals, CHAR and VARCHAR2 with their lengths, RAW and the LOBs. XMLTYPE and JSON keep their text in a CLOB, so the script loads on 19c.
Are identity columns and sequences kept?
Yes. Identity columns and sequences stay as they are and continue from where the export says they reached.
What about PL/SQL packages, procedures and triggers?
They are listed in the report by name and not converted, so you can recreate them once the tables are in.
Which Oracle versions does the script load on?
Oracle 19c and later. Every test export loads on 21c and 23ai, stopping at the first error if anything goes wrong.