Skip to content
IntoSQL home

Convert SQLite to Oracle

Drop a SQLite database file (.db, .sqlite, .sqlite3) or the SQL that sqlite3's .dump writes, and download a script for Oracle with the same tables, keys, indexes and data. Each column gets the type its values fit, and every value Oracle can't hold as it is is listed in the report.

Your file
sqlshop_2024_sqlite.sqlSample file, 3.4 kB
Your database

18 rows converted, no fixes, none rejected

Show

Tables in shop_2024_sqlite.sqlSQLite dump · written by sqlite3 · utf-8

  • customerswritten as CUSTOMERS8 rows
    7 columns
    IDNUMBER(19) not null
    NAMEVARCHAR2(4000 CHAR) not null
    EMAILVARCHAR2(750 CHAR) not null
    COUNTRYVARCHAR2(4000 CHAR) not null
    IS_ACTIVENUMBER(1) not null
    SIGNED_UPDATE
    CREATED_ATTIMESTAMP(0) not null
  • orderswritten as ORDERS10 rows
    9 columns
    IDNUMBER(19) not null
    CUSTOMER_IDNUMBER(19) not null
    STATUSVARCHAR2(750 CHAR) not null
    TOTALNUMBER(10,2) not null
    ITEMSNUMBER(10) not null
    ORDERED_ATTIMESTAMP(0) not null
    SHIPPED_ATTIMESTAMP(0)
    NOTEVARCHAR2(4000 CHAR)
    RECEIPTBLOB

Not converted

  • View order_totals
  • Trigger orders_shipped

Each database writes these in its own language. Rewrite them for Oracle.

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

CREATE TABLE "CUSTOMERS" (
    "ID" NUMBER(19) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 9),
    "NAME" VARCHAR2(4000 CHAR) NOT NULL,
    "EMAIL" VARCHAR2(750 CHAR) NOT NULL,
    "COUNTRY" VARCHAR2(4000 CHAR) DEFAULT 'US' NOT NULL,
    "IS_ACTIVE" NUMBER(1) DEFAULT 1 NOT NULL,
    "SIGNED_UP" DATE,
    "CREATED_AT" TIMESTAMP(0) DEFAULT LOCALTIMESTAMP NOT NULL,
    CONSTRAINT "CK_CUSTOMERS_IS_ACTIVE" CHECK ("IS_ACTIVE" IN (0, 1)),
    CONSTRAINT "CHECK1" CHECK (LENGTH("COUNTRY") = 2)
);

CREATE TABLE "ORDERS" (
    "ID" NUMBER(19) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 11),
    "CUSTOMER_ID" NUMBER(19) NOT NULL,
    "STATUS" VARCHAR2(750 CHAR) DEFAULT 'pending' NOT NULL,
    "TOTAL" NUMBER(10,2) NOT NULL,
    "ITEMS" NUMBER(10) DEFAULT 1 NOT NULL,
    "ORDERED_AT" TIMESTAMP(0) NOT NULL,
    "SHIPPED_AT" TIMESTAMP(0),
    "NOTE" VARCHAR2(4000 CHAR),
    "RECEIPT" BLOB,
    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,
