Convertir Oracle en PostgreSQL
Déposez le fichier .sql qu'écrit Database Export de SQL Developer ou SQLcl, avec ses tables et ses instructions Insert, et téléchargez un script PostgreSQL avec les mêmes tables, clés, index et données. Chaque valeur que PostgreSQL ne peut pas contenir telle quelle, et chaque objet qu'il ne reprend pas, est listé dans le rapport.
18 lignes converties, aucune correction, aucune rejetée
Tables de shop_2024_oracle.sqlDump Oracle · écrit par SQL Developer · utf-8
customers8 lignes
7 colonnes id bigint not null name varchar(100) not null email varchar(150) not null country varchar(2) not null is_active smallint not null signed_up timestamp(0) created_at timestamp(0) not null orders10 lignes
9 colonnes id bigint not null customer_id bigint not null invoice bigint status varchar(10) not null total numeric(10,2) not null items integer not null ordered_at timestamp(0) not null shipped_at timestamp(0) note varchar(255)
-- Written by IntoSQL from a Oracle dump. Load it with: psql -v ON_ERROR_STOP=1 -f <file>
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET synchronous_commit = off;
SET maintenance_work_mem = '512MB';
CREATE SEQUENCE invoice_numbers INCREMENT BY 1 MINVALUE 1 START WITH 1020;
CREATE TABLE customers (
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
name varchar(100) NOT NULL,
email varchar(150) NOT NULL,
country varchar(2) DEFAULT 'US' NOT NULL,
is_active smallint DEFAULT 1 NOT NULL,
signed_up timestamp(0),
created_at timestamp(0) DEFAULT LOCALTIMESTAMP NOT NULL,
CONSTRAINT customers_check1 CHECK (is_active IN (0, 1))
);
CREATE TABLE orders (
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
customer_id bigint NOT NULL,
invoice bigint DEFAULT (nextval('invoice_numbers')),
status varchar(10) DEFAULT 'pending' NOT NULL,
total numeric(10,2) NOT NULL,
items integer DEFAULT 1 NOT NULL,
ordered_at timestamp(0) NOT NULL,
shipped_at timestamp(0),
note varchar(255),
CONSTRAINT orders_check1 CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))
);
COPY customers (id, name, email, country, is_active, signed_up, created_at) FROM stdin;
1 Ana Martínez ana@example.com MX 1 2023-01-15 00:00:00 2023-01-15 10:24:00.000000
2 Zoë Müller zoe@example.de DE 1 2023-02-03 00:00:00 2023-02-03 08:11:42.000000
3 José Peña jose@example.es ES 0 2023-03-21 00:00:00 2023-03-21 19:05:17.000000
4 李雷 lilei@example.cn CN 1 2023-05-09 00:00:00 2023-05-09 02:40:00.000000
5 Noah O'Brien noah@example.ie IE 1 \N 2023-06-30 14:00:00.000000
6 Amélie Laurent amelie@example.fr FR 1 2024-01-02 00:00:00 2024-01-02 09:30:00.000000
7 Ravi Patel ravi@example.in IN 0 2024-02-29 00:00:00 2024-02-29 23:59:59.000000
8 Sofia Rossi 🌻 sofia@example.it IT 1 2024-04-12 00:00:00 2024-04-12 12:12:12.000000
\.
COPY orders (id, customer_id, invoice, status, total, items, ordered_at, shipped_at, note) FROM stdin;
1 1 1000 shipped 129.9 3 2024-03-01 10:00:00 2024-03-02 16:30:00 \N
2 2 1001 paid 54 1 2024-03-02 11:15:00 \N Gift wrap 🎁
3 1 1002 cancelled 12.5 1 2024-03-03 09:45:00 \N Customer changed their mind
4 4 1003 shipped 980 12 2024-03-05 02:10:00 2024-03-07 08:00:00 请小心轻放
5 5 1004 pending 45.75 2 2024-03-08 17:20:00 \N Leave at the door; ring twice
6 6 1005 shipped 310.4 5 2024-03-10 13:00:00 2024-03-11 09:00:00 Fragile: "glass"
7 8 1006 paid 76.2 2 2024-03-11 18:45:00 \N \N
8 2 1007 paid 19.99 1 2024-03-12 08:00:00 \N Paid at the counter
9 3 1008 shipped 64 2 2024-03-14 15:30:00 \N Shipping date never recorded
10 7 1009 paid 1499 1 2024-03-15 21:05:00 \N C:\\Users\\ravi\\invoice.pdf
\.
-- Keys, indexes and constraints, after the rows so each builds once.
CREATE UNIQUE INDEX customers_customers_pk ON customers (id);
CREATE UNIQUE INDEX customers_customers_email_uk ON customers (email);
CREATE UNIQUE INDEX orders_orders_pk ON orders (id);
CREATE INDEX orders_orders_status_date_ix ON orders (status, ordered_at);
ALTER TABLE customers ADD CONSTRAINT customers_customers_pk PRIMARY KEY USING INDEX customers_customers_pk;
ALTER TABLE customers ADD CONSTRAINT customers_customers_email_uk UNIQUE USING INDEX customers_customers_email_uk;
ALTER TABLE orders ADD CONSTRAINT orders_orders_pk PRIMARY KEY USING INDEX orders_orders_pk;
ALTER TABLE orders ADD CONSTRAINT orders_customer_fk FOREIGN KEY (customer_id) REFERENCES customers (id) NOT VALID;
DO $f$ BEGIN
ALTER TABLE orders VALIDATE CONSTRAINT orders_customer_fk;
EXCEPTION WHEN foreign_key_violation THEN
RAISE WARNING 'foreign key % has rows without a parent: it stays NOT VALID, and new rows are checked', 'orders_customer_fk';
END $f$;
SELECT setval(pg_get_serial_sequence('customers', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM customers)::numeric, 0) + 1, 1), 9223372036854775807)::bigint, false);
COMMENT ON TABLE customers IS 'People who bought at least once';
SELECT setval(pg_get_serial_sequence('orders', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM orders)::numeric, 0) + 1, 1), 9223372036854775807)::bigint, false);
Corrections
Rien n'avait besoin d'être corrigé.
Votre fichier est converti sur cet ordinateur et n'est jamais envoyé.
Autres exemples :
Ce qui change de Oracle à PostgreSQL
Chaque ligne est une règle appliquée par le convertisseur, avec la ligne de rapport qu'il écrit quand la règle change quelque chose dans vos données.
BOOLEAN (23ai)
boolean
TRUE et FALSE restent true et false.
VARCHAR2(n BYTE|CHAR), NVARCHAR2(n), CHAR(n), NCHAR(n), CLOB, NCLOB, LONG
varchar(n) et text
Les longueurs comptent les caractères, qu'Oracle ait compté des octets ou des caractères, donc chaque valeur tient.
NUMBER, NUMBER(p,s), FLOAT(b)
numeric(p,s), ou bigint
NUMBER garde chaque chiffre ; un NUMBER sans précision dont toutes les valeurs sont des nombres entiers de 18 chiffres au plus est un entier 64 bits, et une échelle négative garde autant de chiffres avant la virgule.
DATE, TIMESTAMP(p)
timestamp(p)
Le DATE d'Oracle contient une heure à la seconde près ; c'est donc une date et une heure. Un export dans une session américaine ou allemande écrit les dates sans leur heure ni leur siècle (DD-MON-RR) ; exportez dans une session ISO (SYYYY-MM-DD HH24:MI:SS) pour les garder.
TIMESTAMP(7) to TIMESTAMP(9)
six chiffres de seconde, le septième coupé
PostgreSQL garde les microsecondes ; le septième chiffre (100 nanosecondes) est coupé, jamais arrondi, donc aucune valeur ne passe à la seconde ou au jour suivant.
Ligne du rapport: fraction_cut
TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE
timestamptz, les mêmes instants
timestamptz garde l'instant ; PostgreSQL l'affiche dans le fuseau horaire de la session au lieu du décalage qu'avait chaque valeur.
JSON (21c), and CLOB holding JSON
jsonb
Les mêmes valeurs JSON ; un NULL SQL que l'export a écrit 'null' reste NULL.
BLOB, RAW(n), LONG RAW
bytea
Les octets restent des octets.
ROWID, UROWID, XMLTYPE, SDO_GEOMETRY
text, et xml pour XMLTYPE
Les types propres à Oracle n'ont pas d'équivalent ; leurs valeurs gardent leur texte, valeurs ROWID comprises, même si elles n'ont aucun sens hors d'Oracle.
Ligne du rapport: type_replaced
collations that ignore case (BINARY_CI)
comparaison exacte
PostgreSQL compare le texte exactement ; les colonnes insensibles à la casse dans Oracle distinguent donc maintenant Ana et ana.
Ligne du rapport: case_insensitive_collation
identity columns, and a sequence a BEFORE INSERT trigger assigns
GENERATED BY DEFAULT AS IDENTITY, qui reprend après l'identifiant le plus élevé
Les identifiants copiés se chargent tels quels, et le compteur reprend après le plus élevé. Une colonne qu'un déclencheur remplit depuis une séquence (la méthode d'avant 12c) devient aussi une colonne d'identité.
CREATE SEQUENCE
CREATE SEQUENCE avec les mêmes options
L'export écrit où en est une séquence comme son START WITH ; elle reprend donc à partir de là.
virtual columns (GENERATED ALWAYS AS ... VIRTUAL)
colonnes générées, STORED
PostgreSQL 13 à 17 ne font que stocker les colonnes générées ; la formule est la même, avec || écrit pour que NULL compte comme vide, comme dans Oracle.
Ligne du rapport: virtual_as_stored
virtual columns with functions IntoSQL doesn't write, and the || operator
une colonne simple
Une formule qui ne peut pas être écrite pour PostgreSQL laisse une colonne simple et une ligne dans le rapport ; les valeurs que l'export a écrites pour les colonnes virtuelles sont omises, et PostgreSQL ne peut pas les calculer.
Ligne du rapport: generated_not_translated
defaults with functions IntoSQL doesn't write (SYS_GUID(), ||)
aucune valeur par défaut
Une valeur par défaut qui ne peut pas être écrite pour PostgreSQL est omise et signalée.
Ligne du rapport: default_not_translated
CHECK constraints
traduites
Les mêmes contraintes CHECK, vérifiées au chargement de chaque ligne.
CHECK constraints with functions IntoSQL doesn't write
non créée
Une contrainte CHECK qui ne peut pas être écrite pour PostgreSQL est omise et signalée.
Ligne du rapport: check_skipped
CHECK constraints that are DISABLE or ENABLE NOVALIDATE
non créée
Oracle n'applique pas ces contraintes CHECK aux lignes déjà présentes, qui peuvent donc les enfreindre.
Ligne du rapport: check_not_enforced
function-based, bitmap and domain indexes
non créée
IntoSQL écrit des index b-tree sur des colonnes ; recréez les autres après le chargement.
Ligne du rapport: index_skipped
foreign keys
clés étrangères, après les lignes
Les lignes se chargent dans tout ordre, puis les clés les vérifient.
foreign keys to tables the export doesn't create, and DISABLE or NOVALIDATE ones
non créée
Une clé ne peut pas désigner une table absente, et une clé qu'Oracle ne vérifie pas peut avoir des lignes sans parent.
Ligne du rapport: foreign_key_skipped
partitioned tables
une seule table avec les lignes de chaque partition
Les lignes sont les mêmes ; partitionnez à nouveau la table dans PostgreSQL si besoin.
Ligne du rapport: partitions_dropped
NOT NULL columns with values that become NULL
NOT NULL retiré
Une colonne dont des valeurs ont dû devenir NULL ne peut pas garder NOT NULL, sinon le chargement s'arrêterait.
Ligne du rapport: not_null_relaxed
views, synonyms, types, packages, procedures, functions and other triggers
listés dans le rapport
Leur code est écrit en PL/SQL ; ils sont donc listés pour que vous les recréiez.
Ligne du rapport: not_converted_objects
exports without CREATE TABLE (data only)
types déduits des valeurs
Un export de données seules ne contient que des lignes, donc les types viennent des valeurs.
Ligne du rapport: no_schema
expressions in INSERT values other than the ones exports write
NULL
Un convertisseur qui n'exécute pas de base de données ne peut pas les évaluer ; to_date, to_timestamp, TO_CLOB, HEXTORAW et les autres que les exports écrivent sont lus.
Ligne du rapport: expression_value
types IntoSQL doesn't know, and object types
text
Le texte contient n'importe quelle valeur ; changez le type après le chargement.
Ligne du rapport: unknown_type
dates before year 1 and time zones given as region names
NULL
Une valeur qui n'est pas une date devient NULL et le rapport la compte.
Ligne du rapport: date_out_of_range
NaN and Infinity in BINARY_FLOAT and BINARY_DOUBLE
NaN et Infinity conservés
Les types à virgule flottante de PostgreSQL contiennent NaN et l'infini.
NUL characters in text
retiré
Le texte PostgreSQL ne peut pas contenir de caractère NUL. Il est retiré et le rapport compte les valeurs.
Ligne du rapport: nul_removed
text that isn't valid in its encoding
les octets invalides deviennent le caractère de remplacement
Le texte PostgreSQL doit être de l'UTF-8 valide.
Ligne du rapport: invalid_utf8_replaced
text decoded twice (ñ)
copié tel quel
Le script contient exactement ces caractères, ils sont donc copiés ; le rapport les signale.
Ligne du rapport: mojibake_suspected
| Dans Oracle | Dans PostgreSQL | Ligne du rapport |
|---|---|---|
| BOOLEAN (23ai) | booleanTRUE et FALSE restent true et false. | |
| VARCHAR2(n BYTE|CHAR), NVARCHAR2(n), CHAR(n), NCHAR(n), CLOB, NCLOB, LONG | varchar(n) et textLes longueurs comptent les caractères, qu'Oracle ait compté des octets ou des caractères, donc chaque valeur tient. | |
| NUMBER, NUMBER(p,s), FLOAT(b) | numeric(p,s), ou bigintNUMBER garde chaque chiffre ; un NUMBER sans précision dont toutes les valeurs sont des nombres entiers de 18 chiffres au plus est un entier 64 bits, et une échelle négative garde autant de chiffres avant la virgule. | |
| DATE, TIMESTAMP(p) | timestamp(p)Le DATE d'Oracle contient une heure à la seconde près ; c'est donc une date et une heure. Un export dans une session américaine ou allemande écrit les dates sans leur heure ni leur siècle (DD-MON-RR) ; exportez dans une session ISO (SYYYY-MM-DD HH24:MI:SS) pour les garder. | |
| TIMESTAMP(7) to TIMESTAMP(9) | six chiffres de seconde, le septième coupéPostgreSQL garde les microsecondes ; le septième chiffre (100 nanosecondes) est coupé, jamais arrondi, donc aucune valeur ne passe à la seconde ou au jour suivant. | fraction_cut |
| TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE | timestamptz, les mêmes instantstimestamptz garde l'instant ; PostgreSQL l'affiche dans le fuseau horaire de la session au lieu du décalage qu'avait chaque valeur. | |
| JSON (21c), and CLOB holding JSON | jsonbLes mêmes valeurs JSON ; un NULL SQL que l'export a écrit 'null' reste NULL. | |
| BLOB, RAW(n), LONG RAW | byteaLes octets restent des octets. | |
| ROWID, UROWID, XMLTYPE, SDO_GEOMETRY | text, et xml pour XMLTYPELes types propres à Oracle n'ont pas d'équivalent ; leurs valeurs gardent leur texte, valeurs ROWID comprises, même si elles n'ont aucun sens hors d'Oracle. | type_replaced |
| collations that ignore case (BINARY_CI) | comparaison exactePostgreSQL compare le texte exactement ; les colonnes insensibles à la casse dans Oracle distinguent donc maintenant Ana et ana. | case_insensitive_collation |
| identity columns, and a sequence a BEFORE INSERT trigger assigns | GENERATED BY DEFAULT AS IDENTITY, qui reprend après l'identifiant le plus élevéLes identifiants copiés se chargent tels quels, et le compteur reprend après le plus élevé. Une colonne qu'un déclencheur remplit depuis une séquence (la méthode d'avant 12c) devient aussi une colonne d'identité. | |
| CREATE SEQUENCE | CREATE SEQUENCE avec les mêmes optionsL'export écrit où en est une séquence comme son START WITH ; elle reprend donc à partir de là. | |
| virtual columns (GENERATED ALWAYS AS ... VIRTUAL) | colonnes générées, STOREDPostgreSQL 13 à 17 ne font que stocker les colonnes générées ; la formule est la même, avec || écrit pour que NULL compte comme vide, comme dans Oracle. | virtual_as_stored |
| virtual columns with functions IntoSQL doesn't write, and the || operator | une colonne simpleUne formule qui ne peut pas être écrite pour PostgreSQL laisse une colonne simple et une ligne dans le rapport ; les valeurs que l'export a écrites pour les colonnes virtuelles sont omises, et PostgreSQL ne peut pas les calculer. | generated_not_translated |
| defaults with functions IntoSQL doesn't write (SYS_GUID(), ||) | aucune valeur par défautUne valeur par défaut qui ne peut pas être écrite pour PostgreSQL est omise et signalée. | default_not_translated |
| CHECK constraints | traduitesLes mêmes contraintes CHECK, vérifiées au chargement de chaque ligne. | |
| CHECK constraints with functions IntoSQL doesn't write | non crééeUne contrainte CHECK qui ne peut pas être écrite pour PostgreSQL est omise et signalée. | check_skipped |
| CHECK constraints that are DISABLE or ENABLE NOVALIDATE | non crééeOracle n'applique pas ces contraintes CHECK aux lignes déjà présentes, qui peuvent donc les enfreindre. | check_not_enforced |
| function-based, bitmap and domain indexes | non crééeIntoSQL écrit des index b-tree sur des colonnes ; recréez les autres après le chargement. | index_skipped |
| foreign keys | clés étrangères, après les lignesLes lignes se chargent dans tout ordre, puis les clés les vérifient. | |
| foreign keys to tables the export doesn't create, and DISABLE or NOVALIDATE ones | non crééeUne clé ne peut pas désigner une table absente, et une clé qu'Oracle ne vérifie pas peut avoir des lignes sans parent. | foreign_key_skipped |
| partitioned tables | une seule table avec les lignes de chaque partitionLes lignes sont les mêmes ; partitionnez à nouveau la table dans PostgreSQL si besoin. | partitions_dropped |
| NOT NULL columns with values that become NULL | NOT NULL retiréUne colonne dont des valeurs ont dû devenir NULL ne peut pas garder NOT NULL, sinon le chargement s'arrêterait. | not_null_relaxed |
| views, synonyms, types, packages, procedures, functions and other triggers | listés dans le rapportLeur code est écrit en PL/SQL ; ils sont donc listés pour que vous les recréiez. | not_converted_objects |
| exports without CREATE TABLE (data only) | types déduits des valeursUn export de données seules ne contient que des lignes, donc les types viennent des valeurs. | no_schema |
| expressions in INSERT values other than the ones exports write | NULLUn convertisseur qui n'exécute pas de base de données ne peut pas les évaluer ; to_date, to_timestamp, TO_CLOB, HEXTORAW et les autres que les exports écrivent sont lus. | expression_value |
| types IntoSQL doesn't know, and object types | textLe texte contient n'importe quelle valeur ; changez le type après le chargement. | unknown_type |
| dates before year 1 and time zones given as region names | NULLUne valeur qui n'est pas une date devient NULL et le rapport la compte. | date_out_of_range |
| NaN and Infinity in BINARY_FLOAT and BINARY_DOUBLE | NaN et Infinity conservésLes types à virgule flottante de PostgreSQL contiennent NaN et l'infini. | |
| NUL characters in text | retiréLe texte PostgreSQL ne peut pas contenir de caractère NUL. Il est retiré et le rapport compte les valeurs. | nul_removed |
| text that isn't valid in its encoding | les octets invalides deviennent le caractère de remplacementLe texte PostgreSQL doit être de l'UTF-8 valide. | invalid_utf8_replaced |
| text decoded twice (ñ) | copié tel quelLe script contient exactement ces caractères, ils sont donc copiés ; le rapport les signale. | mojibake_suspected |
Charger le script dans PostgreSQL
Le script crée les tables, copie les lignes, puis ajoute les clés, les index et les clés étrangères ; il se charge donc vite et s'arrête à la première erreur.
psql -v ON_ERROR_STOP=1 -d mydb -f oracle.postgresql.sqlLes clés étrangères sont ajoutées en NOT VALID, puis validées. Si une ligne pointe vers une ligne qui n'existe pas, le script vous indique quelle contrainte, au lieu d'échouer à mi-chemin.
Pour un gros dump, créez d'abord la base de données et exécutez le script avec psql depuis la machine du serveur.
IntoSQL Pro charge le fichier directement dans PostgreSQL depuis l'application de bureau ou la ligne de commande, sans écrire de script.
Faire le dump Oracle pour un meilleur résultat
Le convertisseur lit ce que contient le dump. Ces options y gardent davantage de vos données :
- Dans SQL Developer, choisissez Tools, Database Export, avec DDL et le format de données Insert. SQLcl écrit la même chose avec SET SQLFORMAT INSERT.
- Exécutez ALTER SESSION SET NLS_DATE_FORMAT = 'SYYYY-MM-DD HH24:MI:SS' avant l'export : le format par défaut écrit les dates sans leur heure ni leur siècle.
- Un fichier .dmp de exp ou expdp est binaire : importez-le dans une base Oracle, par exemple Oracle Database Free dans Docker, et exportez-le avec SQL Developer.
Ce qui n'est pas converti
Les vues, packages, procédures, fonctions, déclencheurs, types et synonymes sont écrits en PL/SQL. Ils sont listés par nom dans le rapport, jamais supprimés en silence, pour que vous puissiez les réécrire pour PostgreSQL.
Questions sur Oracle vers PostgreSQL
Pas de réponse ici ? Écrivez-nous depuis la page de contact, une personne vous répond. Page de contact
Mon dump est-il envoyé ?
Non. Le convertisseur s'exécute dans votre navigateur et lit le dump depuis votre disque par morceaux ; il ne quitte donc jamais votre ordinateur. L'application de bureau et la ligne de commande convertissent aussi sur votre ordinateur.
Quelle taille peut avoir le dump ?
Le convertisseur ne garde jamais tout le dump en mémoire : il lit le fichier deux fois, par plages, et écrit le script au fur et à mesure. Les dumps de plusieurs gigaoctets se convertissent dans le navigateur, dans l'application de bureau et avec la ligne de commande.
Puis-je convertir un fichier .dmp de exp ou expdp ?
Pas directement : Data Pump et l'Export d'origine écrivent un format binaire que seul Oracle lit. Importez-le dans n'importe quelle base Oracle, Oracle Database Free tourne dans Docker, et exportez-le avec Database Export de SQL Developer ou SQLcl. Le convertisseur le signale quand il reçoit un fichier .dmp.
De quelles versions de Oracle l'export peut-il venir ?
Les exports de SQL Developer et SQLcl, avec les définitions de tables de DBMS_METADATA, depuis Oracle 11g. Chaque export de test vient de Oracle 21c et 23ai, dans des sessions américaine, allemande et ISO, et est comparé à sa source cellule par cellule.
Que deviennent NUMBER, DATE et les autres types Oracle ?
NUMBER devient numeric, ou bigint si chaque valeur est un nombre entier ; DATE devient timestamp et garde son heure ; CLOB text, BLOB et RAW bytea, BINARY_DOUBLE double precision avec NaN et Infinity, et les intervalles interval.
Les colonnes d'identité et les séquences sont-elles gardées ?
Oui. Les colonnes d'identité restent des colonnes d'identité et reprennent après l'identifiant le plus élevé, tout comme une colonne qu'un déclencheur remplit depuis une séquence. Les séquences reprennent là où l'export indique qu'elles en étaient.
Et les packages, procédures et déclencheurs PL/SQL ?
Ils sont listés par nom dans le rapport et ne sont pas convertis, car chaque base de données les écrit dans son propre langage.
Sur quelles versions de PostgreSQL le script se charge-t-il ?
PostgreSQL 13 à 18. Le même script se charge sur chacune, et s'arrête à la première erreur en cas de problème.