{"id":6751,"date":"2026-04-20T13:19:11","date_gmt":"2026-04-20T11:19:11","guid":{"rendered":"https:\/\/rootfan.com\/?p=6751"},"modified":"2026-04-20T14:29:39","modified_gmt":"2026-04-20T12:29:39","slug":"exemple-de-migration-ora2pg","status":"publish","type":"post","link":"https:\/\/rootfan.com\/fr\/ora2pg-migration-example\/","title":{"rendered":"Exemple de migration ora2pg\u00a0: Sch\u00e9ma Oracle HR vers PostgreSQL"},"content":{"rendered":"<p>Le sch\u00e9ma RH d'Oracle est ce qui se rapproche le plus d'une r\u00e9f\u00e9rence universelle dans le monde des bases de donn\u00e9es.<\/p>\n\n\n\n<p>Chaque DBA Oracle l'a vu. <\/p>\n\n\n\n<p>Chaque consultant en migration l'utilise pour expliquer le d\u00e9roulement du processus.<\/p>\n\n\n\n<p>Ce qui le rend vraiment utile comme \u00e9tude de cas, ce n'est pas sa taille \u2014 il s'agit de sept tables et de 107 employ\u00e9s. <\/p>\n\n\n\n<p>Ce qui le rend utile, c'est qu'il contient, sous une forme minimale, les sch\u00e9mas exacts qui rendent les migrations d'Oracle vers PostgreSQL non triviales : s\u00e9quences li\u00e9es \u00e0 des d\u00e9clencheurs, proc\u00e9dures stock\u00e9es, incompatibilit\u00e9s de types qui compilent proprement et \u00e9chouent \u00e0 l'ex\u00e9cution.<\/p>\n\n\n\n<p>J'ai ex\u00e9cut\u00e9 la migration compl\u00e8te en utilisant ora2pg 25.0 avec Oracle 19c comme source et PostgreSQL 18 comme cible. <\/p>\n\n\n\n<p>Ce post couvre les cinq choses qui ont n\u00e9cessit\u00e9 une intervention manuelle \u2014 et pourquoi chacune d'elles est beaucoup plus importante sur un sch\u00e9ma de production que sur une d\u00e9mo.<\/p>\n\n\n\n<!--more-->\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table des mati\u00e8res<\/h2><nav><ul><li><a href=\"#what-hr-contains\">Ce que les RH contiennent<\/a><ul><li><a href=\"#1-number-type-mapping-the-monetary-column-trap\">1. Mappage des types NUM\u00c9RIQUES : le pi\u00e8ge des colonnes mon\u00e9taires<\/a><\/li><li><a href=\"#2-the-sequence-trigger-pattern\">2. Le mod\u00e8le de d\u00e9clencheur de s\u00e9quence<\/a><\/li><li><a href=\"#3-type-in-procedure-parameter-signatures\">3. %TYPE dans les signatures de param\u00e8tres de proc\u00e9dure<\/a><\/li><li><a href=\"#4-the-statement_timestamp-type-mismatch\">4. L'incompatibilit\u00e9 de type statement_timestamp()<\/a><\/li><li><a href=\"#5-fk-constraints-are-not-re-applied-after-data-load\">5. Les contraintes FK ne sont pas r\u00e9appliqu\u00e9es apr\u00e8s le chargement des donn\u00e9es<\/a><\/li><\/ul><\/li><li><a href=\"#running-the-migration-every-command-in-order\">Ex\u00e9cution de la migration\u00a0: chaque commande dans l'ordre<\/a><ul><li><a href=\"#environment\">Environnement<\/a><\/li><li><a href=\"#step-1-collect-oracle-statistics\">\u00c9tape 1 \u2014 Collecter les statistiques Oracle<\/a><\/li><li><a href=\"#step-2-create-the-output-directory\">\u00c9tape 2 \u2014 Cr\u00e9ez le r\u00e9pertoire de sortie<\/a><\/li><li><a href=\"#step-3-minimal-ora2pg-conf\">\u00c9tape 3 \u2014 ora2pg.conf minimale<\/a><\/li><li><a href=\"#step-4-generate-the-assessment-report\">\u00c9tape 4 \u2014 G\u00e9n\u00e9rer le rapport d'\u00e9valuation<\/a><\/li><li><a href=\"#step-5-analyse-column-types\">\u00c9tape 5 \u2014 Analyser les types de colonnes<\/a><\/li><li><a href=\"#step-6-add-modify_type-and-re-save-the-config\">\u00c9tape 6 \u2014 Ajouter MODIFY_TYPE et r\u00e9enregistrer la configuration<\/a><\/li><li><a href=\"#step-7-run-the-exports\">\u00c9tape 7 \u2014 Ex\u00e9cuter les exportations<\/a><\/li><li><a href=\"#step-8-write-the-manual-fix-files\">\u00c9tape 8 \u2014 R\u00e9diger les fichiers de correctifs manuels<\/a><\/li><li><a href=\"#step-9-create-the-database\">\u00c9tape 9 \u2014 Cr\u00e9er la base de donn\u00e9es<\/a><\/li><li><a href=\"#step-10-load-in-dependency-order\">\u00c9tape 10 \u2014 Charger dans l'ordre des d\u00e9pendances<\/a><\/li><li><a href=\"#step-11-re-apply-fk-constraints\">\u00c9tape 11 \u2014 R\u00e9appliquer les contraintes FK<\/a><\/li><li><a href=\"#step-12-load-the-remaining-files\">\u00c9tape 12 \u2014 Charger les fichiers restants<\/a><\/li><li><a href=\"#step-13-static-analysis-with-plpgsql_check\">\u00c9tape 13 \u2014 Analyse statique avec plpgsql_check<\/a><\/li><li><a href=\"#step-14-run-the-test-comparison\">\u00c9tape 14 \u2014 Lancez la comparaison TEST<\/a><\/li><li><a href=\"#what-the-final-test-report-looks-like\">\u00c0 quoi ressemble le rapport final de TEST<\/a><\/li><\/ul><\/li><li><a href=\"#summary\">En r\u00e9sum\u00e9<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-hr-contains\">Ce que les RH contiennent<\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/oracle-samples\/db-sample-schemas\/tree\/main\/human_resources\" rel=\"nofollow noopener\" target=\"_blank\">Le sch\u00e9ma RH<\/a> est le sch\u00e9ma de r\u00e9f\u00e9rence standard d'Oracle, livr\u00e9 avec chaque installation Oracle et disponible sur GitHub. <\/p>\n\n\n\n<p>Elle poss\u00e8de sept tables couvrant la hi\u00e9rarchie RH standard : r\u00e9gions, pays, sites, d\u00e9partements, postes, employ\u00e9s et ant\u00e9c\u00e9dents professionnels. <\/p>\n\n\n\n<p>Il a trois s\u00e9quences autonomes (<code>employes_seq<\/code>, <code>departments_seq<\/code>, <code>locations_seq<\/code>), une vue (<code>EMP_DETAILS_VIEW<\/code>), deux proc\u00e9dures stock\u00e9es (<code>s\u00e9curiser les DML<\/code>, <code>ajouter_historique_professionnel<\/code>), et cinq d\u00e9clencheurs. <\/p>\n\n\n\n<p>Trois de ces d\u00e9clencheurs existent uniquement pour appeler <code>sequence.NEXTVAL<\/code> sur INSERT \u2014 un mod\u00e8le que les d\u00e9veloppeurs Oracle utilisaient auparavant <code>D\u00c9FAUT<\/code> avec des s\u00e9quences fut largement adopt\u00e9. <\/p>\n\n\n\n<p>Les deux restants impl\u00e9mentent la logique m\u00e9tier : l'un restreint les op\u00e9rations DML aux heures de bureau, l'autre \u00e9crit une ligne d'historique lorsqu'un employ\u00e9 change de poste.<\/p>\n\n\n\n<p>ora2pg \u00e9value le sch\u00e9ma RH <strong>B-5<\/strong>: r\u00e9\u00e9criture du code requise, environ une journ\u00e9e de travail pour un expert PostgreSQL. <\/p>\n\n\n\n<p>La classification est enti\u00e8rement aliment\u00e9e par les proc\u00e9dures stock\u00e9es et les d\u00e9clencheurs. <\/p>\n\n\n\n<p>Les tables, les vues et les donn\u00e9es migrent sans intervention.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1-number-type-mapping-the-monetary-column-trap\">1. Mappage des types NUM\u00c9RIQUES : le pi\u00e8ge des colonnes mon\u00e9taires<\/h3>\n\n\n\n<p>ora2pg a deux directives qui g\u00e8rent la plupart <code>NOMBRE<\/code> colonnes automatiquement.<\/p>\n\n\n\n<p><code>PG_INTEGER_TYPE 1<\/code> cartes <code>NOMBRE(p)<\/code> colonnes sans \u00e9chelle vers le type entier correct : la pr\u00e9cision 1-4 devient <code>petit entier<\/code>, 5\u20139 devient <code>entier<\/code>, 10\u201318 devient <code>grand entier<\/code>. <\/p>\n\n\n\n<p>Pour le sch\u00e9ma RH, cela signifie <code>NUM\u00c9RO_EMPLOY\u00c9 NUMBER(6)<\/code> devient <code>ENTIER<\/code>, <code>DEPARTEMENT_ID NOMBRE(4)<\/code> devient <code>PETIT ENTIER<\/code> \u2014 aucun travail manuel n\u00e9cessaire.<\/p>\n\n\n\n<p>Le pi\u00e8ge est <code>PG_NUMERIC_TYPE 1<\/code>, qui g\u00e8re les colonnes ayant une \u00e9chelle d\u00e9clar\u00e9e. <\/p>\n\n\n\n<p>Cette directive ne se contente pas de regarder le type d\u00e9clar\u00e9 \u2014 elle interroge les valeurs de donn\u00e9es r\u00e9elles. <\/p>\n\n\n\n<p>Si un <code>NOMBRE(8,2)<\/code> la colonne contient actuellement uniquement des valeurs enti\u00e8res, elle associe la colonne \u00e0 <code>double pr\u00e9cision<\/code> plut\u00f4t que <code>num\u00e9rique<\/code>.<\/p>\n\n\n\n<p><code>EMPLOY\u00c9S.SALAIRE<\/code> est <code>NOMBRE(8,2)<\/code> dans Oracle. <\/p>\n\n\n\n<p>Chaque salaire dans le jeu de donn\u00e9es RH est un nombre entier : 24000, 17000, 9000. Avec <code>PG_NUMERIC_TYPE 1<\/code> active, ora2pg maps <code>SALAIRE<\/code> \u00e0 <code>double pr\u00e9cision<\/code>.<\/p>\n\n\n\n<p><code>double pr\u00e9cision<\/code> est un type \u00e0 virgule flottante. <\/p>\n\n\n\n<p>Les types \u00e0 virgule flottante sont inappropri\u00e9s pour les valeurs mon\u00e9taires \u2014 ils ne peuvent pas repr\u00e9senter exactement toutes les fractions d\u00e9cimales, ce qui produit des erreurs d'arrondi dans les calculs financiers. <\/p>\n\n\n\n<p>Le type correct pour le salaire est <code>num\u00e9rique<\/code>.<\/p>\n\n\n\n<p>La correction est un <code>MODIFICATION_TYPE<\/code> substituer<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE    EMPLOYEES:SALARY:numeric\n<\/pre><\/div>\n\n\n<p>Dans chaque migration de production r\u00e9elle que j'\u00e9value, les colonnes de salaire et autres colonnes mon\u00e9taires n\u00e9cessitent cette substitution. <\/p>\n\n\n\n<p>Le <code>PG_NUMERIC_TYPE<\/code> directive fait son travail correctement \u2014 elle d\u00e9duit les types des donn\u00e9es. <\/p>\n\n\n\n<p>L'inf\u00e9rence est tout simplement fausse pour l'argent, o\u00f9 la pr\u00e9cision d\u00e9clar\u00e9e compte plus que ce que les donn\u00e9es actuelles contiennent.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-the-sequence-trigger-pattern\">2. Le mod\u00e8le de d\u00e9clencheur de s\u00e9quence<\/h3>\n\n\n\n<p>Dans Oracle, le mod\u00e8le standard pour les cl\u00e9s primaires auto-incr\u00e9ment\u00e9es \u00e9tait :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Cr\u00e9er une s\u00e9quence autonome.<\/li>\n\n\n\n<li>Cr\u00e9er un d\u00e9clencheur `BEFORE INSERT` qui s'ex\u00e9cute pour chaque ligne et attribue <code>sequence.NEXTVAL<\/code> \u00e0 la colonne ID.<\/li>\n<\/ol>\n\n\n\n<p>Voici comment les RH sont construites. <code>EMPLOY\u00c9S.EMPLOY\u00c9_ID<\/code> n'a pas <code>D\u00c9FAUT<\/code> clause dans Oracle \u2014 le d\u00e9clencheur la remplit \u00e0 chaque INSERT.<\/p>\n\n\n\n<p>Dans PostgreSQL, l'\u00e9quivalent est une colonne <code>DEFAULT nextval('sequence_name')<\/code>. <\/p>\n\n\n\n<p>Lorsqu'une ligne est ins\u00e9r\u00e9e sans sp\u00e9cifier la colonne ID, PostgreSQL appelle <code>nextval()<\/code> automatiquement. Aucun d\u00e9clencheur n'est n\u00e9cessaire.<\/p>\n\n\n\n<p>ora2pg convertit les d\u00e9clencheurs de s\u00e9quence Oracle \u2014 il les exporte en tant que fonctions de d\u00e9clenchement PostgreSQL. <\/p>\n\n\n\n<p>Mais l'exportation TABLE n'ajoute pas <code>DEFAULT nextval(...)<\/code> aux d\u00e9finitions de colonnes, car Oracle n'en a jamais eu. Apr\u00e8s avoir charg\u00e9 le DDL export\u00e9, les tables n'ont pas de g\u00e9n\u00e9ration d'ID automatique. Tout INSERT sans un explicit <code>identifiant_employ\u00e9<\/code> \u00e9chouera avec <code>La valeur nulle dans la colonne viole la contrainte NOT NULL<\/code>.<\/p>\n\n\n\n<p>La solution consiste \u00e0 ajouter les valeurs par d\u00e9faut apr\u00e8s le chargement des d\u00e9clencheurs\u00a0:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nALTER TABLE hr.employees   ALTER COLUMN employee_id   SET DEFAULT nextval(&#039;hr.employees_seq&#039;);\nALTER TABLE hr.departments ALTER COLUMN department_id SET DEFAULT nextval(&#039;hr.departments_seq&#039;);\nALTER TABLE hr.locations   ALTER COLUMN location_id   SET DEFAULT nextval(&#039;hr.locations_seq&#039;);\n<\/pre><\/div>\n\n\n<p>Liez \u00e9galement chaque s\u00e9quence \u00e0 sa colonne avec <code>PROPRI\u00c9T\u00c9 DE<\/code> de sorte que la suppression de la table se propage automatiquement \u00e0 la s\u00e9quence. <\/p>\n\n\n\n<p>Sans <code>PROPRI\u00c9T\u00c9 DE<\/code>, les s\u00e9quences deviennent orphelines et doivent \u00eatre abandonn\u00e9es s\u00e9par\u00e9ment.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-type-in-procedure-parameter-signatures\">3. %TYPE dans les signatures de param\u00e8tres de proc\u00e9dure<\/h3>\n\n\n\n<p><code>ajouter_historique_professionnel<\/code> est export\u00e9 par ora2pg avec la syntaxe du type ancr\u00e9 d'Oracle dans sa liste de param\u00e8tres :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- What ora2pg exports\nCREATE OR REPLACE PROCEDURE hr.add_job_history (\n    p_emp_id        job_history.employee_id%TYPE,\n    p_start_date    job_history.start_date%TYPE,\n    ...\n<\/pre><\/div>\n\n\n<p>PostgreSQL prend en charge <code>%TYPE<\/code> \u00e0 l'int\u00e9rieur du corps d'une fonction ou d'une proc\u00e9dure pour les d\u00e9clarations de variables. Il ne prend pas en charge <code>%TYPE<\/code> dans la signature du param\u00e8tre. <\/p>\n\n\n\n<p>Le chargement du fichier export\u00e9 \u00e9choue imm\u00e9diatement avec une erreur de syntaxe.<\/p>\n\n\n\n<p>La correction consiste \u00e0 substituer les types concrets :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE OR REPLACE PROCEDURE hr.add_job_history (\n    p_emp_id        integer,\n    p_start_date    timestamp,\n    p_end_date      timestamp,\n    p_job_id        varchar(10),\n    p_department_id smallint\n)\n<\/pre><\/div>\n\n\n<p>Les types corrects proviennent de l'analyse des colonnes effectu\u00e9e avant la migration \u2014 <code>EMPLOYEE_ID<\/code> est <code>ENTIER<\/code>, <code>DATE_DEBUT<\/code> est <code>HORODATEUR<\/code>, et ainsi de suite. <\/p>\n\n\n\n<p>C'est pourquoi l'\u00e9tape d'analyse des types n'est pas facultative : vous avez besoin des types PostgreSQL finaux pour \u00e9crire cette correction.<\/p>\n\n\n\n<p>Dans un sch\u00e9ma de production comportant des dizaines de proc\u00e9dures stock\u00e9es, ce mod\u00e8le appara\u00eet fr\u00e9quemment. <\/p>\n\n\n\n<p>Chaque proc\u00e9dure qui utilise des param\u00e8tres de type ancr\u00e9 dans Oracle n\u00e9cessite la m\u00eame substitution manuelle avant de pouvoir \u00eatre charg\u00e9e dans PostgreSQL.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"4-the-statement_timestamp-type-mismatch\">4. L'incompatibilit\u00e9 de type statement_timestamp()<\/h3>\n\n\n\n<p>Le <code>update_job_history<\/code> d\u00e9clencher des appels <code>ajouter_historique_professionnel<\/code> pour \u00e9crire un historique de ligne lorsqu'un employ\u00e9 change de poste. <\/p>\n\n\n\n<p>Il doit passer l'heure actuelle en tant que <code>date_de_fin<\/code> argument.<\/p>\n\n\n\n<p>ora2pg convertit Oracle en <code>SYSDATE<\/code> \u00e0 <code>statement_timestamp()<\/code>. <code>statement_timestamp()<\/code> retours <code>horodatage avec fuseau horaire<\/code> dans PostgreSQL. <code>ajouter_historique_professionnel<\/code> attend <code>horodatage<\/code> (sans fuseau horaire) pour <code>date_de_fin<\/code>.<\/p>\n\n\n\n<p>PostgreSQL ne convertit pas implicitement <code>timestamptz<\/code> \u00e0 <code>horodatage<\/code> dans les appels de proc\u00e9dure. <\/p>\n\n\n\n<p>La fonction de d\u00e9clenchement se charge sans erreur \u2014 <code>CR\u00c9ER UNE FONCTION<\/code> r\u00e9ussit. <\/p>\n\n\n\n<p>L'incoh\u00e9rence de type n'appara\u00eet qu'\u00e0 l'ex\u00e9cution, la premi\u00e8re fois qu'un employ\u00e9 change de poste ou de d\u00e9partement.<\/p>\n\n\n\n<p>C'est la cat\u00e9gorie de bug de migration la plus insidieuse : du code qui compile sans erreur et qui \u00e9choue en production.<\/p>\n\n\n\n<p>J'utilise <code>plpgsql_check<\/code> pour intercepter exactement cette classe d'erreur. <\/p>\n\n\n\n<p>Il effectue une analyse statique sur les corps de fonctions compil\u00e9s et signale les \u00e9carts de types, les variables manquantes et les appels de proc\u00e9dure invalides, et ce, avant m\u00eame tout test d'application.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE EXTENSION plpgsql_check;\n<\/pre><\/div>\n\n\n<p>L'ex\u00e9cution de plpgsql_check sur la fonction de d\u00e9clenchement de ce laboratoire a produit\u00a0:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ntrigger_fct_update_job_history() | error | procedure add_job_history(integer,\ntimestamp without time zone, timestamp with time zone, character varying, smallint)\ndoes not exist\n<\/pre><\/div>\n\n\n<p>La correction est un seul moulage :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCALL add_job_history(OLD.employee_id, OLD.hire_date, statement_timestamp()::TIMESTAMP,\n                     OLD.job_id, OLD.department_id);\n<\/pre><\/div>\n\n\n<p><code>plpgsql_check<\/code> n\u2019est pas une partie int\u00e9grante du flux de travail ora2pg \u2014 il n\u2019est pas ex\u00e9cut\u00e9 par d\u00e9faut, n\u2019est pas mentionn\u00e9 dans la documentation ora2pg et n\u2019est inclus par d\u00e9faut dans aucun package PostgreSQL. <\/p>\n\n\n\n<p>Je l'ex\u00e9cute \u00e0 chaque migration avant de transmettre le code migr\u00e9 \u00e0 l'\u00e9quipe d'application. Il a d\u00e9tect\u00e9 des bugs d'ex\u00e9cution sur chaque sch\u00e9ma de production que j'ai migr\u00e9.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"5-fk-constraints-are-not-re-applied-after-data-load\">5. Les contraintes FK ne sont pas r\u00e9appliqu\u00e9es apr\u00e8s le chargement des donn\u00e9es<\/h3>\n\n\n\n<p>L'exportation COPY d'ora2pg supprime toutes les contraintes de cl\u00e9 \u00e9trang\u00e8re au d\u00e9but du fichier de donn\u00e9es, charge les donn\u00e9es, puis valide. <\/p>\n\n\n\n<p>Il ne r\u00e9tablit pas les contraintes FK apr\u00e8s le commit. <\/p>\n\n\n\n<p>Ceci est un bug connu \u2014 <a href=\"https:\/\/github.com\/darold\/ora2pg\/issues\" rel=\"nofollow noopener\" target=\"_blank\">probl\u00e8me #1960 dans le d\u00e9p\u00f4t darold\/ora2pg<\/a>.<\/p>\n\n\n\n<p>Apr\u00e8s le chargement du fichier de donn\u00e9es, la base de donn\u00e9es migr\u00e9e ne poss\u00e8de aucune contrainte de cl\u00e9 \u00e9trang\u00e8re. <\/p>\n\n\n\n<p>Le <code>ora2pg -t TEST<\/code> La comparaison montrera que toutes les tables ont 0 FK dans PostgreSQL, m\u00eame si les d\u00e9finitions de FK existent dans le DDL export\u00e9.<\/p>\n\n\n\n<p>La solution consiste \u00e0 r\u00e9appliquer les contraintes de cl\u00e9 \u00e9trang\u00e8re depuis l'exportation de la table apr\u00e8s le chargement des donn\u00e9es :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ngrep &#039;ADD CONSTRAINT.*FOREIGN KEY&#039; HR_tables.sql | \\\n  PGPASSWORD=hr psql -U hr -d hrdb -h localhost\n<\/pre><\/div>\n\n\n<p>Pour le sch\u00e9ma RH, cela restaure 10 contraintes de cl\u00e9 \u00e9trang\u00e8re sur cinq tables. <\/p>\n\n\n\n<p>Sur un sch\u00e9ma de production comportant des centaines de tables, le nombre de cl\u00e9s \u00e9trang\u00e8res (FK) est un point de contr\u00f4le de validation essentiel : s'il ne correspond pas au d\u00e9compte d'Oracle apr\u00e8s la migration, le mod\u00e8le de donn\u00e9es est incomplet.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"running-the-migration-every-command-in-order\">Ex\u00e9cution de la migration\u00a0: chaque commande dans l'ordre<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"environment\">Environnement<\/h3>\n\n\n\n<p>Deux VM sur le m\u00eame r\u00e9seau. <code>srv1<\/code> (192.168.0.180) ex\u00e9cute Oracle 19c avec le sch\u00e9ma HR dans PDB <code>pdb1<\/code>. <code>srv2<\/code> (192.168.0.181) ex\u00e9cute Ubuntu avec PostgreSQL 18 et ora2pg install\u00e9s. Toutes les commandes ci-dessous sont ex\u00e9cut\u00e9es sur <code>srv2<\/code> sauf indication contraire.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-1-collect-oracle-statistics\">\u00c9tape 1 \u2014 Collecter les statistiques Oracle<\/h3>\n\n\n\n<p>Ex\u00e9cutez ceci sur <code>srv1<\/code> Avant de g\u00e9n\u00e9rer le rapport d'\u00e9valuation, les estimations de co\u00fbts d'ora2pg sont bas\u00e9es sur les statistiques stock\u00e9es d'Oracle \u2014 des statistiques obsol\u00e8tes produisent des estimations inexactes du nombre de lignes et des unit\u00e9s de migration.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- On Oracle (srv1)\nBEGIN\n  DBMS_STATS.GATHER_SCHEMA_STATS(&#039;HR&#039;);\n  DBMS_STATS.GATHER_DICTIONARY_STATS;\nEND;\n\/\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-2-create-the-output-directory\">\u00c9tape 2 \u2014 Cr\u00e9ez le r\u00e9pertoire de sortie<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nmkdir -p \/home\/fernando\/ora2pg-hr\/output\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-3-minimal-ora2pg-conf\">\u00c9tape 3 \u2014 ora2pg.conf minimale<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# \/etc\/ora2pg\/ora2pg.conf\nORACLE_DSN      dbi:Oracle:host=192.168.0.180;service_name=pdb1;port=1521\nORACLE_USER     system\nORACLE_PWD      &lt;system password&gt;\nSCHEMA          HR\nEXPORT_SCHEMA   1\nOUTPUT_DIR      \/home\/fernando\/ora2pg-hr\/output\n<\/pre><\/div>\n\n\n<p>Utilisation <code>syst\u00e8me<\/code>, pas <code>rh<\/code> \u2014 l'exportation COPY se lit <code>v$base de donn\u00e9es<\/code> qui n\u00e9cessite le privil\u00e8ge DBA.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-4-generate-the-assessment-report\">\u00c9tape 4 \u2014 G\u00e9n\u00e9rer le rapport d'\u00e9valuation<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t SHOW_REPORT --estimate_cost --dump_as_html \\\n  &gt; \/home\/fernando\/ora2pg-hr\/output\/report-hr.html\n<\/pre><\/div>\n\n\n<p>Ouvrir le HTML dans un navigateur. <\/p>\n\n\n\n<p>Scores RH <strong>B-5<\/strong>, environ une journ\u00e9e-homme. <\/p>\n\n\n\n<p>Les moteurs de co\u00fbts sont les deux proc\u00e9dures stock\u00e9es et les deux d\u00e9clencheurs de logique m\u00e9tier.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-5-analyse-column-types\">\u00c9tape 5 \u2014 Analyser les types de colonnes<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t SHOW_COLUMN &gt; \/home\/fernando\/ora2pg-hr\/output\/columns-hr.txt\n<\/pre><\/div>\n\n\n<p>Puis interroger les plages de donn\u00e9es r\u00e9elles sur Oracle pour tout <code>NOMBRE<\/code> colonne sans pr\u00e9cision d\u00e9clar\u00e9e :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- On Oracle (srv1)\nSELECT &#039;REGIONS.REGION_ID&#039;   AS col, MIN(region_id), MAX(region_id) FROM regions\nUNION ALL\nSELECT &#039;COUNTRIES.REGION_ID&#039;,         MIN(region_id), MAX(region_id) FROM countries;\n<\/pre><\/div>\n\n\n<p>Les deux au maximum \u00e0 50 \u2014 <code>ENTIER<\/code> est le mappage correct.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-6-add-modify_type-and-re-save-the-config\">\u00c9tape 6 \u2014 Ajouter MODIFY_TYPE et r\u00e9enregistrer la configuration<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE    REGIONS:REGION_ID:integer,COUNTRIES:REGION_ID:integer,EMPLOYEES:SALARY:numeric\n<\/pre><\/div>\n\n\n<p><code>EMPLOY\u00c9S:SALAIRE:num\u00e9rique<\/code> est requis car <code>PG_NUMERIC_TYPE 1<\/code> inspecte les donn\u00e9es r\u00e9elles, ne trouve que des valeurs de salaire en nombres entiers, et mappe <code>NOMBRE(8,2)<\/code> \u00e0 <code>double pr\u00e9cision<\/code> \u2014 inappropri\u00e9 pour une colonne mon\u00e9taire. <\/p>\n\n\n\n<p>Les trois remplacements sur une seule ligne \u2014 <code>MODIFICATION_TYPE<\/code> ignore tout ce qui suit la premi\u00e8re ligne.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-7-run-the-exports\">\u00c9tape 7 \u2014 Ex\u00e9cuter les exportations<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t TABLE           -o HR_tables.sql           2&gt;&amp;1 | tee output\/table-export-hr.log\nora2pg -t VIEW            -o HR_views.sql            2&gt;&amp;1 | tee output\/view-export-hr.log\nora2pg -t SEQUENCE        -o HR_sequences.sql        2&gt;&amp;1 | tee output\/sequence-export-hr.log\nora2pg -t SEQUENCE_VALUES -o HR_sequence_values.sql  2&gt;&amp;1 | tee output\/seqval-export-hr.log\nora2pg -t TRIGGER         -o HR_triggers.sql         2&gt;&amp;1 | tee output\/trigger-export-hr.log\nora2pg -t PROCEDURE       -o HR_procedures.sql       2&gt;&amp;1 | tee output\/procedure-export-hr.log\nora2pg -t COPY            -o HR_data.sql             2&gt;&amp;1 | tee output\/copy-export-hr.log\n<\/pre><\/div>\n\n\n<p>L'exportation TABLE fait <strong>non<\/strong> inclure les s\u00e9quences \u2014 l'exportation SEQUENCE s\u00e9par\u00e9e est obligatoire. Sans elle, le chargement des sequence_values \u00e0 l'\u00e9tape 12 \u00e9choue avec le message \u201c relation does not exist \u201d.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-8-write-the-manual-fix-files\">\u00c9tape 8 \u2014 R\u00e9diger les fichiers de correctifs manuels<\/h3>\n\n\n\n<p>Deux fichiers qui ne peuvent pas \u00eatre g\u00e9n\u00e9r\u00e9s \u00e0 partir de la sortie ora2pg.<\/p>\n\n\n\n<p><code>HR_procedures_fixed.sql<\/code> \u2014 remplace <code>%TYPE<\/code> param\u00e8tres ancr\u00e9s avec des types concrets :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSET search_path = hr,public;\n\nCREATE OR REPLACE PROCEDURE hr.add_job_history (\n    p_emp_id        integer,\n    p_start_date    timestamp,\n    p_end_date      timestamp,\n    p_job_id        varchar(10),\n    p_department_id smallint\n) AS $body$\nBEGIN\n  INSERT INTO job_history(employee_id, start_date, end_date, job_id, department_id)\n    VALUES (p_emp_id, p_start_date, p_end_date, p_job_id, p_department_id);\nEND;\n$body$\nLANGUAGE PLPGSQL SECURITY DEFINER;\n\nCREATE OR REPLACE PROCEDURE hr.secure_dml () AS $body$\nBEGIN\n  IF TO_CHAR(clock_timestamp(), &#039;HH24:MI&#039;) NOT BETWEEN &#039;08:00&#039; AND &#039;18:00&#039;\n      OR TO_CHAR(clock_timestamp(), &#039;DY&#039;) IN (&#039;SAT&#039;, &#039;SUN&#039;) THEN\n      RAISE EXCEPTION &#039;%&#039;, &#039;You may only make changes during normal office hours&#039;\n        USING ERRCODE = &#039;45205&#039;;\n  END IF;\nEND;\n$body$\nLANGUAGE PLPGSQL SECURITY DEFINER;\n<\/pre><\/div>\n\n\n<p><code>HR_corrections_d\u00e9clencheurs.sql<\/code> \u2014 lance <code>statement_timestamp()<\/code> \u00e0 \u00e9viter <code>timestamptz<\/code> inad\u00e9quation, et ajoute les valeurs par d\u00e9faut de s\u00e9quence aux colonnes d'ID\u00a0:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSET search_path = hr,public;\n\nCREATE OR REPLACE FUNCTION hr.trigger_fct_update_job_history() RETURNS trigger AS $BODY$\nBEGIN\n  CALL add_job_history(OLD.employee_id, OLD.hire_date,\n                       statement_timestamp()::TIMESTAMP,\n                       OLD.job_id, OLD.department_id);\n  RETURN NEW;\nEND\n$BODY$\nLANGUAGE &#039;plpgsql&#039; SECURITY DEFINER;\n\nALTER TABLE hr.employees   ALTER COLUMN employee_id   SET DEFAULT nextval(&#039;hr.employees_seq&#039;);\nALTER TABLE hr.departments ALTER COLUMN department_id SET DEFAULT nextval(&#039;hr.departments_seq&#039;);\nALTER TABLE hr.locations   ALTER COLUMN location_id   SET DEFAULT nextval(&#039;hr.locations_seq&#039;);\n\nALTER SEQUENCE hr.employees_seq   OWNED BY hr.employees.employee_id;\nALTER SEQUENCE hr.departments_seq OWNED BY hr.departments.department_id;\nALTER SEQUENCE hr.locations_seq   OWNED BY hr.locations.location_id;\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-9-create-the-database\">\u00c9tape 9 \u2014 Cr\u00e9er la base de donn\u00e9es<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;CREATE USER hr WITH PASSWORD &#039;hr&#039;;&quot;\nsudo -u postgres psql -c &quot;CREATE DATABASE hrdb OWNER hr;&quot;\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-10-load-in-dependency-order\">\u00c9tape 10 \u2014 Charger dans l'ordre des d\u00e9pendances<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Tables first \u2014 everything else depends on them\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_tables.sql\n\n# Sequences \u2014 must exist before data load and before sequence_values\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_sequences.sql\n\n# Data \u2014 loads rows via COPY FROM stdin; FKs are dropped inside the file before load\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_data.sql\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-11-re-apply-fk-constraints\">\u00c9tape 11 \u2014 R\u00e9appliquer les contraintes FK<\/h3>\n\n\n\n<p>La commande COPY d'ora2pg supprime toutes les contraintes FK avant le chargement des donn\u00e9es et ne les ajoute jamais. Sans cette \u00e9tape, la base de donn\u00e9es n'a aucune contrainte FK apr\u00e8s l'\u00e9tape 10.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(echo &quot;SET search_path TO hr;&quot;; \\\n grep &#039;ADD CONSTRAINT.*FOREIGN KEY&#039; \\\n   \/home\/fernando\/ora2pg-hr\/output\/HR_tables.sql) | \\\n  PGPASSWORD=hr psql -U hr -d hrdb -h localhost\n<\/pre><\/div>\n\n\n<p>V\u00e9rifier le compte :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost -c &quot;\nSELECT COUNT(*) FROM information_schema.table_constraints\nWHERE constraint_schema = &#039;hr&#039; AND constraint_type = &#039;FOREIGN KEY&#039;;&quot;\n<\/pre><\/div>\n\n\n<p>Attendu : <code>10<\/code><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-12-load-the-remaining-files\">\u00c9tape 12 \u2014 Charger les fichiers restants<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Reset sequences to Oracle&#039;s last values \u2014 run after data, not before\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_sequence_values.sql\n\n# Procedures \u2014 must load before triggers (triggers call these procedures)\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_procedures_fixed.sql\n\n# Triggers \u2014 after procedures\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_triggers.sql\n\n# Trigger fixes \u2014 replaces the timestamptz trigger function, adds sequence defaults\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_trigger_fixes.sql\n\n# View \u2014 after all tables are in place\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_views.sql\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-13-static-analysis-with-plpgsql_check\">\u00c9tape 13 \u2014 Analyse statique avec plpgsql_check<\/h3>\n\n\n\n<p>Installez l'extension, puis v\u00e9rifiez tout le code stock\u00e9 avant tout test d'application :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsudo apt install -y postgresql-18-plpgsql-check\nsudo -u postgres psql -d hrdb -c &quot;CREATE EXTENSION plpgsql_check;&quot;\n<\/pre><\/div>\n\n\n<p>V\u00e9rifier les proc\u00e9dures et les fonctions :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost -c &quot;\nSELECT pg_proc.oid::regprocedure AS function, level, message\nFROM pg_proc,\n     plpgsql_check_function_tb(pg_proc.oid) AS pcf\nWHERE pronamespace = &#039;hr&#039;::regnamespace\n  AND prolang = (SELECT oid FROM pg_language WHERE lanname = &#039;plpgsql&#039;)\n  AND prorettype &lt;&gt; &#039;trigger&#039;::regtype\nORDER BY 1, 2;&quot;\n<\/pre><\/div>\n\n\n<p>V\u00e9rifier les fonctions de d\u00e9clenchement\u00a0:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost -c &quot;\nSELECT pg_proc.oid::regprocedure AS function, level, message\nFROM pg_proc\nJOIN pg_trigger ON pg_trigger.tgfoid = pg_proc.oid,\n     plpgsql_check_function_tb(pg_proc.oid, pg_trigger.tgrelid) AS pcf\nWHERE pg_proc.pronamespace = &#039;hr&#039;::regnamespace\n  AND pg_proc.prolang = (SELECT oid FROM pg_language WHERE lanname = &#039;plpgsql&#039;)\n  AND pg_proc.prorettype = &#039;trigger&#039;::regtype\nORDER BY 1, 2;&quot;\n<\/pre><\/div>\n\n\n<p>Attendu apr\u00e8s l'application de toutes les corrections : <code>(0 lignes)<\/code> sur les deux requ\u00eates.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-14-run-the-test-comparison\">\u00c9tape 14 \u2014 Lancez la comparaison TEST<\/h3>\n\n\n\n<p>Ajoutez la connexion PostgreSQL \u00e0 <code>ora2pg.conf<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPG_DSN      dbi:Pg:dbname=hrdb;host=localhost;port=5432\nPG_USER     hr\nPG_PWD      hr\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t TEST 2&gt;&amp;1 | tee \/home\/fernando\/ora2pg-hr\/output\/migration_diff-hr.txt\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"what-the-final-test-report-looks-like\">\u00c0 quoi ressemble le rapport final de TEST<\/h3>\n\n\n\n<p>Apr\u00e8s que les cinq correctifs sont appliqu\u00e9s, <code>ora2pg -t TEST<\/code> compare le code source Oracle et la cible PostgreSQL c\u00f4te \u00e0 c\u00f4te. Chaque section doit afficher OK, sauf deux faux positifs connus :<\/p>\n\n\n\n<p><strong>VALEUR PAR D\u00c9FAUT DE COLONNE DIFF<\/strong> \u2014 Oracle a utilis\u00e9 des d\u00e9clencheurs pour remplir les colonnes d'ID ; PostgreSQL utilise <code>DEFAULT nextval(...)<\/code>. Les trois tables auxquelles nous avons ajout\u00e9 des valeurs par d\u00e9faut pr\u00e9senteront une diff\u00e9rence de comptage. C'est la conception correcte de PostgreSQL, pas une erreur.<\/p>\n\n\n\n<p><strong>FONCTION COMPTER DIFF<\/strong> \u2014 PostgreSQL stocke la logique des d\u00e9clencheurs dans un objet de fonction distinct qui n'a pas d'\u00e9quivalent dans le dictionnaire de donn\u00e9es d'Oracle. Les deux fonctions de d\u00e9clencheur (<code>trigger_fct_secure_employees<\/code>, <code>d\u00e9clencheur_fct_mise_\u00e0_jour_historique_poste<\/code>) apparaissent comme des fonctions PostgreSQL suppl\u00e9mentaires sans \u00e9quivalent Oracle. Ceci est \u00e9galement attendu.<\/p>\n\n\n\n<p>Toute autre ligne DIFF dans la sortie de TEST est un r\u00e9el probl\u00e8me qui doit \u00eatre r\u00e9solu avant le cutover.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"summary\">En r\u00e9sum\u00e9<\/h2>\n\n\n\n<p>RH est un petit sch\u00e9ma. Corriger cinq probl\u00e8mes sur un jeu de donn\u00e9es de 107 lignes repr\u00e9sente une demi-journ\u00e9e de travail.<\/p>\n\n\n\n<p>Sur un sch\u00e9ma de production r\u00e9el \u2014 50 tables, 30 proc\u00e9dures stock\u00e9es, des cha\u00eenes de d\u00e9clencheurs complexes, 10 millions de lignes \u2014 les m\u00eames cinq cat\u00e9gories de probl\u00e8mes existent, multipli\u00e9es par 10 en volume.<\/p>\n\n\n\n<p>Le pi\u00e8ge de la colonne mon\u00e9taire affecte tous les sch\u00e9mas qui stockent des prix, des salaires ou des chiffres financiers. <\/p>\n\n\n\n<p>Le <code>%TYPE<\/code> Le probl\u00e8me de param\u00e8tre affecte tous les sch\u00e9mas avec des proc\u00e9dures stock\u00e9es. <\/p>\n\n\n\n<p>Le <code>statement_timestamp()<\/code> une incompatibilit\u00e9 de type affecte chaque d\u00e9clencheur qui enregistre un horodatage actuel. <\/p>\n\n\n\n<p>Le probl\u00e8me de r\u00e9application des cl\u00e9s \u00e9trang\u00e8res affecte tous les sch\u00e9mas comportant des cl\u00e9s \u00e9trang\u00e8res. <\/p>\n\n\n\n<p>Le mod\u00e8le s\u00e9quence-d\u00e9clencheur-par-d\u00e9faut affecte tous les sch\u00e9mas Oracle construits avant la version 12c.<\/p>\n\n\n\n<p>La valeur de l'ex\u00e9cution de la migration sur les RH en premier n'est pas la migration elle-m\u00eame, mais la cr\u00e9ation de la liste de contr\u00f4le que vous appliquez \u00e0 tous les sch\u00e9mas suivants.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><em>Si vous pr\u00e9voyez une migration d'Oracle vers PostgreSQL et souhaitez une \u00e9valuation ind\u00e9pendante de la complexit\u00e9, de l'effort et des risques avant de vous engager sur un calendrier, <a href=\"https:\/\/rootfan.com\/fr\/services\/\">Je propose un audit de migration \u00e0 prix fixe<\/a> qui produit exactement cela.<\/em><\/p>","protected":false},"excerpt":{"rendered":"<p>Oracle&#8217;s HR schema is the closest thing the database world has to a universal reference. Every Oracle DBA has seen it. Every migration consultant uses it to explain how the process works. What makes it genuinely useful as a case study is not its size \u2014 it is seven tables and 107 employees. What makes &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/rootfan.com\/fr\/ora2pg-migration-example\/\" class=\"more-link\">Continuer la lecture<span class=\"screen-reader-text\"> de &laquo;&nbsp;ora2pg Migration Example: Oracle HR Schema to PostgreSQL&nbsp;&raquo;<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":6758,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_focus_keyword":"ora2pg migration oracle hr schema postgresql","rank_math_title":"","rank_math_description":"Step-by-step ora2pg migration of Oracle HR schema to PostgreSQL. Covers NUMBER type mapping, sequence-trigger patterns, %TYPE parameters, and the FK re-apply bug.","rank_math_robots":"","rank_math_og_title":"","rank_math_og_description":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[146],"tags":[143,141,148,137,147,142,149,150],"class_list":["post-6751","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oracle-to-postgresql","tag-data-migration","tag-data-types","tag-hr-schema","tag-migration","tag-ora2pg","tag-pl-sql","tag-sequences","tag-triggers"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rootfan.com\/wp-content\/uploads\/pexels-photo-34191411.jpeg?fit=678%2C1300&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/comments?post=6751"}],"version-history":[{"count":6,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6751\/revisions"}],"predecessor-version":[{"id":6768,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6751\/revisions\/6768"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/media\/6758"}],"wp:attachment":[{"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/media?parent=6751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/categories?post=6751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/tags?post=6751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}