{"id":6744,"date":"2026-04-20T13:51:59","date_gmt":"2026-04-20T11:51:59","guid":{"rendered":"https:\/\/rootfan.com\/?p=6744"},"modified":"2026-04-22T00:16:54","modified_gmt":"2026-04-21T22:16:54","slug":"migration-doracle-vers-postgresql-avec-ora2pg-etape-par-etape","status":"publish","type":"post","link":"https:\/\/rootfan.com\/fr\/ora2pg-migration-oracle-to-postgres-step-by-step\/","title":{"rendered":"Migration d'Oracle vers Postgres \u00e9tape par \u00e9tape : Le sch\u00e9ma SH (partitions, vues mat\u00e9rialis\u00e9es, index bitmap)"},"content":{"rendered":"<p>Le sch\u00e9ma SH (Sales History) d'Oracle est l'exemple standard d'un entrep\u00f4t de donn\u00e9es en sch\u00e9ma \u00e9toile d'Oracle.<\/p>\n\n\n\n<p>Elle poss\u00e8de une table de faits centrale \"Sales\", six tables de dimensions, deux vues mat\u00e9rialis\u00e9es, des tables de faits partitionn\u00e9es par intervalle et des index bitmap sur des colonnes de faible cardinalit\u00e9.<\/p>\n\n\n\n<p>Toutes ces fonctionnalit\u00e9s existent dans chaque entrep\u00f4t de donn\u00e9es Oracle r\u00e9el.<\/p>\n\n\n\n<p>Et toutes elles exigent des d\u00e9cisions qui, sans d\u00e9tour <code>ora2pg<\/code> Ex\u00e9cuter ne fera pas pour vous.<\/p>\n\n\n\n<p>Ce post d\u00e9crit les \u00e9tapes exactes que j'ai suivies pour migrer SH d'Oracle 19c vers PostgreSQL 18 en utilisant <code>ora2pg<\/code>.<\/p>\n\n\n\n<p>Le sch\u00e9ma d'exemple SH d'Oracle est disponible sur <a href=\"https:\/\/github.com\/oracle-samples\/db-sample-schemas\/tree\/main\/sales_history\" rel=\"nofollow noopener\" target=\"_blank\">GitHub<\/a>.<\/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-sh-contains\">Qu'est-ce que SH contient<\/a><ul><li><a href=\"#problem-1-partitioned-tables-require-two-separate-exports\">Probl\u00e8me 1 : Les tables partitionn\u00e9es n\u00e9cessitent deux exportations distinctes<\/a><\/li><li><a href=\"#problem-2-number-38-columns-are-not-handled-by-pg-integer-type\">Probl\u00e8me 2 : Les colonnes NUMBER(38) ne sont pas g\u00e9r\u00e9es par le type PG_INTEGER_TYPE<\/a><\/li><li><a href=\"#problem-3-bitmap-indexes-keep-drop-or-replace\">Probl\u00e8me 3 : Index Bitmap \u2014 Conserver, Supprimer ou Remplacer<\/a><\/li><li><a href=\"#problem-4-materialized-view-refresh-is-no-longer-automatic\">Probl\u00e8me 4 : Le rafra\u00eechissement des vues mat\u00e9rialis\u00e9es n'est plus automatique<\/a><\/li><li><a href=\"#problem-5-dimension-objects-are-silently-dropped\">Probl\u00e8me 5 : Les objets dimensionnels sont silencieusement abandonn\u00e9s<\/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-populate-oracle-sh-data-using-sq-lcl\">\u00c9tape 1 \u2014 Remplir les donn\u00e9es Oracle SH \u00e0 l'aide de SQLcl<\/a><\/li><li><a href=\"#step-2-create-the-postgre-sql-database-and-user\">\u00c9tape 2 \u2014 Cr\u00e9er la base de donn\u00e9es et l'utilisateur PostgreSQL<\/a><\/li><li><a href=\"#step-3-create-the-output-directory\">\u00c9tape 3 \u2014 Cr\u00e9er le r\u00e9pertoire de sortie<\/a><\/li><li><a href=\"#step-4-configure-ora-2-pg-conf\">\u00c9tape 4 \u2014 Configurer ora2pg.conf<\/a><\/li><li><a href=\"#step-5-run-show-column-to-identify-number-38-columns\">\u00c9tape 5 \u2014 Ex\u00e9cuter SHOW_COLUMN pour identifier les colonnes NUMBER(38)<\/a><\/li><li><a href=\"#step-6-set-modify-type-overrides\">\u00c9tape 6 \u2014 D\u00e9finir les remplacements de `MODIFY_TYPE`<\/a><\/li><li><a href=\"#step-7-export-table\">\u00c9tape 7 \u2014 Exporter TABLEAU<\/a><\/li><li><a href=\"#step-8-fix-the-table-export-bitmap-indexes\">\u00c9tape 8 \u2013 Corriger l'exportation TABLE (index bitmap)<\/a><\/li><li><a href=\"#step-9-export-partition\">\u00c9tape 9 \u2014 Exporter PARTITION<\/a><\/li><li><a href=\"#step-10-export-mview\">\u00c9tape 10 \u2014 Exporter MVIEW<\/a><\/li><li><a href=\"#step-11-export-view\">\u00c9tape 11 \u2014 Exporter la VUE<\/a><\/li><li><a href=\"#step-12-export-data-copy\">\u00c9tape 12 \u2014 Exporter les donn\u00e9es (COPY)<\/a><\/li><li><a href=\"#step-13-load-in-the-correct-order\">\u00c9tape 13 \u2014 Charger dans le bon ordre<\/a><\/li><li><a href=\"#step-14-re-apply-fk-constraints\">\u00c9tape 14 \u2014 R\u00e9appliquer les contraintes FK<\/a><\/li><\/ul><\/li><li><a href=\"#what-the-final-test-report-looks-like\">\u00c0 quoi ressemble le rapport final de TEST<\/a><\/li><li><a href=\"#summary\">En r\u00e9sum\u00e9<\/a><\/li><li><a href=\"#next-steps\">Prochaines \u00e9tapes<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-sh-contains\">Qu'est-ce que SH contient<\/h2>\n\n\n\n<p>SH mod\u00e9lise un entrep\u00f4t de donn\u00e9es de vente au d\u00e9tail.<\/p>\n\n\n\n<p><strong>Tables de dimension<\/strong> CANAUX, PAYS, CLIENTS, PRODUITS, PROMOTIONS, HEURES<\/p>\n\n\n\n<p><strong>Tables de faits :<\/strong> VENTES, CO\u00dbTS \u2014 tous deux partitionn\u00e9s par plage selon TIME_ID<\/p>\n\n\n\n<p><strong>Vues mat\u00e9rialis\u00e9es :<\/strong> CAL_MOIS_VENTES_MV, FWEEK_PSCAT_VENTES_MV<\/p>\n\n\n\n<p><strong>Volume de donn\u00e9es :<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Tableau<\/th><th>Rang\u00e9es<\/th><\/tr><\/thead><tbody><tr><td>CANAUX<\/td><td>5<\/td><\/tr><tr><td>Pays<\/td><td>23<\/td><\/tr><tr><td>PROMOTIONS<\/td><td>503<\/td><\/tr><tr><td>PRODUITS<\/td><td>72<\/td><\/tr><tr><td>Temps<\/td><td>1,826<\/td><\/tr><tr><td>CLIENTS<\/td><td>55,500<\/td><\/tr><tr><td>CO\u00dbTS<\/td><td>82,112<\/td><\/tr><tr><td>VENTES<\/td><td>918,843<\/td><\/tr><tr><td>DONN\u00c9ES_D\u00c9MOGRAPHIQUES_SUPPL\u00c9MENTAIRES<\/td><td>4,500<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>La table SALES, avec pr\u00e8s d'un million de lignes, fait en sorte que l'exportation et le chargement COPY prennent 15 \u00e0 25 minutes.<\/p>\n\n\n\n<p>Planifiez cela avant de commencer.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"problem-1-partitioned-tables-require-two-separate-exports\">Probl\u00e8me 1 : Les tables partitionn\u00e9es n\u00e9cessitent deux exportations distinctes<\/h3>\n\n\n\n<p>SALES et COSTS sont partitionn\u00e9s par plage selon TIME_ID dans Oracle.<\/p>\n\n\n\n<p>PostgreSQL prend en charge le partitionnement d\u00e9claratif par intervalle avec la m\u00eame s\u00e9mantique.<\/p>\n\n\n\n<p>Le probl\u00e8me est que <code>ora2pg<\/code> diviser ceci en deux types d'exportation.<\/p>\n\n\n\n<p>Le <code>TABLE<\/code> export g\u00e9n\u00e8re le DDL de la table parente \u2014 le <code>PARTITION BY RANGE (time_id)<\/code> d\u00e9claration \u2014 mais pas de tables de partition enfant.<\/p>\n\n\n\n<p>Si vous chargez ceci et essayez d'ins\u00e9rer des lignes, PostgreSQL renvoie\u00a0:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ERREUR : aucune partition de la relation \" sales \" trouv\u00e9e pour la ligne<\/code><\/pre>\n\n\n\n<p>Les tables enfants partitionn\u00e9es proviennent d'un <code>Partition<\/code> exporter.<\/p>\n\n\n\n<p>Vous avez besoin des deux fichiers, et vous devez charger le DDL de la table parente avant les tables filles partitionn\u00e9es.<\/p>\n\n\n\n<p><strong>Ce que cela ressemble en pratique :<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- TABLE export generates:\nCREATE TABLE sh.sales (\n  prod_id       bigint NOT NULL,\n  cust_id       bigint NOT NULL,\n  time_id       date   NOT NULL,\n  channel_id    bigint NOT NULL,\n  promo_id      bigint NOT NULL,\n  quantity_sold numeric(10,2) NOT NULL,\n  amount_sold   numeric(10,2) NOT NULL\n) PARTITION BY RANGE (time_id);\n\n-- PARTITION export generates:\nCREATE TABLE sales_1995 PARTITION OF sh.sales\n  FOR VALUES FROM (MINVALUE) TO (&#039;1996-01-01&#039;);\nCREATE TABLE sales_1996 PARTITION OF sh.sales\n  FOR VALUES FROM (&#039;1996-01-01&#039;) TO (&#039;1997-01-01&#039;);\n-- ... 33 more partitions\n<\/pre><\/div>\n\n\n<p>Le sch\u00e9ma SH a 28 partitions SALES et 9 partitions COSTS, soit 37 au total.<\/p>\n\n\n\n<p>V\u00e9rifiez que le compte correspond \u00e0 Oracle avant le chargement.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"problem-2-number-38-columns-are-not-handled-by-pg-integer-type\">Probl\u00e8me 2 : Les colonnes NUMBER(38) ne sont pas g\u00e9r\u00e9es par le type PG_INTEGER_TYPE<\/h3>\n\n\n\n<p>Chaque colonne ID enti\u00e8re dans SH est d\u00e9clar\u00e9e comme nue <code>NOMBRE<\/code> dans Oracle, qui stocke en interne sous forme de <code>NOMBRE(38)<\/code>.<\/p>\n\n\n\n<p>L'option ora2pg <code>PG_INTEGER_TYPE 1<\/code> couvre brut <code>NOMBRE<\/code> \u00e0 <code>grand entier<\/code>.<\/p>\n\n\n\n<p>Mais <code>NOMBRE(38)<\/code> poss\u00e8de une pr\u00e9cision explicite - ora2pg le traite comme un type num\u00e9rique, pas un entier, et le mappe sur <code>num\u00e9rique(38)<\/code>.<\/p>\n\n\n\n<p>A <code>num\u00e9rique(38)<\/code> la colonne fonctionne mais est inappropri\u00e9e pour une colonne de cl\u00e9 primaire ou de cl\u00e9 \u00e9trang\u00e8re.<\/p>\n\n\n\n<p>C'est 8 fois plus grand que <code>grand entier<\/code>, plus lent \u00e0 indexer, et impossible \u00e0 utiliser avec des op\u00e9rations sp\u00e9cifiques aux entiers.<\/p>\n\n\n\n<p>La correction est <code>MODIFICATION_TYPE<\/code> \u2014 une directive sur une seule ligne dans <code>ora2pg.conf<\/code> qui force un type sp\u00e9cifique pour des colonnes sp\u00e9cifiques.<\/p>\n\n\n\n<p><strong>R\u00e8gle critique :<\/strong> <code>MODIFICATION_TYPE<\/code> est une seule ligne.<\/p>\n\n\n\n<p>Toutes les substitutions se font sur une ligne, s\u00e9par\u00e9es par des virgules.<\/p>\n\n\n\n<p>Si vous \u00e9crivez plusieurs <code>MODIFICATION_TYPE<\/code> lignes, <code>ora2pg<\/code> n'utilise que le premier et ignore silencieusement le reste.<\/p>\n\n\n\n<p>Ex\u00e9cuter <code>AFFICHER_COLONNE<\/code> le premier \u00e0 identifier tout <code>num\u00e9rique(38)<\/code> colonne :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nora2pg -t SHOW_COLUMN &gt; \/home\/fernando\/ora2pg-sh\/output\/columns-sh.txt\ngrep &#039;numeric(38)&#039; \/home\/fernando\/ora2pg-sh\/output\/columns-sh.txt\n<\/pre><\/div>\n\n\n<p>Puis ajoutez toutes les substitutions \u00e0 <code>ora2pg.conf<\/code> sur une seule ligne :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE  CUSTOMERS:CUST_ID:bigint,CUSTOMERS:CUST_CITY_ID:bigint,CUSTOMERS:CUST_STATE_PROVINCE_ID:bigint,CUSTOMERS:CUST_TOTAL_ID:bigint,CUSTOMERS:CUST_SRC_ID:bigint,CHANNELS:CHANNEL_ID:bigint,CHANNELS:CHANNEL_CLASS_ID:bigint,CHANNELS:CHANNEL_TOTAL_ID:bigint,COUNTRIES:COUNTRY_ID:bigint,COUNTRIES:COUNTRY_SUBREGION_ID:bigint,COUNTRIES:COUNTRY_REGION_ID:bigint,COUNTRIES:COUNTRY_TOTAL_ID:bigint,PRODUCTS:PROD_ID:bigint,PRODUCTS:PROD_SUBCATEGORY_ID:bigint,PRODUCTS:PROD_CATEGORY_ID:bigint,PRODUCTS:SUPPLIER_ID:bigint,PRODUCTS:PROD_TOTAL_ID:bigint,PRODUCTS:PROD_SRC_ID:bigint,PROMOTIONS:PROMO_ID:bigint,PROMOTIONS:PROMO_SUBCATEGORY_ID:bigint,PROMOTIONS:PROMO_CATEGORY_ID:bigint,PROMOTIONS:PROMO_TOTAL_ID:bigint,SALES:PROD_ID:bigint,SALES:CUST_ID:bigint,SALES:CHANNEL_ID:bigint,SALES:PROMO_ID:bigint,COSTS:PROD_ID:bigint,COSTS:PROMO_ID:bigint,COSTS:CHANNEL_ID:bigint\n<\/pre><\/div>\n\n\n<p>Avant d'ex\u00e9cuter toute exportation, assurez-vous qu'il n'y a qu'un seul \u00e9l\u00e9ment actif <code>MODIFICATION_TYPE<\/code> ligne :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\ngrep &#039;MODIFY_TYPE&#039; \/etc\/ora2pg\/ora2pg.conf | grep -v &#039;^\\s*#&#039; | wc -l\n# Must be: 1\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=\"problem-3-bitmap-indexes-keep-drop-or-replace\">Probl\u00e8me 3 : Index Bitmap \u2014 Conserver, Supprimer ou Remplacer<\/h3>\n\n\n\n<p>Oracle utilise des index bitmap sur des colonnes \u00e0 faible cardinalit\u00e9 dans SH : CHANNEL_ID (5 valeurs), PROMO_ID (503 valeurs) et TIME_ID.<\/p>\n\n\n\n<p>PostgreSQL n'a pas de type d'index bitmap.<\/p>\n\n\n\n<p>Par d\u00e9faut, ora2pg convertit les index bitmap en index GIN (<code>BITMAP_AS_GIN 1<\/code>).<\/p>\n\n\n\n<p>C'est incorrect pour ces colonnes \u2014 GIN est con\u00e7u pour la recherche plein texte et les donn\u00e9es de tableaux, pas pour les cl\u00e9s \u00e9trang\u00e8res enti\u00e8res.<\/p>\n\n\n\n<p>Ensemble <code>BITMAP_AS_GIN 0<\/code> en <code>ora2pg.conf<\/code>.<\/p>\n\n\n\n<p>Ceci indique \u00e0 ora2pg de convertir les index bitmap en index B-tree r\u00e9guliers, que vous examinez ensuite individuellement.<\/p>\n\n\n\n<p>Le tableau de d\u00e9cision pour SH :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Index bitmap Oracle<\/th><th>Cardinalit\u00e9 de colonne<\/th><th>D\u00e9cision PostgreSQL<\/th><\/tr><\/thead><tbody><tr><td>canal_de_vente_bix<\/td><td>5 cha\u00eenes<\/td><td><strong>Chute<\/strong> \u2014 Un arbre B \u00e0 5 valeurs n'apporte aucun b\u00e9n\u00e9fice; PostgreSQL utilise un scan s\u00e9quentiel<\/td><\/tr><tr><td>ventes_cust_bix<\/td><td>55 500 clients<\/td><td><strong>Garder comme arbre B<\/strong> \u2014 cardinalit\u00e9 \u00e9lev\u00e9e, utilis\u00e9e pour les requ\u00eates au niveau du client<\/td><\/tr><tr><td>produit_vente_bix<\/td><td>72\u00a0produits<\/td><td><strong>Garder comme arbre B<\/strong><\/td><\/tr><tr><td>bix_promo_ventes<\/td><td>503 promotions<\/td><td><strong>Garder comme arbre B<\/strong><\/td><\/tr><tr><td>temps_de_vente_bix<\/td><td>1 826 jours<\/td><td><strong>Remplacez par BRIN<\/strong> \u2014 TIME_ID est ordonn\u00e9 le long d'une table partitionn\u00e9e par plage<\/td><\/tr><tr><td>co\u00fbts du canal bix<\/td><td>5 cha\u00eenes<\/td><td><strong>Chute<\/strong><\/td><\/tr><tr><td>co\u00fbts_prod_bix, co\u00fbts_promo_bix<\/td><td>pareil<\/td><td><strong>Garder comme arbre B<\/strong><\/td><\/tr><tr><td>co\u00fbts_temps_bix<\/td><td>dates ordonn\u00e9es<\/td><td><strong>Remplacez par BRIN<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>BRIN (Block Range Index) est un index de plages de blocs qui stocke les valeurs min\/max par bloc de 128 pages.<\/p>\n\n\n\n<p>Pour une colonne de s\u00e9ries temporelles sur une table partitionn\u00e9e, il est beaucoup moins cher \u00e0 maintenir qu'un B-tree et tout aussi efficace pour les scans de plage.<\/p>\n\n\n\n<p>Modifier le fichier d'exportation TABLE avant de charger :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- Delete these two lines:\nCREATE INDEX sales_channel_bix ON sh.sales (channel_id);\nCREATE INDEX costs_channel_bix ON sh.costs (channel_id);\n\n-- Replace:\nCREATE INDEX sales_time_bix ON sh.sales (time_id);\n-- With:\nCREATE INDEX sales_time_bix ON sh.sales USING BRIN (time_id);\n\n-- Replace:\nCREATE INDEX costs_time_bix ON sh.costs (time_id);\n-- With:\nCREATE INDEX costs_time_bix ON sh.costs USING BRIN (time_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=\"problem-4-materialized-view-refresh-is-no-longer-automatic\">Probl\u00e8me 4 : Le rafra\u00eechissement des vues mat\u00e9rialis\u00e9es n'est plus automatique<\/h3>\n\n\n\n<p>Utilisations de CAL_MONTH_SALES_MV et FWEEK_PSCAT_SALES_MV par Oracle <code>REFRESH COMPLET SUR DEMANDE<\/code>.<\/p>\n\n\n\n<p>Un rafra\u00eechissement complet signifie que la vue est reconstruite \u00e0 partir de z\u00e9ro \u00e0 chaque rafra\u00eechissement.<\/p>\n\n\n\n<p>PostgreSQL <code>ACTUALISER LA VUE MAT\u00c9RIALIS\u00c9E<\/code> fait exactement cela \u2014 aucun probl\u00e8me de traduction.<\/p>\n\n\n\n<p>Le probl\u00e8me est la planification.<\/p>\n\n\n\n<p>Oracle's <code>DBMS_MVIEW.REFRESH<\/code> et <code>DBMS_SCHEDULER<\/code> g\u00e9rer le rafra\u00eechissement automatique.<\/p>\n\n\n\n<p>PostgreSQL n'a pas d'\u00e9quivalent int\u00e9gr\u00e9.<\/p>\n\n\n\n<p>Apr\u00e8s la migration, les vues mat\u00e9rialis\u00e9es sont des instantan\u00e9s statiques jusqu'\u00e0 ce que quelqu'un ex\u00e9cute explicitement :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nREFRESH MATERIALIZED VIEW sh.cal_month_sales_mv;\nREFRESH MATERIALIZED VIEW sh.fweek_pscat_sales_mv;\n<\/pre><\/div>\n\n\n<p>Pour r\u00e9pliquer l'actualisation planifi\u00e9e, installez pg_cron (n\u00e9cessite les superutilisateurs) et cr\u00e9ez un travail apr\u00e8s la migration.<\/p>\n\n\n\n<p>Il y a aussi une contrainte d'ordre de chargement.<\/p>\n\n\n\n<p>PostgreSQL ex\u00e9cute la requ\u00eate MV SELECT imm\u00e9diatement lorsque vous lancez <code>CR\u00c9ER UNE VUE MAT\u00c9RIALIS\u00c9E COMME SELECT<\/code>.<\/p>\n\n\n\n<p>Si vous chargez le DDL de la MV avant les donn\u00e9es, la requ\u00eate s'ex\u00e9cute sur des tables vides et la MV est cr\u00e9\u00e9e vide.<\/p>\n\n\n\n<p>Chargez les donn\u00e9es d'abord, puis les MV.<\/p>\n\n\n\n<p>Il y a aussi un probl\u00e8me de `search_path`.<\/p>\n\n\n\n<p>ora2pg n'ajoute pas <code>SET search_path<\/code> au fichier d'exportation MVIEW.<\/p>\n\n\n\n<p>Si vous chargez <code>SH_mviews.sql<\/code> directement, psql s'ex\u00e9cute en tant qu'utilisateur du syst\u00e8me d'exploitation postgres, dont le search_path par d\u00e9faut est <code>public<\/code> \u2014 les MV sont cr\u00e9\u00e9s dans <code>public<\/code>, pas <code>le<\/h2><\/code>.<\/p>\n\n\n\n<p>La solution est le truc du tuyau :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n(echo &quot;SET search_path TO sh;&quot;; cat \/home\/fernando\/ora2pg-sh\/output\/SH_mviews.sql) | \\\n  sudo -u postgres psql -d shdb\n<\/pre><\/div>\n\n\n<p>Le <code>\u00e9cho<\/code> pr\u00e9pole <code>SET search_path<\/code> directive alors chaque <code>CR\u00c9ER UNE VUE MAT\u00c9RIALIS\u00c9E<\/code> dans le fichier arrive dans le <code>le<\/h2><\/code> sch\u00e9ma.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"problem-5-dimension-objects-are-silently-dropped\">Probl\u00e8me 5 : Les objets dimensionnels sont silencieusement abandonn\u00e9s<\/h3>\n\n\n\n<p>Oracle prend en charge <code>CR\u00c9ER DIMENSION<\/code> syntaxe, qui d\u00e9finit les hi\u00e9rarchies de cumul pour le moteur de r\u00e9\u00e9criture de requ\u00eates et les op\u00e9rations OLAP d'Oracle.<\/p>\n\n\n\n<p>PostgreSQL n'a pas d'\u00e9quivalent.<\/p>\n\n\n\n<p>ora2pg ignore silencieusement les objets DIMENSION.<\/p>\n\n\n\n<p>Il n'y a pas d'avertissement, aucune erreur, aucun DDL fragmentaire.<\/p>\n\n\n\n<p>Si vous ne les contr\u00f4lez pas manuellement, vous ne saurez pas qu'ils existaient jusqu'\u00e0 ce que quelqu'un vous demande pourquoi un rapport c\u00f4t\u00e9 Oracle qui utilisait la r\u00e9\u00e9criture de requ\u00eate ne fonctionne plus comme pr\u00e9vu.<\/p>\n\n\n\n<p>Dans le sch\u00e9ma SH, CUSTOMERS_DIM, PRODUCTS_DIM, TIMES_DIM et CHANNELS_DIM sont tous ignor\u00e9s.<\/p>\n\n\n\n<p>Il n'y a aucun impact fonctionnel sur les requ\u00eates SQL standard \u2014 les dimensions sont des m\u00e9tadonn\u00e9es de l'optimiseur, pas des donn\u00e9es.<\/p>\n\n\n\n<p>La bonne d\u00e9marche est de les documenter comme abandonn\u00e9s dans le registre des risques de migration.<\/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\"><strong>Environnement<\/strong><\/h3>\n\n\n\n<p>Deux VM sur le m\u00eame r\u00e9seau.<br><code>srv1<\/code> (192.168.0.180) ex\u00e9cute Oracle 19c avec le sch\u00e9ma SH dans PDB <code>pdb1<\/code>.<br><code>srv2<\/code> (192.168.0.181) ex\u00e9cute Ubuntu avec PostgreSQL 18 et ora2pg install\u00e9s.<br>Toutes les commandes ci-dessous s'ex\u00e9cutent 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-populate-oracle-sh-data-using-sq-lcl\">\u00c9tape 1 \u2014 Remplir les donn\u00e9es Oracle SH \u00e0 l'aide de SQLcl<\/h3>\n\n\n\n<p>Le script d'installation SH appelle <code>sh_populate.sql<\/code> en interne, qui utilise le SQLcl de <code>CHARGER<\/code> commande pour lire les fichiers CSV.<\/p>\n\n\n\n<p>SQL*Plus ne prend pas en charge <code>CHARGER<\/code> \u2014 il saute silencieusement chaque <code>CHARGER<\/code> appel avec <code>SP2-0734 : commande inconnue<\/code>.<\/p>\n\n\n\n<p>Le sch\u00e9ma est cr\u00e9\u00e9 mais chaque grande table reste \u00e0 0 ligne.<\/p>\n\n\n\n<p>T\u00e9l\u00e9chargez d'abord les fichiers CSV :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv1 (Oracle)\ncd \/home\/oracle\/sh\n\nfor f in costs customers promotions sales supplementary_demographics times; do\n  curl -L -o ${f}.csv \\\n    &quot;https:\/\/raw.githubusercontent.com\/oracle-samples\/db-sample-schemas\/main\/sales_history\/${f}.csv&quot;\ndone\n\nls -lh *.csv\n# Expected: 6 files; sales.csv ~74 MB, customers.csv ~13 MB\n<\/pre><\/div>\n\n\n<p>Ex\u00e9cutez l'installation avec SQLcl, pas avec SQL*Plus :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv1 (Oracle)\ncd \/home\/oracle\/sh\nsql \/ as sysdba\n<\/pre><\/div>\n\n\n<p>Dans SQLcl :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nALTER SESSION SET CONTAINER = pdb1;\n@sh_install.sql\n-- Prompts:\n-- Enter a password for the user SH:             sh\n-- Enter a tablespace for SH &#x5B;USERS]:            (press Enter)\n-- Do you want to overwrite the schema? &#x5B;YES|no]: YES\n<\/pre><\/div>\n\n\n<p>V\u00e9rifier les d\u00e9comptes de lignes :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv1 (Oracle)\nsqlplus sh\/sh@\/\/localhost:1521\/pdb1 &lt;&lt;&#039;EOF&#039;\nSELECT &#039;SALES&#039;,      COUNT(*) FROM sh.sales    UNION ALL\nSELECT &#039;COSTS&#039;,      COUNT(*) FROM sh.costs    UNION ALL\nSELECT &#039;CUSTOMERS&#039;,  COUNT(*) FROM sh.customers;\nEXIT;\nEOF\n# Expected: SALES 918843, COSTS 82112, CUSTOMERS 55500\n# If any large table is 0: SQLcl was not used \u2014 re-run using sql, not sqlplus\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-2-create-the-postgre-sql-database-and-user\">\u00c9tape 2 \u2014 Cr\u00e9er la base de donn\u00e9es et l'utilisateur PostgreSQL<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -c &quot;CREATE USER sh WITH PASSWORD &#039;sh&#039;;&quot;\nsudo -u postgres psql -c &quot;CREATE DATABASE shdb OWNER sh;&quot;\n\nsudo -u postgres psql -c &quot;\\l shdb&quot;\n# Expected: one row, owner sh, encoding UTF8\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-3-create-the-output-directory\">\u00c9tape 3 \u2014 Cr\u00e9er le r\u00e9pertoire de sortie<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nmkdir -p \/home\/fernando\/ora2pg-sh\/output\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-4-configure-ora-2-pg-conf\">\u00c9tape 4 \u2014 Configurer ora2pg.conf<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo cp \/etc\/ora2pg\/ora2pg.conf \/etc\/ora2pg\/ora2pg.conf.$(date +%Y%m%d)\nsudo vi \/etc\/ora2pg\/ora2pg.conf\n<\/pre><\/div>\n\n\n<p>D\u00e9finir ces valeurs :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nORACLE_DSN      dbi:Oracle:host=192.168.0.180;sid=pdb1;port=1521\nORACLE_USER     sh\nORACLE_PWD      sh\nSCHEMA          SH\nOUTPUT_DIR      \/home\/fernando\/ora2pg-sh\/output\nCREATE_SCHEMA   1\nPG_INTEGER_TYPE 1\nPG_NUMERIC_TYPE 1\nBITMAP_AS_GIN   0\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-5-run-show-column-to-identify-number-38-columns\">\u00c9tape 5 \u2014 Ex\u00e9cuter SHOW_COLUMN pour identifier les colonnes NUMBER(38)<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t SHOW_COLUMN &gt; \/home\/fernando\/ora2pg-sh\/output\/columns-sh.txt\ngrep &#039;numeric(38)&#039; \/home\/fernando\/ora2pg-sh\/output\/columns-sh.txt\n# Every column listed here needs a MODIFY_TYPE override to bigint\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-6-set-modify-type-overrides\">\u00c9tape 6 \u2014 D\u00e9finir les remplacements de `MODIFY_TYPE`<\/h3>\n\n\n\n<p>Ajouter le <code>MODIFICATION_TYPE<\/code> ligne vers <code>ora2pg.conf<\/code> (doit tenir sur une seule ligne \u2014 voir Probl\u00e8me 2 ci-dessus).<\/p>\n\n\n\n<p>V\u00e9rifiez qu'une seule ligne active existe\u00a0:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\ngrep &#039;MODIFY_TYPE&#039; \/etc\/ora2pg\/ora2pg.conf | grep -v &#039;^\\s*#&#039; | wc -l\n# Must be: 1\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-7-export-table\">\u00c9tape 7 \u2014 Exporter TABLEAU<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t TABLE -o SH_tables.sql 2&gt;&1 | tee \/home\/fernando\/ora2pg-sh\/output\/table-export-sh.log\n\nls -lh \/home\/fernando\/ora2pg-sh\/output\/SH_tables.sql\n# Expected: file present, non-zero size\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-8-fix-the-table-export-bitmap-indexes\">\u00c9tape 8 \u2013 Corriger l'exportation TABLE (index bitmap)<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\ncp \/home\/fernando\/ora2pg-sh\/output\/SH_tables.sql \\\n   \/home\/fernando\/ora2pg-sh\/output\/SH_tables_fixed.sql\n\nvi \/home\/fernando\/ora2pg-sh\/output\/SH_tables_fixed.sql\n<\/pre><\/div>\n\n\n<p>Appliquez les d\u00e9cisions d'indexation du probl\u00e8me 3 :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Supprimer <code>canal_de_vente_bix<\/code> et <code>co\u00fbts du canal bix<\/code><\/li>\n\n\n\n<li>Remplacer <code>temps_de_vente_bix<\/code> et <code>co\u00fbts_temps_bix<\/code> avec <code>EN UTILISANT BRIN<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-9-export-partition\">\u00c9tape 9 \u2014 Exporter PARTITION<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t PARTITION -o SH_partitions.sql 2&gt;&1 | tee \/home\/fernando\/ora2pg-sh\/output\/partition-export-sh.log\n\ngrep -c &#039;PARTITION OF&#039; \/home\/fernando\/ora2pg-sh\/output\/SH_partitions.sql\n# Expected: 37 (28 SALES + 9 COSTS)\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-10-export-mview\">\u00c9tape 10 \u2014 Exporter MVIEW<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t MVIEW -o SH_mviews.sql 2&gt;&1 | tee \/home\/fernando\/ora2pg-sh\/output\/mview-export-sh.log\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-11-export-view\">\u00c9tape 11 \u2014 Exporter la VUE<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t VIEW -o SH_view.sql 2&gt;&1 | tee \/home\/fernando\/ora2pg-sh\/output\/view-export-sh.log\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-12-export-data-copy\">\u00c9tape 12 \u2014 Exporter les donn\u00e9es (COPY)<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t COPY -o SH_data.sql 2&gt;&1 | tee \/home\/fernando\/ora2pg-sh\/output\/copy-export-sh.log\n# Expect 10-20 minutes -- SALES is ~918,000 rows\n# Monitor in a second terminal: watch -n 10 &quot;wc -l \/home\/fernando\/ora2pg-sh\/output\/SH_data.sql&quot;\n\ntail -5 \/home\/fernando\/ora2pg-sh\/output\/SH_data.sql\n# Expected: ends with COMMIT; -- if truncated, the export was interrupted, re-run\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-13-load-in-the-correct-order\">\u00c9tape 13 \u2014 Charger dans le bon ordre<\/h3>\n\n\n\n<p>V\u00e9rifiez que la cible est vide :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb -c &quot;\\dn&quot;\n# Expected: no rows (sh schema does not exist yet)\n<\/pre><\/div>\n\n\n<p><strong>Chargement 1 \u2014 DDL de table (tables parentes, index, cl\u00e9s primaires) :<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb &lt; \/home\/fernando\/ora2pg-sh\/output\/SH_tables_fixed.sql\n# Expected: CREATE TABLE, CREATE INDEX, ALTER TABLE -- no ERROR lines\n\nsudo -u postgres psql -d shdb -c &quot;\\dt sh.*&quot;\n# Expected: 9 tables listed\n<\/pre><\/div>\n\n\n<p><strong>Chargement 2 \u2014 Partitionnement des tables enfants (doit pr\u00e9c\u00e9der les donn\u00e9es) :<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb &lt; \/home\/fernando\/ora2pg-sh\/output\/SH_partitions.sql\n\nsudo -u postgres psql -d shdb -c &quot;\nSELECT COUNT(*) FROM pg_tables\nWHERE schemaname = &#039;sh&#039;\n  AND (tablename LIKE &#039;sales_%&#039; OR tablename LIKE &#039;costs_%&#039;);&quot;\n# Expected: 37\n<\/pre><\/div>\n\n\n<p><strong>Chargement 3 \u2014 Donn\u00e9es :<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb &lt; \/home\/fernando\/ora2pg-sh\/output\/SH_data.sql\n# Single transaction -- if any COPY fails, everything rolls back\n# Expect several minutes for SALES\n<\/pre><\/div>\n\n\n<p><strong>Chargement 4 \u2014 Vues mat\u00e9rialis\u00e9es (apr\u00e8s les donn\u00e9es, avec astuce de pipe search_path) :<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\n(echo &quot;SET search_path TO sh;&quot;; cat \/home\/fernando\/ora2pg-sh\/output\/SH_mviews.sql) | \\\n  sudo -u postgres psql -d shdb\n\nsudo -u postgres psql -d shdb -c &quot;\\dm sh.*&quot;\n# Expected: cal_month_sales_mv, fweek_pscat_sales_mv, both ispopulated = t\n<\/pre><\/div>\n\n\n<p><strong>Charger 5 \u2014 Voir :<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb &lt; \/home\/fernando\/ora2pg-sh\/output\/SH_view.sql\n\nsudo -u postgres psql -d shdb -c &quot;\\dv sh.*&quot;\n# Expected: sh | profits | view\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"step-14-re-apply-fk-constraints\">\u00c9tape 14 \u2014 R\u00e9appliquer les contraintes FK<\/h3>\n\n\n\n<p>Le fichier de donn\u00e9es COPY d'ora2pg supprime toutes les contraintes FK avant de charger les donn\u00e9es et ne les r\u00e9ajoute pas.<\/p>\n\n\n\n<p>C'est un comportement confirm\u00e9 de ora2pg (probl\u00e8me #1960).<\/p>\n\n\n\n<p>V\u00e9rifier que les cl\u00e9s \u00e9trang\u00e8res sont manquantes apr\u00e8s le chargement :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb -c &quot;\nSELECT COUNT(*) FROM information_schema.table_constraints\nWHERE constraint_schema = &#039;sh&#039;\n  AND constraint_type   = &#039;FOREIGN KEY&#039;;&quot;\n# Expected: 0\n<\/pre><\/div>\n\n\n<p>R\u00e9appliquer les 10 FK en une seule commande :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\ngrep &#039;^ALTER TABLE.*FOREIGN KEY&#039; \/home\/fernando\/ora2pg-sh\/output\/SH_tables.sql | \\\n  sudo -u postgres psql -d shdb\n# Expected: 10 ALTER TABLE lines, no ERROR lines\n<\/pre><\/div>\n\n\n<p>V\u00e9rifier :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb -c &quot;\nSELECT COUNT(*) FROM information_schema.table_constraints\nWHERE constraint_schema = &#039;sh&#039;\n  AND constraint_type   = &#039;FOREIGN KEY&#039;;&quot;\n# Expected: 10\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-the-final-test-report-looks-like\">\u00c0 quoi ressemble le rapport final de TEST<\/h2>\n\n\n\n<p>Activer PG_DSN dans <code>ora2pg.conf<\/code>, puis ex\u00e9cuter :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nora2pg -t TEST 2&gt;&1 | tee \/home\/fernando\/ora2pg-sh\/output\/SH_test.txt\n<\/pre><\/div>\n\n\n<p><strong>Diffs attendus (pas d'erreurs de migration) :<\/strong><\/p>\n\n\n\n<p><strong>Incompatibilit\u00e9 de compte de cl\u00e9 primaire sur CHANNELS, COUNTRIES, PRODUCTS (Oracle : 0, PostgreSQL : 1) :<\/strong><\/p>\n\n\n\n<p>Le sch\u00e9ma SH d'Oracle d\u00e9finit les cl\u00e9s primaires avec <code>D\u00e9sactiver NOVALIDATE<\/code> \u2014 les contraintes sont d\u00e9finies mais pas appliqu\u00e9es.<\/p>\n\n\n\n<p>Le catalogue d'Oracle les signale comme 0 cl\u00e9s primaires actives.<\/p>\n\n\n\n<p>PostgreSQL les applique correctement.<\/p>\n\n\n\n<p>Il s'agit d'un artefact de la conception du sch\u00e9ma d'exemple d'Oracle \u2014 aucune action n'est n\u00e9cessaire.<\/p>\n\n\n\n<p><strong>Incompatibilit\u00e9 de comptage NOT NULL sur COSTS et SALES (Oracle : 6 et 7, PostgreSQL : 0)<\/strong><\/p>\n\n\n\n<p>Les deux sont des tables partitionn\u00e9es.<\/p>\n\n\n\n<p>PostgreSQL applique les contraintes NOT NULL aux tables enfants de partition, et non pas \u00e0 la table parente.<\/p>\n\n\n\n<p>Le test ora2pg lit le parent et obtient 0.<\/p>\n\n\n\n<p>Les contraintes existent et sont appliqu\u00e9es \u2014 v\u00e9rifiez avec <code>\\d sh.sales_1995<\/code> si n\u00e9cessaire.<\/p>\n\n\n\n<p><strong>Nombre MVIEW (2 contre 0) :<\/strong><\/p>\n\n\n\n<p>Le TEST d'ora2pg ne d\u00e9tecte pas de mani\u00e8re fiable les vues mat\u00e9rialis\u00e9es dans le sch\u00e9ma cible.<\/p>\n\n\n\n<p>V\u00e9rifier directement :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb -c &quot;\\dm sh.*&quot;\n# Expected: both MVs present with ispopulated = t\n# If they appear: ignore the TEST counter\n<\/pre><\/div>\n\n\n<p><strong>Nombre de lignes \u2014 toutes les tables doivent correspondre :<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CHA\u00ceNES 5 \/ 5 MATCH\nPAYS 23 \/ 23 MATCH\nCLIENTS 55500 \/ 55500 MATCH\nPRODUITS 72 \/ 72 MATCH\nPROMOTIONS 503 \/ 503 MATCH\nTEMPS 1826 \/ 1826 MATCH\nCO\u00dbTS 82112 \/ 82112 MATCH\nVENTES 918843 \/918843 MATCH\nSUPPLEMENTARY_DEMOGRAPHICS 4500 \/ 4500 MATCH<\/code><\/pre>\n\n\n\n<p>Si SALES pr\u00e9sente une anomalie, v\u00e9rifiez le nombre de lignes de partitions individuelles :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# On srv2 (PostgreSQL)\nsudo -u postgres psql -d shdb -c &quot;\nSELECT tableoid::regclass AS partition, COUNT(*)\nFROM   sh.sales\nGROUP  BY tableoid::regclass\nORDER  BY tableoid::regclass;&quot;\n# Look for partitions with 0 rows -- those indicate a load failure for that time range\n<\/pre><\/div>\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>La plupart des sch\u00e9mas d'entrep\u00f4t de donn\u00e9es Oracle ressemblent \u00e0 SH.<\/p>\n\n\n\n<p>Tables de faits partitionn\u00e9es, index bitmap sur les colonnes FK, vues mat\u00e9rialis\u00e9es reconstruites selon un calendrier, et m\u00e9tadonn\u00e9es de dimension que personne n'a consult\u00e9es depuis des ann\u00e9es.<\/p>\n\n\n\n<p>Aucun de ceux-ci n'est un blocage - ils ont tous des \u00e9quivalents PostgreSQL clairs.<\/p>\n\n\n\n<p>Mais la migration n'est pas automatique.<\/p>\n\n\n\n<p><code>ora2pg<\/code> vous emm\u00e8ne \u00e0 80% du chemin.<\/p>\n\n\n\n<p>Les 20% restants constituent un ensemble de d\u00e9cisions sp\u00e9cifiques : ex\u00e9cuter TABLE et PARTITION comme des exportations distinctes et les charger dans le bon ordre ; remplacer chaque colonne NUMBER(38) par bigint en utilisant MODIFY_TYPE ; d\u00e9finir BITMAP_AS_GIN \u00e0 0 et examiner chaque index individuellement ; pr\u00e9fixer SET search_path lors du chargement des vues mat\u00e9rialis\u00e9es ; et r\u00e9appliquer les contraintes FK apr\u00e8s le chargement des donn\u00e9es car ora2pg les supprime et ne les remet jamais.<\/p>\n\n\n\n<p>Les objets DIMENSION sont les seuls \u00e0 ne pas avoir d'\u00e9quivalent PostgreSQL.<\/p>\n\n\n\n<p>Ce sont des m\u00e9tadonn\u00e9es d'optimiseur \u2014 leur suppression n'a aucun effet sur la correction de la requ\u00eate.<\/p>\n\n\n\n<p>Documentez-les comme abandonn\u00e9s et passez \u00e0 autre chose.<\/p>\n\n\n\n<p>Dans chaque migration de classes SH que j'ex\u00e9cute, les sept m\u00eames probl\u00e8mes se pr\u00e9sentent.<\/p>\n\n\n\n<p>Les conna\u00eetre avant la premi\u00e8re exportation est ce qui s\u00e9pare une transition nette d'une journ\u00e9e de d\u00e9bogage.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"next-steps\">Prochaines \u00e9tapes<\/h2>\n\n\n\n<p>Si vous avez un sch\u00e9ma d'entrep\u00f4t de donn\u00e9es Oracle et que vous souhaitez comprendre la port\u00e9e de la migration avant de vous engager dans une \u00e9valuation compl\u00e8te, commencez par le <a href=\"https:\/\/rootfan.com\/fr\/services\/\">audit de migration gratuit sur rootfan.com\/services\/<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>The Oracle SH (Sales History) schema is the standard Oracle example of a star schema data warehouse. It has a central SALES fact table, six dimension tables, two materialized views, range-partitioned fact tables, and bitmap indexes on low-cardinality columns. All of those features exist in every real Oracle data warehouse. And all of them require &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/rootfan.com\/fr\/ora2pg-migration-oracle-to-postgres-step-by-step\/\" class=\"more-link\">Continuer la lecture<span class=\"screen-reader-text\"> de &laquo;&nbsp;Oracle to Postgres Migration Step by Step: The SH Schema (Partitions, Materialized Views, Bitmap Indexes)&nbsp;&raquo;<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":6763,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_focus_keyword":"oracle to postgres migration step by step","rank_math_title":"","rank_math_description":"Complete ora2pg migration of Oracle SH schema to PostgreSQL. Covers partitioned tables, bitmap indexes, materialized views, and NUMBER(38) type overrides \u2014 every command in order.","rank_math_robots":"","rank_math_og_title":"","rank_math_og_description":"","_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":"","jetpack_post_was_ever_published":false},"categories":[146],"tags":[153,143,141,154,137,147,152,151],"class_list":["post-6744","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oracle-to-postgresql","tag-bitmap-index","tag-data-migration","tag-data-types","tag-materialized-views","tag-migration","tag-ora2pg","tag-partitioning","tag-sh-schema"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rootfan.com\/wp-content\/uploads\/pexels-photo-2394899.jpeg?fit=1786%2C1300&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6744","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=6744"}],"version-history":[{"count":11,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6744\/revisions"}],"predecessor-version":[{"id":6805,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6744\/revisions\/6805"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/media\/6763"}],"wp:attachment":[{"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/media?parent=6744"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/categories?post=6744"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/tags?post=6744"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}