DATE '2023-01-15',
TIMESTAMP '2023-01-15 10:24: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 08:11:42'
);
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 19:05:17'
);
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 02:40: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 14:00: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 09:30: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-02-29 23:59:59'
);
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 12:12:12'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
1,
1,
'shipped',
129.9,
3,
TIMESTAMP '2024-03-01 10:00:00',
TIMESTAMP '2024-03-02 16:30:00',
NULL,
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
2,
2,
'paid',
54,
1,
TIMESTAMP '2024-03-02 11:15:00',
NULL,
'Gift wrap 🎁',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
3,
1,
'cancelled',
12.5,
1,
TIMESTAMP '2024-03-03 09:45:00',
NULL,
'Customer changed their mind',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
4,
4,
'shipped',
980,
12,
TIMESTAMP '2024-03-05 02:10:00',
TIMESTAMP '2024-03-07 08:00:00',
'请小心轻放',
HEXTORAW('25504446')
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
5,
5,
'pending',
45.75,
2,
TIMESTAMP '2024-03-08 17:20:00',
NULL,
'Leave at the door;'
|| CHR(10)
|| 'ring twice',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
6,
6,
'shipped',
310.4,
5,
TIMESTAMP '2024-03-10 13:00:00',
TIMESTAMP '2024-03-11 09:00:00',
'Fragile: "glass"',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
7,
8,
'paid',
76.2,
2,
TIMESTAMP '2024-03-11 18:45:00',
NULL,
NULL,
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
8,
2,
'paid',
19.99,
1,
TIMESTAMP '2024-03-12 08:00:00',
NULL,
'Paid at the counter',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
9,
3,
'shipped',
64,
2,
TIMESTAMP '2024-03-14 15:30:00',
NULL,
'Shipping date never recorded',
NULL
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"RECEIPT"
) VALUES (
10,
7,
'paid',
1499,
1,
TIMESTAMP '2024-03-15 21:05:00',
NULL,
'C:\Users\ravi\invoice.pdf',
NULL
);
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_EMAIL" UNIQUE ("EMAIL");
ALTER TABLE "ORDERS" ADD CONSTRAINT "PK_ORDERS" PRIMARY KEY ("ID");
CREATE INDEX "IX_ORDERS_IDX_ORDERS_CUSTOMER" ON "ORDERS" ("CUSTOMER_ID");
CREATE INDEX "IX_ORDERS_IDX_ORDERS_STATUS_DATE" ON "ORDERS" ("STATUS", "ORDERED_AT");
ALTER TABLE "ORDERS" ADD CONSTRAINT "FK_ORDERS" FOREIGN KEY ("CUSTOMER_ID") REFERENCES "CUSTOMERS" ("ID") ON DELETE CASCADE 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 "CUSTOMERS";
    IF next_id < 9 THEN
        next_id := 9;
    END IF;
    EXECUTE IMMEDIATE 'ALTER TABLE "CUSTOMERS" 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 "ORDERS";
    IF next_id < 11 THEN
        next_id := 11;
    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.

Needs a look

  • 1 text column compares 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.

Your file is converted on this computer and never uploaded.

Other samples:

