---
title: "Migrate PostgreSQL to Oracle in your browser"
description: "Migrate a PostgreSQL database to Oracle: convert a pg_dump file into a script with the same tables, keys and data, and a report of every change."
canonical: https://intosql.com/postgresql-to-oracle
last_updated: 2026-10-15
---

# Migrate PostgreSQL to Oracle in your browser

Drop a plain dump from pg_dump or pg_dumpall, as .sql or .sql.gz, 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.

[PostgreSQL to Oracle migration guide](https://intosql.com/docs/migrations/postgresql-to-oracle)

[PostgreSQL to Oracle data types](https://intosql.com/docs/types/postgresql-to-oracle)

## What changes from PostgreSQL to Oracle

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

| In PostgreSQL | In Oracle | Report line |
| --- | --- | --- |
| boolean | NUMBER(1) with a CHECK of 0 and 1 |  |
| varchar(n), char(n) | VARCHAR2(n CHAR), counting characters |  |
| text | VARCHAR2(4000 CHAR) when every value fits 4000 bytes (750 characters in a key), else CLOB | `text_as_lob` |
| empty strings and empty bytea values | NULL | `empty_string_null` |
| citext | exact comparison | `case_insensitive_collation` |
| numeric(p,s) past 38 digits; numeric without a precision | NUMBER when its values fit 38 digits, else VARCHAR2 | `decimal_as_text` |
| money | NUMBER(19,2) | `type_replaced` |
| interval | VARCHAR2(100 CHAR) with PostgreSQL's text | `type_replaced` |
| arrays (integer[], text[]...) | CLOB with a JSON array | `type_replaced` |
| ranges, geometric types, tsvector, xml, hstore, PostGIS, composite and extension types | VARCHAR2 or CLOB with PostgreSQL's text | `type_replaced` |
| json, jsonb | CLOB |  |
| timestamp | TIMESTAMP |  |
| timestamp with time zone | TIMESTAMP WITH TIME ZONE, written in UTC |  |
| time | INTERVAL DAY TO SECOND from midnight |  |
| infinity, -infinity, BC dates and years past 9999 | NULL | `date_out_of_range` |
| NaN and Infinity in numeric | NULL | `invalid_value_null` |
| NOT NULL columns with values that become NULL | NOT NULL dropped | `not_null_relaxed` |
| enum types | VARCHAR2 with a CHECK of its labels |  |
| enum columns holding values outside their labels | VARCHAR2 without a check | `enum_check_skipped` |
| uuid, inet, cidr, macaddr | VARCHAR2 |  |
| bytea | BLOB |  |
| bit(n), bit varying | NUMBER with the bits' value up to 63 bits; text digits beyond |  |
| oid, xid, cid | NUMBER(10) with a CHECK that it isn't negative |  |
| serial, identity | GENERATED BY DEFAULT ON NULL AS IDENTITY, continuing after the highest id |  |
| a second serial or identity column in a table | a plain integer | `counter_dropped` |
| sequences not owned by a column | CREATE SEQUENCE, at the value it had reached |  |
| generated columns | virtual columns | `stored_as_virtual` |
| generated columns with functions Oracle lacks | a plain column with the values | `generated_not_translated` |
| defaults with functions Oracle lacks, and defaults from a sequence | no default | `default_not_translated` |
| CHECK constraints | translated, inline in CREATE TABLE |  |
| CHECK constraints with functions Oracle lacks | not created | `check_skipped` |
| CHECK ... NOT VALID | not created | `check_not_valid` |
| gin, gist, brin and hash indexes, partial and expression indexes, and indexes on CLOB columns | not created | `index_skipped` |
| foreign keys | after the data ENABLE NOVALIDATE, then validated with a warning on orphans |  |
| ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT | dropped | `foreign_key_action_unsupported` |
| foreign keys to tables the dump doesn't create | not created | `foreign_key_skipped` |
| names over 128 bytes | shortened | `name_shortened` |
| partitioned tables | one table with every partition's rows | `partitions_dropped` |
| schemas | one schema, tables named after their schema first | `schemas_merged` |
| views, functions, procedures, triggers, rules, policies, extensions and types | listed in the report | `not_converted_objects` |
| dumps without CREATE TABLE (--data-only) | types read from the values | `no_schema` |
| expressions in INSERT values | NULL | `expression_value` |
| types IntoSQL doesn't know | CLOB | `unknown_type` |
| text that isn't valid in its encoding | the bad bytes as the replacement character | `invalid_utf8_replaced` |
| text decoded twice (Ã±) | copied as it is | `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.

```shell
NLS_LANG=.AL32UTF8 sqlplus -S -L user@//localhost/mydb @postgresql.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 PostgreSQL for the best result

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

- --format=plain, pg_dump's default, writes SQL. A custom or directory archive (-Fc, -Fd) becomes SQL with pg_restore -f dump.sql, without a database server.
- --no-owner --no-privileges leave out roles and grants, which the converter skips anyway, so the dump is smaller.
- pg_dumpall dumps every database in one file; each one's tables are named after it, so nothing collides.

## What isn't converted

Views, materialized views, functions, procedures, triggers, rules, row security policies, extensions and types are written in PostgreSQL's own language. They are listed in the report by name, never dropped silently, so you can rewrite them for Oracle.

## Questions about PostgreSQL to Oracle

### 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 pg_dump -Fc archives?

Not directly: a custom archive is a binary format. pg_restore -f dump.sql dump.backup turns it into the plain SQL the converter reads, without connecting to a server. The converter says so when it gets an archive.

### Which PostgreSQL versions can the dump come from?

Dumps written by pg_dump and pg_dumpall from PostgreSQL 10 on, with COPY or INSERT rows. Every test dump comes from PostgreSQL 13 and 18 and is compared with its source cell by cell.

### Are serial and identity values kept?

Yes. Each table's serial or identity column becomes an identity column, every row keeps its id, and the next id continues after the highest one. Sequences not owned by a column become Oracle sequences at the value they had reached.

### What happens to arrays, enums and other PostgreSQL types?

Arrays become JSON arrays, booleans NUMBER(1), timestamps with a time zone TIMESTAMP WITH TIME ZONE, and text VARCHAR2 when its values fit, else CLOB. Empty strings become NULL, as Oracle stores them, and the report counts them.

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

## From PostgreSQL

- [PostgreSQL to PostgreSQL](https://intosql.com/postgresql-to-postgresql)
- [PostgreSQL to MySQL](https://intosql.com/postgresql-to-mysql)
- [PostgreSQL to MariaDB](https://intosql.com/postgresql-to-mariadb)
- [PostgreSQL to SQLite](https://intosql.com/postgresql-to-sqlite)
- [PostgreSQL to SQL Server](https://intosql.com/postgresql-to-sql-server)

## Into Oracle

- [MySQL to Oracle](https://intosql.com/mysql-to-oracle)
- [MariaDB to Oracle](https://intosql.com/mariadb-to-oracle)
- [SQL Server to Oracle](https://intosql.com/sql-server-to-oracle)
- [SQLite to Oracle](https://intosql.com/sqlite-to-oracle)
- [Oracle to Oracle](https://intosql.com/oracle-to-oracle)

## Guide and every pair

- [Oracle to PostgreSQL](https://intosql.com/oracle-to-postgresql)
- [PostgreSQL to Oracle migration guide](https://intosql.com/docs/migrations/postgresql-to-oracle)
- [PostgreSQL to Oracle data types](https://intosql.com/docs/types/postgresql-to-oracle)
- [Every database pair](https://intosql.com/database-migration)
