{"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-20T14:29:41","modified_gmt":"2026-04-20T12:29:41","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\">What SH Contains<\/a><ul><li><a href=\"#problem-1-partitioned-tables-require-two-separate-exports\">Problem 1: Partitioned Tables Require Two Separate Exports<\/a><\/li><li><a href=\"#problem-2-number-38-columns-are-not-handled-by-pg-integer-type\">Problem 2: NUMBER(38) Columns Are Not Handled by PG_INTEGER_TYPE<\/a><\/li><li><a href=\"#problem-3-bitmap-indexes-keep-drop-or-replace\">Problem 3: Bitmap Indexes \u2014 Keep, Drop, or Replace<\/a><\/li><li><a href=\"#problem-4-materialized-view-refresh-is-no-longer-automatic\">Problem 4: Materialized View Refresh Is No Longer Automatic<\/a><\/li><li><a href=\"#problem-5-dimension-objects-are-silently-dropped\">Problem 5: DIMENSION Objects Are Silently Dropped<\/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=\"#step-1-populate-oracle-sh-data-using-sq-lcl\">Step 1 \u2014 Populate Oracle SH Data Using SQLcl<\/a><\/li><li><a href=\"#step-2-create-the-postgre-sql-database-and-user\">Step 2 \u2014 Create the PostgreSQL Database and User<\/a><\/li><li><a href=\"#step-3-create-the-output-directory\">Step 3 \u2014 Create the Output Directory<\/a><\/li><li><a href=\"#step-4-configure-ora-2-pg-conf\">Step 4 \u2014 Configure ora2pg.conf<\/a><\/li><li><a href=\"#step-5-run-show-column-to-identify-number-38-columns\">Step 5 \u2014 Run SHOW_COLUMN to Identify NUMBER(38) Columns<\/a><\/li><li><a href=\"#step-6-set-modify-type-overrides\">Step 6 \u2014 Set MODIFY_TYPE Overrides<\/a><\/li><li><a href=\"#step-7-export-table\">Step 7 \u2014 Export TABLE<\/a><\/li><li><a href=\"#step-8-fix-the-table-export-bitmap-indexes\">Step 8 \u2014 Fix the TABLE Export (Bitmap Indexes)<\/a><\/li><li><a href=\"#step-9-export-partition\">Step 9 \u2014 Export PARTITION<\/a><\/li><li><a href=\"#step-10-export-mview\">Step 10 \u2014 Export MVIEW<\/a><\/li><li><a href=\"#step-11-export-view\">Step 11 \u2014 Export VIEW<\/a><\/li><li><a href=\"#step-12-export-data-copy\">Step 12 \u2014 Export Data (COPY)<\/a><\/li><li><a href=\"#step-13-load-in-the-correct-order\">Step 13 \u2014 Load in the Correct Order<\/a><\/li><li><a href=\"#step-14-re-apply-fk-constraints\">Step 14 \u2014 Re-Apply FK Constraints<\/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\">Next Steps<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-sh-contains\">What SH Contains<\/h2>\n\n\n\n<p>SH models a retail sales data warehouse.<\/p>\n\n\n\n<p><strong>Dimension tables:<\/strong> CHANNELS, COUNTRIES, CUSTOMERS, PRODUCTS, PROMOTIONS, TIMES<\/p>\n\n\n\n<p><strong>Fact tables:<\/strong> SALES, COSTS \u2014 both range-partitioned by TIME_ID<\/p>\n\n\n\n<p><strong>Materialized views:<\/strong> CAL_MONTH_SALES_MV, FWEEK_PSCAT_SALES_MV<\/p>\n\n\n\n<p><strong>Data volume:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Tableau<\/th><th>Rows<\/th><\/tr><\/thead><tbody><tr><td>CHANNELS<\/td><td>5<\/td><\/tr><tr><td>COUNTRIES<\/td><td>23<\/td><\/tr><tr><td>PROMOTIONS<\/td><td>503<\/td><\/tr><tr><td>PRODUCTS<\/td><td>72<\/td><\/tr><tr><td>TIMES<\/td><td>1,826<\/td><\/tr><tr><td>CUSTOMERS<\/td><td>55,500<\/td><\/tr><tr><td>COSTS<\/td><td>82,112<\/td><\/tr><tr><td>SALES<\/td><td>918,843<\/td><\/tr><tr><td>SUPPLEMENTARY_DEMOGRAPHICS<\/td><td>4,500<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>The SALES table at nearly one million rows means the COPY export and load take 15\u201325 minutes.<\/p>\n\n\n\n<p>Plan for that before starting.<\/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\">Problem 1: Partitioned Tables Require Two Separate Exports<\/h3>\n\n\n\n<p>SALES and COSTS are range-partitioned by TIME_ID in Oracle.<\/p>\n\n\n\n<p>PostgreSQL supports declarative range partitioning with the same semantics.<\/p>\n\n\n\n<p>The problem is that <code>ora2pg<\/code> splits this into two export types.<\/p>\n\n\n\n<p>Le <code>TABLE<\/code> export generates the parent table DDL \u2014 the <code>PARTITION BY RANGE (time_id)<\/code> declaration \u2014 but no child partition tables.<\/p>\n\n\n\n<p>If you load this and try to insert rows, PostgreSQL returns:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ERROR:  no partition of relation \"sales\" found for row<\/code><\/pre>\n\n\n\n<p>The partition child tables come from a separate <code>PARTITION<\/code> export.<\/p>\n\n\n\n<p>You need both files, and you need to load the parent table DDL before the partition child tables.<\/p>\n\n\n\n<p><strong>What this looks like in practice:<\/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>The SH schema has 28 SALES partitions and 9 COSTS partitions \u2014 37 total.<\/p>\n\n\n\n<p>Verify the count matches Oracle before loading.<\/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\">Problem 2: NUMBER(38) Columns Are Not Handled by PG_INTEGER_TYPE<\/h3>\n\n\n\n<p>Every integer ID column in SH is declared as bare <code>NOMBRE<\/code> in Oracle, which stores internally as <code>NOMBRE(38)<\/code>.<\/p>\n\n\n\n<p>The ora2pg option <code>PG_INTEGER_TYPE 1<\/code> couvre brut <code>NOMBRE<\/code> (no precision) to <code>grand entier<\/code>.<\/p>\n\n\n\n<p>But <code>NOMBRE(38)<\/code> has an explicit precision \u2014 ora2pg treats it as a numeric type, not an integer, and maps it to <code>num\u00e9rique(38)<\/code>.<\/p>\n\n\n\n<p>A <code>num\u00e9rique(38)<\/code> column works but is wrong for a primary key or foreign key column.<\/p>\n\n\n\n<p>It is 8\u00d7 larger than <code>grand entier<\/code>, slower to index, and impossible to use with integer-specific operations.<\/p>\n\n\n\n<p>The fix is <code>MODIFICATION_TYPE<\/code> \u2014 a single-line directive in <code>ora2pg.conf<\/code> that forces a specific type for specific columns.<\/p>\n\n\n\n<p><strong>Critical rule:<\/strong> <code>MODIFICATION_TYPE<\/code> is a single line.<\/p>\n\n\n\n<p>All overrides go on one line, comma-separated.<\/p>\n\n\n\n<p>If you write multiple <code>MODIFICATION_TYPE<\/code> lines, <code>ora2pg<\/code> uses only the first and silently ignores the rest.<\/p>\n\n\n\n<p>Ex\u00e9cuter <code>SHOW_COLUMN<\/code> first to identify every <code>num\u00e9rique(38)<\/code> column:<\/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>Then add all overrides to <code>ora2pg.conf<\/code> on one line:<\/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>Before running any export, confirm there is exactly one active <code>MODIFICATION_TYPE<\/code> line:<\/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\">Problem 3: Bitmap Indexes \u2014 Keep, Drop, or Replace<\/h3>\n\n\n\n<p>Oracle uses bitmap indexes on low-cardinality columns in SH: CHANNEL_ID (5 values), PROMO_ID (503 values), and TIME_ID.<\/p>\n\n\n\n<p>PostgreSQL has no bitmap index type.<\/p>\n\n\n\n<p>By default, ora2pg converts bitmap indexes to GIN indexes (<code>BITMAP_AS_GIN 1<\/code>).<\/p>\n\n\n\n<p>That is wrong for these columns \u2014 GIN is designed for full-text search and array data, not integer foreign keys.<\/p>\n\n\n\n<p>Ensemble <code>BITMAP_AS_GIN 0<\/code> en <code>ora2pg.conf<\/code>.<\/p>\n\n\n\n<p>This tells ora2pg to convert bitmap indexes to regular B-tree indexes, which you then review individually.<\/p>\n\n\n\n<p>The decision table for SH:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Oracle bitmap index<\/th><th>Column cardinality<\/th><th>PostgreSQL decision<\/th><\/tr><\/thead><tbody><tr><td>sales_channel_bix<\/td><td>5 channels<\/td><td><strong>Drop<\/strong> \u2014 B-tree on 5 values brings no benefit; PostgreSQL uses a sequential scan<\/td><\/tr><tr><td>sales_cust_bix<\/td><td>55,500 customers<\/td><td><strong>Keep as B-tree<\/strong> \u2014 high cardinality, used for customer-level queries<\/td><\/tr><tr><td>sales_prod_bix<\/td><td>72 products<\/td><td><strong>Keep as B-tree<\/strong><\/td><\/tr><tr><td>sales_promo_bix<\/td><td>503 promotions<\/td><td><strong>Keep as B-tree<\/strong><\/td><\/tr><tr><td>sales_time_bix<\/td><td>1,826 days<\/td><td><strong>Replace with BRIN<\/strong> \u2014 TIME_ID is monotonically ordered on a range-partitioned table<\/td><\/tr><tr><td>costs_channel_bix<\/td><td>5 channels<\/td><td><strong>Drop<\/strong><\/td><\/tr><tr><td>costs_prod_bix, costs_promo_bix<\/td><td>same<\/td><td><strong>Keep as B-tree<\/strong><\/td><\/tr><tr><td>costs_time_bix<\/td><td>ordered dates<\/td><td><strong>Replace with BRIN<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>BRIN (Block Range Index) is a block-range index that stores min\/max values per 128-page block.<\/p>\n\n\n\n<p>For a time series column on a partitioned table, it is far cheaper to maintain than a B-tree and equally effective for range scans.<\/p>\n\n\n\n<p>Edit the TABLE export file before loading:<\/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\">Problem 4: Materialized View Refresh Is No Longer Automatic<\/h3>\n\n\n\n<p>Oracle's CAL_MONTH_SALES_MV and FWEEK_PSCAT_SALES_MV use <code>REFRESH COMPLETE ON DEMAND<\/code>.<\/p>\n\n\n\n<p>COMPLETE refresh means the view is rebuilt from scratch on each refresh.<\/p>\n\n\n\n<p>PostgreSQL <code>REFRESH MATERIALIZED VIEW<\/code> does exactly this \u2014 no translation problem.<\/p>\n\n\n\n<p>The problem is scheduling.<\/p>\n\n\n\n<p>Oracle's <code>DBMS_MVIEW.REFRESH<\/code> et <code>DBMS_SCHEDULER<\/code> handle automatic refresh.<\/p>\n\n\n\n<p>PostgreSQL has no built-in equivalent.<\/p>\n\n\n\n<p>After migration, the MVs are static snapshots until someone explicitly runs:<\/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>To replicate scheduled refresh, install pg_cron (requires superuser) and create a job after the migration.<\/p>\n\n\n\n<p>There is also a load-order constraint.<\/p>\n\n\n\n<p>PostgreSQL executes the MV SELECT query immediately when you run <code>CREATE MATERIALIZED VIEW AS SELECT<\/code>.<\/p>\n\n\n\n<p>If you load the MV DDL before the data, the query runs against empty tables and the MV is created empty.<\/p>\n\n\n\n<p>Load data first, then MVs.<\/p>\n\n\n\n<p>There is also a search_path problem.<\/p>\n\n\n\n<p>ora2pg does not add <code>SET search_path<\/code> to the MVIEW export file.<\/p>\n\n\n\n<p>If you load <code>SH_mviews.sql<\/code> directly, psql runs as the postgres OS user, whose default search_path is <code>public<\/code> \u2014 the MVs are created in <code>public<\/code>, pas <code>sh<\/code>.<\/p>\n\n\n\n<p>The fix is the pipe trick:<\/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>echo<\/code> prepends the <code>SET search_path<\/code> directive so every <code>CREATE MATERIALIZED VIEW<\/code> in the file lands in the <code>sh<\/code> schema.<\/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\">Problem 5: DIMENSION Objects Are Silently Dropped<\/h3>\n\n\n\n<p>Oracle supports <code>CREATE DIMENSION<\/code> syntax, which defines rollup hierarchies for Oracle's query rewrite engine and OLAP operations.<\/p>\n\n\n\n<p>PostgreSQL has no equivalent.<\/p>\n\n\n\n<p>ora2pg silently skips DIMENSION objects.<\/p>\n\n\n\n<p>There is no warning, no error, no stub DDL.<\/p>\n\n\n\n<p>If you don't check for them manually, you won't know they existed until someone asks why an Oracle-side report that used query rewrite no longer performs as expected.<\/p>\n\n\n\n<p>In the SH schema, CUSTOMERS_DIM, PRODUCTS_DIM, TIMES_DIM, and CHANNELS_DIM are all skipped.<\/p>\n\n\n\n<p>There is no functional impact on standard SQL queries \u2014 dimensions are optimizer metadata, not data.<\/p>\n\n\n\n<p>The right action is to document them as dropped in the migration risk register.<\/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=\"step-1-populate-oracle-sh-data-using-sq-lcl\">Step 1 \u2014 Populate Oracle SH Data Using SQLcl<\/h3>\n\n\n\n<p>The SH installation script calls <code>sh_populate.sql<\/code> internally, which uses SQLcl's <code>LOAD<\/code> command to read CSV files.<\/p>\n\n\n\n<p>SQL*Plus does not support <code>LOAD<\/code> \u2014 it silently skips every <code>LOAD<\/code> call with <code>SP2-0734: unknown command<\/code>.<\/p>\n\n\n\n<p>The schema is created but every large table stays at 0 rows.<\/p>\n\n\n\n<p>Download the CSV files first:<\/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>Run the install using SQLcl, not 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>Inside 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>Verify row counts:<\/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\">Step 2 \u2014 Create the PostgreSQL Database and User<\/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\">Step 3 \u2014 Create the Output Directory<\/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\">Step 4 \u2014 Configure 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>Set these values:<\/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\">Step 5 \u2014 Run SHOW_COLUMN to Identify NUMBER(38) Columns<\/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\">Step 6 \u2014 Set MODIFY_TYPE Overrides<\/h3>\n\n\n\n<p>Add the <code>MODIFICATION_TYPE<\/code> line to <code>ora2pg.conf<\/code> (must be one line \u2014 see Problem 2 above).<\/p>\n\n\n\n<p>Verify exactly one active line exists:<\/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\">Step 7 \u2014 Export TABLE<\/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\">Step 8 \u2014 Fix the TABLE Export (Bitmap Indexes)<\/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>Apply the index decisions from Problem 3:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Delete <code>sales_channel_bix<\/code> et <code>costs_channel_bix<\/code><\/li>\n\n\n\n<li>Replace <code>sales_time_bix<\/code> et <code>costs_time_bix<\/code> with <code>USING BRIN<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-9-export-partition\">Step 9 \u2014 Export 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\">Step 10 \u2014 Export 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\">Step 11 \u2014 Export VIEW<\/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\">Step 12 \u2014 Export Data (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\">Step 13 \u2014 Load in the Correct Order<\/h3>\n\n\n\n<p>Verify the target is empty:<\/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>Load 1 \u2014 Table DDL (parent tables, indexes, PKs):<\/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>Load 2 \u2014 Partition child tables (must come before data):<\/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>Load 3 \u2014 Data:<\/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>Load 4 \u2014 Materialized views (after data, with search_path pipe trick):<\/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>Load 5 \u2014 View:<\/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\">Step 14 \u2014 Re-Apply FK Constraints<\/h3>\n\n\n\n<p>ora2pg's COPY data file drops all FK constraints before loading data and does not re-add them.<\/p>\n\n\n\n<p>This is confirmed ora2pg behaviour (issue #1960).<\/p>\n\n\n\n<p>Verify FKs are missing after the load:<\/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>Re-apply all 10 FKs in one command:<\/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>Enable PG_DSN in <code>ora2pg.conf<\/code>, then run:<\/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>Expected diffs (not migration errors):<\/strong><\/p>\n\n\n\n<p><strong>PRIMARY KEY count mismatch on CHANNELS, COUNTRIES, PRODUCTS (Oracle: 0, PostgreSQL: 1):<\/strong><\/p>\n\n\n\n<p>Oracle's SH schema defines PKs with <code>DISABLE NOVALIDATE<\/code> \u2014 the constraints are defined but not enforced.<\/p>\n\n\n\n<p>Oracle's catalog reports them as 0 active PKs.<\/p>\n\n\n\n<p>PostgreSQL correctly enforces them.<\/p>\n\n\n\n<p>This is an artefact of the Oracle sample schema design \u2014 no action needed.<\/p>\n\n\n\n<p><strong>NOT NULL count mismatch on COSTS and SALES (Oracle: 6 and 7, PostgreSQL: 0):<\/strong><\/p>\n\n\n\n<p>Both are partitioned tables.<\/p>\n\n\n\n<p>PostgreSQL enforces NOT NULL constraints on partition child tables, not on the parent.<\/p>\n\n\n\n<p>ora2pg's TEST reads the parent and gets 0.<\/p>\n\n\n\n<p>The constraints exist and are enforced \u2014 verify with <code>\\d sh.sales_1995<\/code> if needed.<\/p>\n\n\n\n<p><strong>MVIEW count (2 vs 0):<\/strong><\/p>\n\n\n\n<p>ora2pg's TEST does not reliably detect materialized views in the target schema.<\/p>\n\n\n\n<p>Verify directly:<\/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>Row counts \u2014 all tables must match:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CHANNELS                     5  \/     5   MATCH\nCOUNTRIES                   23  \/    23   MATCH\nCUSTOMERS                55500  \/ 55500   MATCH\nPRODUCTS                    72  \/    72   MATCH\nPROMOTIONS                 503  \/   503   MATCH\nTIMES                     1826  \/  1826   MATCH\nCOSTS                    82112  \/ 82112   MATCH\nSALES                   918843  \/918843   MATCH\nSUPPLEMENTARY_DEMOGRAPHICS 4500 \/  4500  MATCH<\/code><\/pre>\n\n\n\n<p>If SALES shows a mismatch, check individual partition row counts:<\/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>Most Oracle data warehouse schemas look like SH.<\/p>\n\n\n\n<p>Partitioned fact tables, bitmap indexes on FK columns, materialized views rebuilt on a schedule, and dimension metadata that no one has looked at in years.<\/p>\n\n\n\n<p>None of these are blockers \u2014 they all have clean PostgreSQL equivalents.<\/p>\n\n\n\n<p>But the migration is not automatic.<\/p>\n\n\n\n<p><code>ora2pg<\/code> gets you 80% of the way there.<\/p>\n\n\n\n<p>The remaining 20% is a set of specific decisions: run TABLE and PARTITION as separate exports and load them in the right order; override every NUMBER(38) column to bigint using MODIFY_TYPE; set BITMAP_AS_GIN 0 and review each index individually; prepend SET search_path when loading materialized views; and re-apply FK constraints after the data load because ora2pg drops them and never puts them back.<\/p>\n\n\n\n<p>The DIMENSION objects are the only thing with no PostgreSQL equivalent.<\/p>\n\n\n\n<p>They are optimizer metadata \u2014 dropping them has no effect on query correctness.<\/p>\n\n\n\n<p>Document them as dropped and move on.<\/p>\n\n\n\n<p>In every SH-class migration I run, the same seven issues come up.<\/p>\n\n\n\n<p>Knowing them before the first export is what separates a clean cutover from a day of debugging.<\/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\">Next Steps<\/h2>\n\n\n\n<p>If you have an Oracle data warehouse schema and want to understand the migration scope before committing to a full assessment, start with the <a href=\"https:\/\/rootfan.com\/fr\/services\/\">free migration audit at 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_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":[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":9,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6744\/revisions"}],"predecessor-version":[{"id":6766,"href":"https:\/\/rootfan.com\/fr\/wp-json\/wp\/v2\/posts\/6744\/revisions\/6766"}],"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}]}}