What changes from SQLite to Oracle

Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.

  • declared types (any text: VARCHAR(255), DATETIME, BOOLEAN, JSON or none)

    the type every value in the column fits

    SQLite keeps any value in any column, so the declared type is a hint: each column gets the type all of its values fit, and a column holding mixed values keeps their text.

  • BOOLEAN, BOOL

    NUMBER(1) with a CHECK of 0 and 1

    When every value is 0 or 1; a column that also holds other values keeps them as numbers or text.

  • TEXT, VARCHAR(n), CHAR(n), CLOB

    VARCHAR2(n CHAR) when every value fits, else CLOB

    SQLite never enforces a length; a declared length holds only when every value fits it.

  • TEXT and VARCHAR(n) longer than 4000 bytes

    VARCHAR2(4000 CHAR) when every value fits 4000 bytes (750 characters in a key), 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

  • DECIMAL(p,s), NUMERIC

    the exact type when every value fits it, else a double

    SQLite stores these as integers or doubles; a declared precision holds only when the values fit it.

  • BLOB, and columns holding bytes

    BLOB

    Bytes stay bytes; text and numbers in the same column are kept as their bytes.

  • DATE, DATETIME, TIMESTAMP holding ISO 8601 text

    DATE or TIMESTAMP(p), by what the values hold

    When every value is a date or a date and time; epoch numbers and other text keep their values as they are.

  • JSON holding JSON text

    CLOB

    When every value is a JSON object or array.

  • Infinity in REAL columns

    kept as BINARY_DOUBLE_INFINITY

    Oracle's doubles hold infinity.

  • INTEGER PRIMARY KEY, AUTOINCREMENT

    GENERATED BY DEFAULT ON NULL AS IDENTITY, continuing after the highest id or sqlite_sequence's

    Only a column declared exactly INTEGER PRIMARY KEY is the rowid; every row keeps its id, and the counter starts after the highest id ever handed out.

  • COLLATE NOCASE

    exact comparison

    Oracle compares text exactly unless told otherwise; NOCASE columns lose ignoring case.

    Report line: case_insensitive_collation

  • generated columns

    virtual columns

    Oracle computes generated columns on read only; STORED columns become virtual with the same values.

    Report line: stored_as_virtual

  • generated columns with functions IntoSQL doesn't write, and the || operator

    a plain column

    A formula that can't be written leaves a plain column and a report line; the dump has no values for generated columns.

    Report line: generated_not_translated

  • defaults with functions IntoSQL doesn't write (strftime, randomblob)

    no default

    A default that can't be written is left out and reported; CURRENT_TIMESTAMP and datetime('now') are written.

    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

  • partial and expression indexes

    not created

    IntoSQL writes indexes on whole columns; create the others again after loading.

    Report line: index_skipped

  • foreign keys (unenforced unless PRAGMA foreign_keys=ON)

    after the data ENABLE NOVALIDATE, then validated

    SQLite checks foreign keys only when told to, so rows without a parent are common: they load, and the key is left unchecked 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

  • empty strings and empty blobs

    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

  • 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

  • primary keys holding NULL, and NOT NULL columns with values that become NULL

    NOT NULL dropped

    SQLite lets a primary key other than the rowid hold NULL; the column can't be NOT NULL in the target, or the load would stop.

    Report line: not_null_relaxed

  • views, triggers and virtual tables (FTS, R*Tree)

    listed in the report

    Their code is written in SQLite's own language, and virtual tables need SQLite's modules; they are listed for you to recreate. Their internal tables aren't copied.

    Report line: not_converted_objects

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

    types read from the values

    A data-only dump has only rows, so the types come from the values and the columns are numbered.

    Report line: no_schema

  • expressions in INSERT values other than the text functions dumps write

    NULL

    unistr(), replace(), char() and || are evaluated as the shell writes them; other expressions can't be without a database.

    Report line: expression_value

  • NUL characters in text

    kept

    The sqlite3 shell and Python cut text at a NUL character when they dump it, so only what precedes it is in the dump.

  • text that isn't valid UTF-8

    the bad bytes as the replacement character

    Dumps write such text as raw bytes; the script is UTF-8.

    Report line: invalid_utf8_replaced

  • text decoded twice (ñ)

    copied as it is

    The dump holds exactly these characters, so they are copied; the report points them out.

    Report line: mojibake_suspected

In SQLiteIn OracleReport line
declared types (any text: VARCHAR(255), DATETIME, BOOLEAN, JSON or none)the type every value in the column fitsSQLite keeps any value in any column, so the declared type is a hint: each column gets the type all of its values fit, and a column holding mixed values keeps their text.
BOOLEAN, BOOLNUMBER(1) with a CHECK of 0 and 1When every value is 0 or 1; a column that also holds other values keeps them as numbers or text.
TEXT, VARCHAR(n), CHAR(n), CLOBVARCHAR2(n CHAR) when every value fits, else CLOBSQLite never enforces a length; a declared length holds only when every value fits it.
TEXT and VARCHAR(n) longer than 4000 bytesVARCHAR2(4000 CHAR) when every value fits 4000 bytes (750 characters in a key), 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
DECIMAL(p,s), NUMERICthe exact type when every value fits it, else a doubleSQLite stores these as integers or doubles; a declared precision holds only when the values fit it.
BLOB, and columns holding bytesBLOBBytes stay bytes; text and numbers in the same column are kept as their bytes.
DATE, DATETIME, TIMESTAMP holding ISO 8601 textDATE or TIMESTAMP(p), by what the values holdWhen every value is a date or a date and time; epoch numbers and other text keep their values as they are.
JSON holding JSON textCLOBWhen every value is a JSON object or array.
Infinity in REAL columnskept as BINARY_DOUBLE_INFINITYOracle's doubles hold infinity.
INTEGER PRIMARY KEY, AUTOINCREMENTGENERATED BY DEFAULT ON NULL AS IDENTITY, continuing after the highest id or sqlite_sequence'sOnly a column declared exactly INTEGER PRIMARY KEY is the rowid; every row keeps its id, and the counter starts after the highest id ever handed out.
COLLATE NOCASEexact comparisonOracle compares text exactly unless told otherwise; NOCASE columns lose ignoring case.case_insensitive_collation
generated columnsvirtual columnsOracle computes generated columns on read only; STORED columns become virtual with the same values.stored_as_virtual
generated columns with functions IntoSQL doesn't write, and the || operatora plain columnA formula that can't be written leaves a plain column and a report line; the dump has no values for generated columns.generated_not_translated
defaults with functions IntoSQL doesn't write (strftime, randomblob)no defaultA default that can't be written is left out and reported; CURRENT_TIMESTAMP and datetime('now') are written.default_not_translated
CHECK constraintstranslatedThe same checks, tested as each row loads.
CHECK constraints with functions IntoSQL doesn't writenot createdA check that can't be written is left out and reported.check_skipped
partial and expression indexesnot createdIntoSQL writes indexes on whole columns; create the others again after loading.index_skipped
foreign keys (unenforced unless PRAGMA foreign_keys=ON)after the data ENABLE NOVALIDATE, then validatedSQLite checks foreign keys only when told to, so rows without a parent are common: they load, and the key is left unchecked 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
empty strings and empty blobsNULLOracle stores an empty string as NULL, so a value that was ' reads back as NULL. The report counts them per column.empty_string_null
foreign keys to tables the dump doesn't createnot createdA key can't name a table that isn't there.foreign_key_skipped
primary keys holding NULL, and NOT NULL columns with values that become NULLNOT NULL droppedSQLite lets a primary key other than the rowid hold NULL; the column can't be NOT NULL in the target, or the load would stop.not_null_relaxed
views, triggers and virtual tables (FTS, R*Tree)listed in the reportTheir code is written in SQLite's own language, and virtual tables need SQLite's modules; they are listed for you to recreate. Their internal tables aren't copied.not_converted_objects
dumps without CREATE TABLE (--data-only)types read from the valuesA data-only dump has only rows, so the types come from the values and the columns are numbered.no_schema
expressions in INSERT values other than the text functions dumps writeNULLunistr(), replace(), char() and || are evaluated as the shell writes them; other expressions can't be without a database.expression_value
NUL characters in textkeptThe sqlite3 shell and Python cut text at a NUL character when they dump it, so only what precedes it is in the dump.
text that isn't valid UTF-8the bad bytes as the replacement characterDumps write such text as raw bytes; the script is UTF-8.invalid_utf8_replaced
text decoded twice (ñ)copied as it isThe dump 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 @sqlite.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 SQLite for the best result

The converter reads what the dump holds. These options keep more of your data in it:

  • The database file itself converts as it is, read page by page in your browser. Close the app that writes to it first, so no change is left behind in its -wal file.
  • sqlite3 app.db .dump > app.sql writes the same database as SQL, which converts too, as .sql or .sql.gz.
  • DB Browser for SQLite writes that SQL with File, Export, Database to SQL file, and so does Python's iterdump().

What isn't converted

Views and triggers are written in SQLite's own language. They are listed in the report by name, never dropped silently, so you can rewrite them for Oracle. Virtual tables, such as FTS5 and R*Tree, are listed too, and their shadow tables are left out.

Questions about SQLite 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.

Can I convert the .db file itself?

Yes. Drop the .db, .sqlite or .sqlite3 file: it is read in your browser and never uploaded. Text with NUL characters and every digit of a double arrive as stored, which a .dump can cut short.

Which SQLite versions can the dump come from?

Database files from SQLite 3.0 on, in UTF-8 or UTF-16, and dumps from the sqlite3 shell's .dump or Python's iterdump(). Every test file and dump comes from sqlite3 3.31 to 3.54, STRICT and WITHOUT ROWID tables included, and is compared with its source cell by cell.

How are column types decided when SQLite doesn't enforce them?

The declared type is a hint and the values decide: a column becomes NUMBER, BINARY_DOUBLE, DATE, TIMESTAMP, BLOB, VARCHAR2 or CLOB when every value fits it. Empty strings become NULL, as Oracle stores them, and the report counts them.

Are INTEGER PRIMARY KEY and AUTOINCREMENT kept?

Yes. An INTEGER PRIMARY KEY becomes an identity column, every row keeps its id, and the next id continues after the highest one, or after sqlite_sequence's value with AUTOINCREMENT.

What about views and triggers?

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 dump loads on 21c and 23ai, stopping at the first error if anything goes wrong.