{"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":"migracion-de-ora2pg-de-oracle-a-postgresql-paso-a-paso","status":"publish","type":"post","link":"https:\/\/rootfan.com\/es\/ora2pg-migration-oracle-to-postgres-step-by-step\/","title":{"rendered":"Migraci\u00f3n de Oracle a Postgres Paso a Paso: El Esquema SH (Particiones, Vistas Materializadas, \u00cdndices Bitmap)"},"content":{"rendered":"<p>El esquema SH (Sales History) de Oracle es el ejemplo est\u00e1ndar de Oracle de un almac\u00e9n de datos de esquema en estrella.<\/p>\n\n\n\n<p>Tiene una tabla de hechos CENTRAL DE VENTAS, seis tablas de dimensiones, dos vistas materializadas, tablas de hechos particionadas por rango e \u00edndices bitmap en columnas de baja cardinalidad.<\/p>\n\n\n\n<p>Todas esas caracter\u00edsticas existen en cada almac\u00e9n de datos real de Oracle.<\/p>\n\n\n\n<p>Y todas ellas requieren decisiones que un directo <code>ora2pg<\/code> correr no servir\u00e1 para ti.<\/p>\n\n\n\n<p>Esta publicaci\u00f3n detalla los pasos exactos que utilic\u00e9 para migrar SH de Oracle 19c a PostgreSQL 18 utilizando <code>ora2pg<\/code>.<\/p>\n\n\n\n<p>El esquema de ejemplo SH de Oracle est\u00e1 disponible en <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>\u00cdndice<\/h2><nav><ul><li><a href=\"#what-sh-contains\">\u00bfQu\u00e9 contiene el SO?<\/a><ul><li><a href=\"#problem-1-partitioned-tables-require-two-separate-exports\">Problema 1: Las tablas particionadas requieren dos exportaciones separadas<\/a><\/li><li><a href=\"#problem-2-number-38-columns-are-not-handled-by-pg-integer-type\">Problema 2: Las columnas NUMBER(38) no son manejadas por PG_INTEGER_TYPE<\/a><\/li><li><a href=\"#problem-3-bitmap-indexes-keep-drop-or-replace\">Problema 3: \u00cdndices de mapas de bits \u2014 Conservar, Eliminar o Reemplazar<\/a><\/li><li><a href=\"#problem-4-materialized-view-refresh-is-no-longer-automatic\">Problema 4: La Actualizaci\u00f3n de la Vista Materializada Ya No Es Autom\u00e1tica<\/a><\/li><li><a href=\"#problem-5-dimension-objects-are-silently-dropped\">Problema 5: Los objetos DIMENSION se descartan silenciosamente<\/a><\/li><\/ul><\/li><li><a href=\"#running-the-migration-every-command-in-order\">Ejecutando la Migraci\u00f3n: Cada Comando en Orden<\/a><ul><li><a href=\"#step-1-populate-oracle-sh-data-using-sq-lcl\">Paso 1: poblar los datos de Oracle SH usando SQLcl<\/a><\/li><li><a href=\"#step-2-create-the-postgre-sql-database-and-user\">Paso 2 \u2014 Crear la base de datos y el usuario de PostgreSQL<\/a><\/li><li><a href=\"#step-3-create-the-output-directory\">Paso 3 \u2013 Crea el directorio de salida<\/a><\/li><li><a href=\"#step-4-configure-ora-2-pg-conf\">Paso 4 \u2014 Configurar ora2pg.conf<\/a><\/li><li><a href=\"#step-5-run-show-column-to-identify-number-38-columns\">Paso 5: Ejecutar SHOW_COLUMN para identificar columnas NUMBER(38)<\/a><\/li><li><a href=\"#step-6-set-modify-type-overrides\">Paso 6: establecer anulaciones de MODIFY_TYPE<\/a><\/li><li><a href=\"#step-7-export-table\">Paso 7 \u2014 Exportar TABLA<\/a><\/li><li><a href=\"#step-8-fix-the-table-export-bitmap-indexes\">Paso 8 \u2014 Corregir la exportaci\u00f3n de TABLAS (\u00edndices de mapa de bits)<\/a><\/li><li><a href=\"#step-9-export-partition\">Paso 9 \u2014 Exportar PARTICI\u00d3N<\/a><\/li><li><a href=\"#step-10-export-mview\">Paso 10 \u2014 Exportar MVIEW<\/a><\/li><li><a href=\"#step-11-export-view\">Paso 11 \u2014 Exportar VISTA<\/a><\/li><li><a href=\"#step-12-export-data-copy\">Paso 12 \u2014 Exportar datos (COPY)<\/a><\/li><li><a href=\"#step-13-load-in-the-correct-order\">Paso 13: Cargar en el orden correcto<\/a><\/li><li><a href=\"#step-14-re-apply-fk-constraints\">Paso 14 \u2014 Volver a aplicar restricciones FK<\/a><\/li><\/ul><\/li><li><a href=\"#what-the-final-test-report-looks-like\">C\u00f3mo se ve el Informe de Prueba Final<\/a><\/li><li><a href=\"#summary\">En resumen<\/a><\/li><li><a href=\"#next-steps\">Pr\u00f3ximos pasos<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-sh-contains\">\u00bfQu\u00e9 contiene el SO?<\/h2>\n\n\n\n<p>SH modela un almac\u00e9n de datos de ventas minoristas.<\/p>\n\n\n\n<p><strong>Tablas de dimensi\u00f3n:<\/strong> CANALES, PA\u00cdSES, CLIENTES, PRODUCTOS, PROMOCIONES, TIEMPOS<\/p>\n\n\n\n<p><strong>Tablas de hechos:<\/strong> VENTAS, COSTOS \u2014 ambos particionados por rangos por TIME_ID<\/p>\n\n\n\n<p><strong>Vistas materializadas:<\/strong> CAL_MES_VENTAS_MV, FWEEK_PSCAT_VENTAS_MV<\/p>\n\n\n\n<p><strong>Volumen de datos:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Cuadro<\/th><th>Filas<\/th><\/tr><\/thead><tbody><tr><td>CANALES<\/td><td>5<\/td><\/tr><tr><td>Pa\u00edses<\/td><td>23<\/td><\/tr><tr><td>PROMOCIONES<\/td><td>503<\/td><\/tr><tr><td>PRODUCTOS<\/td><td>72<\/td><\/tr><tr><td>TIEMPOS<\/td><td>1,826<\/td><\/tr><tr><td>CLIENTES<\/td><td>55,500<\/td><\/tr><tr><td>COSTOS<\/td><td>82,112<\/td><\/tr><tr><td>VENTAS<\/td><td>918,843<\/td><\/tr><tr><td>DEMOGR\u00c1FICOS_SUPLEMENTARIOS<\/td><td>4,500<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>La tabla de VENTAS con casi un mill\u00f3n de filas hace que la exportaci\u00f3n y carga de COPIAR tome entre 15 y 25 minutos.<\/p>\n\n\n\n<p>Planea eso antes de empezar.<\/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\">Problema 1: Las tablas particionadas requieren dos exportaciones separadas<\/h3>\n\n\n\n<p>SALES y COSTS est\u00e1n particionados por rangos seg\u00fan TIME_ID en Oracle.<\/p>\n\n\n\n<p>PostgreSQL admite el particionamiento declarativo por rangos con las mismas sem\u00e1nticas.<\/p>\n\n\n\n<p>El problema es que <code>ora2pg<\/code> divide esto en dos tipos de exportaci\u00f3n.<\/p>\n\n\n\n<p>En <code>MESA<\/code> export genera el DDL de la tabla padre \u2014 la <code>PARTICIONAR POR RANGO (time_id)<\/code> declaraci\u00f3n \u2014 pero sin tablas de particiones secundarias.<\/p>\n\n\n\n<p>Si carga esto y trata de insertar filas, PostgreSQL devuelve:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ERROR: no se encontr\u00f3 partici\u00f3n de la relaci\u00f3n \"sales\" para la fila<\/code><\/pre>\n\n\n\n<p>Las tablas secundarias de partici\u00f3n provienen de una parte separada <code>PARTICI\u00d3N<\/code> exportar.<\/p>\n\n\n\n<p>Necesitas ambos archivos y debes cargar el DDL de la tabla padre antes que las tablas hijas particionadas.<\/p>\n\n\n\n<p><strong>As\u00ed se ve en la pr\u00e1ctica:<\/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>El esquema SH tiene 28 particiones de VENTAS y 9 particiones de COSTOS \u2014 37 en total.<\/p>\n\n\n\n<p>Verificar que el recuento coincida con Oracle antes de cargar.<\/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\">Problema 2: Las columnas NUMBER(38) no son manejadas por PG_INTEGER_TYPE<\/h3>\n\n\n\n<p>Cada columna de ID entera en SH se declara como desnuda <code>N\u00daMERO<\/code> en Oracle, que almacena internamente como <code>N\u00daMERO(38)<\/code>.<\/p>\n\n\n\n<p>La opci\u00f3n de ora2pg <code>PG_INTEGER_TYPE 1<\/code> convierte desnudo <code>N\u00daMERO<\/code> (sin precisi\u00f3n) para <code>entero largo<\/code>.<\/p>\n\n\n\n<p>Pero <code>N\u00daMERO(38)<\/code> tiene una precisi\u00f3n expl\u00edcita \u2014 ora2pg la trata como un tipo num\u00e9rico, no un entero, y la mapea a <code>num\u00e9rico(38)<\/code>.<\/p>\n\n\n\n<p>A <code>num\u00e9rico(38)<\/code> la columna funciona pero es incorrecta para una columna de clave primaria o clave for\u00e1nea.<\/p>\n\n\n\n<p>Es 8 veces m\u00e1s grande que <code>entero largo<\/code>, m\u00e1s lento de indexar e imposible de usar con operaciones espec\u00edficas de enteros.<\/p>\n\n\n\n<p>La soluci\u00f3n es <code>MODIFICAR_TIPO<\/code> \u2014 una directiva de una sola l\u00ednea en <code>ora2pg.conf<\/code> que fuerza un tipo espec\u00edfico para columnas espec\u00edficas.<\/p>\n\n\n\n<p><strong>Regla cr\u00edtica:<\/strong> <code>MODIFICAR_TIPO<\/code> es una sola l\u00ednea.<\/p>\n\n\n\n<p>Todas las anulaciones van en una l\u00ednea, separadas por comas.<\/p>\n\n\n\n<p>Si escribes m\u00faltiples <code>MODIFICAR_TIPO<\/code> l\u00edneas, <code>ora2pg<\/code> usa solo el primero e ignora silenciosamente el resto.<\/p>\n\n\n\n<p>Ejecutar <code>MOSTRAR_COLUMNA<\/code> primero en identificar cada <code>num\u00e9rico(38)<\/code> columna<\/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>Luego, agrega todas las anulaciones a <code>ora2pg.conf<\/code> en una l\u00ednea:<\/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>Antes de ejecutar cualquier exportaci\u00f3n, confirme que hay exactamente un activo <code>MODIFICAR_TIPO<\/code> l\u00ednea:<\/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\">Problema 3: \u00cdndices de mapas de bits \u2014 Conservar, Eliminar o Reemplazar<\/h3>\n\n\n\n<p>Oracle utiliza \u00edndices bitmap en columnas de baja cardinalidad en SH: CHANNEL_ID (5 valores), PROMO_ID (503 valores) y TIME_ID.<\/p>\n\n\n\n<p>PostgreSQL no tiene un tipo de \u00edndice de mapa de bits.<\/p>\n\n\n\n<p>Por defecto, ora2pg convierte los \u00edndices bitmap en \u00edndices GIN (<code>BITMAP_COMO_GIN 1<\/code>).<\/p>\n\n\n\n<p>Eso est\u00e1 mal para estas columnas \u2014 GIN est\u00e1 dise\u00f1ado para b\u00fasqueda de texto completo y datos de tipo array, no para claves for\u00e1neas de tipo entero.<\/p>\n\n\n\n<p>Configurar <code>BITMAP_COMO_GINEBRA 0<\/code> en <code>ora2pg.conf<\/code>.<\/p>\n\n\n\n<p>Esto indica a ora2pg que convierta los \u00edndices bitmap en \u00edndices B-tree regulares, los cuales revisas individualmente.<\/p>\n\n\n\n<p>La tabla de decisi\u00f3n para SH:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Oracle \u00edndice bitmap<\/th><th>Cardinalidad de columna<\/th><th>Decisi\u00f3n PostgreSQL<\/th><\/tr><\/thead><tbody><tr><td>canal_de_ventas_bix<\/td><td>5 canales<\/td><td><strong>Dejar<\/strong> \u2014 Un \u00e1rbol B con 5 valores no aporta ning\u00fan beneficio; PostgreSQL utiliza un escaneo secuencial<\/td><\/tr><tr><td>ventas_cli_bix<\/td><td>55.500 clientes<\/td><td><strong>Mantener como \u00e1rbol B<\/strong> \u2014 alta cardinalidad, utilizada para consultas a nivel de cliente<\/td><\/tr><tr><td>ventas_prod_bix<\/td><td>72 productos<\/td><td><strong>Mantener como \u00e1rbol B<\/strong><\/td><\/tr><tr><td>venta_promocional_bix<\/td><td>503 promociones<\/td><td><strong>Mantener como \u00e1rbol B<\/strong><\/td><\/tr><tr><td>tiempo_de_venta_bix<\/td><td>1.826 d\u00edas<\/td><td><strong>Reemplazar con BRIN<\/strong> \u2014 TIME_ID est\u00e1 ordenado monot\u00f3nicamente en una tabla particionada por rangos<\/td><\/tr><tr><td>costos_canal_bix<\/td><td>5 canales<\/td><td><strong>Dejar<\/strong><\/td><\/tr><tr><td>costos_prod_bix, costos_promo_bix<\/td><td>igual<\/td><td><strong>Mantener como \u00e1rbol B<\/strong><\/td><\/tr><tr><td>costos_tiempo_bix<\/td><td>fechas ordenadas<\/td><td><strong>Reemplazar con BRIN<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>BRIN (Block Range Index) es un \u00edndice de rangos de bloques que almacena valores m\u00ednimos\/m\u00e1ximos por bloque de 128 p\u00e1ginas.<\/p>\n\n\n\n<p>Para una columna de series de tiempo en una tabla particionada, es mucho m\u00e1s barato de mantener que un B-tree y igualmente efectivo para escaneos de rangos.<\/p>\n\n\n\n<p>Editar el archivo de exportaci\u00f3n de la TABLA antes de cargar:<\/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\">Problema 4: La Actualizaci\u00f3n de la Vista Materializada Ya No Es Autom\u00e1tica<\/h3>\n\n\n\n<p>Oracle CAL_MONTH_SALES_MV y FWEEK_PSCAT_SALES_MV usan <code>REFRESCAR COMPLETAMENTE BAJO DEMANDA<\/code>.<\/p>\n\n\n\n<p>La actualizaci\u00f3n COMPLETA significa que la vista se reconstruye desde cero en cada actualizaci\u00f3n.<\/p>\n\n\n\n<p>PostgreSQL <code>REFRESCAR VISTA MATERIALIZADA<\/code> hace exactamente esto \u2014 ning\u00fan problema de traducci\u00f3n.<\/p>\n\n\n\n<p>El problema es la programaci\u00f3n.<\/p>\n\n\n\n<p>Oracle <code>DBMS_MVIEW.REFRESH<\/code> y <code>DBMS_SCHEDULER<\/code> gestionar la actualizaci\u00f3n autom\u00e1tica.<\/p>\n\n\n\n<p>PostgreSQL no tiene un equivalente incorporado.<\/p>\n\n\n\n<p>Despu\u00e9s de la migraci\u00f3n, las MV son instant\u00e1neas est\u00e1ticas hasta que alguien ejecuta expl\u00edcitamente:<\/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>Para replicar la actualizaci\u00f3n programada, instala pg_cron (requiere superusuario) y crea un trabajo despu\u00e9s de la migraci\u00f3n.<\/p>\n\n\n\n<p>Tambi\u00e9n existe una restricci\u00f3n de orden de carga.<\/p>\n\n\n\n<p>PostgreSQL ejecuta la consulta MV SELECT inmediatamente cuando la ejecutas <code>CREAR VISTA MATERIALIZADA COMO SELECCIONAR<\/code>.<\/p>\n\n\n\n<p>Si carga el DDL de la MV antes de los datos, la consulta se ejecuta contra tablas vac\u00edas y la MV se crea vac\u00eda.<\/p>\n\n\n\n<p>Carga primero los datos, luego las vistas de materiales.<\/p>\n\n\n\n<p>Tambi\u00e9n hay un problema con search_path.<\/p>\n\n\n\n<p>ora2pg no a\u00f1ade <code>SET search_path<\/code> al archivo de exportaci\u00f3n de MVIEW.<\/p>\n\n\n\n<p>Si cargas <code>SH_mviews.sql<\/code> directamente, psql se ejecuta como el usuario del sistema operativo postgres, cuyo search_path predeterminado es <code>p\u00fablico<\/code> los videos musicales se crean en <code>p\u00fablico<\/code>, no <code>a<\/code>.<\/p>\n\n\n\n<p>La soluci\u00f3n es el truco de la tuber\u00eda:<\/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>En <code>eco<\/code> ante pone el <code>SET search_path<\/code> directiva as\u00ed que cada <code>CREAR VISTA MATERIALIZADA<\/code> en el archivo aterriza en el <code>a<\/code> esquema.<\/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\">Problema 5: Los objetos DIMENSION se descartan silenciosamente<\/h3>\n\n\n\n<p>Oracle es compatible <code>CREAR DIMENSI\u00d3N<\/code> sintaxis, que define jerarqu\u00edas de consolidaci\u00f3n para el motor de reescritura de consultas y las operaciones OLAP de Oracle.<\/p>\n\n\n\n<p>PostgreSQL no tiene un equivalente.<\/p>\n\n\n\n<p>ora2pg omite silenciosamente los objetos DIMENSION.<\/p>\n\n\n\n<p>No hay advertencia, ni error, ni DDL de marcador de posici\u00f3n.<\/p>\n\n\n\n<p>Si no los revisas manualmente, no sabr\u00e1s que exist\u00edan hasta que alguien pregunte por qu\u00e9 un informe del lado de Oracle que utilizaba la reescritura de consultas ya no funciona como se esperaba.<\/p>\n\n\n\n<p>En el esquema SH, se omiten CUSTOMERS_DIM, PRODUCTS_DIM, TIMES_DIM y CHANNELS_DIM.<\/p>\n\n\n\n<p>No hay ning\u00fan impacto funcional en las consultas SQL est\u00e1ndar: las dimensiones son metadatos del optimizador, no datos.<\/p>\n\n\n\n<p>La acci\u00f3n correcta es documentarlos como abandonados en el registro de riesgos de migraci\u00f3n.<\/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\">Ejecutando la Migraci\u00f3n: Cada Comando en Orden<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-1-populate-oracle-sh-data-using-sq-lcl\">Paso 1: poblar los datos de Oracle SH usando SQLcl<\/h3>\n\n\n\n<p>El script de instalaci\u00f3n de SH llama <code>sh_populate.sql<\/code> internamente, que usa SQLcl's <code>CARGAR<\/code> comando para leer archivos CSV.<\/p>\n\n\n\n<p>SQL*Plus no soporta <code>CARGAR<\/code> \u2014 salta silenciosamente cada <code>CARGAR<\/code> llamada con <code>SP2-0734: comando desconocido<\/code>.<\/p>\n\n\n\n<p>El esquema se crea pero cada tabla grande permanece con 0 filas.<\/p>\n\n\n\n<p>Primero descargue los archivos 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>Ejecuta la instalaci\u00f3n usando SQLcl, no 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>Dentro de 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>Verificar recuentos de filas:<\/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\">Paso 2 \u2014 Crear la base de datos y el usuario de 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\">Paso 3 \u2013 Crea el directorio de salida<\/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\">Paso 4 \u2014 Configurar 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>Establecer estos valores:<\/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\">Paso 5: Ejecutar SHOW_COLUMN para identificar columnas 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\">Paso 6: establecer anulaciones de MODIFY_TYPE<\/h3>\n\n\n\n<p>A\u00f1adir el <code>MODIFICAR_TIPO<\/code> l\u00ednea a <code>ora2pg.conf<\/code> (debe ser una sola l\u00ednea \u2014 ver Problema 2 arriba).<\/p>\n\n\n\n<p>Verificar que existe exactamente una l\u00ednea activa:<\/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\">Paso 7 \u2014 Exportar TABLA<\/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\">Paso 8 \u2014 Corregir la exportaci\u00f3n de TABLAS (\u00edndices de mapa de bits)<\/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>Aplique las decisiones de \u00edndice del Problema 3:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Borrar <code>canal_de_ventas_bix<\/code> y <code>costos_canal_bix<\/code><\/li>\n\n\n\n<li>Reemplazar <code>tiempo_de_venta_bix<\/code> y <code>costos_tiempo_bix<\/code> con <code>USANDO BRIN<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-9-export-partition\">Paso 9 \u2014 Exportar PARTICI\u00d3N<\/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\">Paso 10 \u2014 Exportar 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\">Paso 11 \u2014 Exportar VISTA<\/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\">Paso 12 \u2014 Exportar datos (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\">Paso 13: Cargar en el orden correcto<\/h3>\n\n\n\n<p>Verificar que el objetivo est\u00e9 vac\u00edo:<\/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>Carga 1 \u2014 DDL de tablas (tablas padre, \u00edndices, PK):<\/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>Cargar 2 \u2014 Particionar tablas secundarias (debe ir antes de los datos):<\/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>Cargar 3 \u2014 Datos:<\/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>Carga 4 \u2014 Vistas materializadas (despu\u00e9s de los datos, con el truco de tuber\u00eda `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>Cargar 5 \u2014 Ver:<\/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\">Paso 14 \u2014 Volver a aplicar restricciones FK<\/h3>\n\n\n\n<p>El archivo de datos COPY de ora2pg elimina todas las restricciones FK antes de cargar los datos y no las vuelve a a\u00f1adir.<\/p>\n\n\n\n<p>Este es un comportamiento confirmado de ora2pg (incidencia #1960).<\/p>\n\n\n\n<p>Verificar que faltan las FK despu\u00e9s de la carga:<\/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>Reaplica todas las 10 FKs en un comando:<\/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>Verificar<\/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\">C\u00f3mo se ve el Informe de Prueba Final<\/h2>\n\n\n\n<p>Habilitar PG_DSN en <code>ora2pg.conf<\/code>, luego ejecuta:<\/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>Diferencias esperadas (no errores de migraci\u00f3n):<\/strong><\/p>\n\n\n\n<p><strong>Desajuste en el recuento dePRIMARY KEY en CHANNELS, COUNTRIES, PRODUCTS (Oracle: 0, PostgreSQL: 1):<\/strong><\/p>\n\n\n\n<p>El esquema SH de Oracle define PKs con <code>DESACTIVAR NOVALIDATE<\/code> \u2014 las restricciones est\u00e1n definidas pero no se aplican.<\/p>\n\n\n\n<p>El cat\u00e1logo de Oracle los reporta como 0 PKs activos.<\/p>\n\n\n\n<p>PostgreSQL los aplica correctamente.<\/p>\n\n\n\n<p>Este es un artefacto del dise\u00f1o del esquema de ejemplo de Oracle; no se necesita ninguna acci\u00f3n.<\/p>\n\n\n\n<p><strong>Inconsistencia en el recuento NOT NULL en COSTS y SALES (Oracle: 6 y 7, PostgreSQL: 0):<\/strong><\/p>\n\n\n\n<p>Ambas son tablas particionadas.<\/p>\n\n\n\n<p>PostgreSQL aplica restricciones NOT NULL en las tablas hijas de la partici\u00f3n, no en la padre.<\/p>\n\n\n\n<p>ora2pg's TEST lee el padre y obtiene 0.<\/p>\n\n\n\n<p>Las restricciones existen y se imponen; verifique con <code>\\d sh.ventas_1995<\/code> si es necesario.<\/p>\n\n\n\n<p><strong>Conteo de MVIEW (2 vs 0):<\/strong><\/p>\n\n\n\n<p>La prueba de ora2pg no detecta de manera confiable las vistas materializadas en el esquema de destino.<\/p>\n\n\n\n<p>Verificar directamente:<\/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>Recuentos de filas: todas las tablas deben coincidir:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CANALES 5 \/ 5 PARTIDO\nPA\u00cdSES 23 \/ 23 COINCIDENCIA\nCLIENTES 55500 \/ 55500 COINCIDEN\nPRODUCTOS 72 \/ 72 COINCIDENCIA\nPROMOCIONES 503 \/ 503 COINCIDENCIA\nTIEMPOS 1826 \/ 1826 MATCH\nCOSTES 82112 \/ 82112 MATCH\nVENTAS 918843 \/918843 MATCH\nDATOS_DEMOGR\u00c1FICOS_COMPLEMENTARIOS 4500 \/ 4500 MATCH<\/code><\/pre>\n\n\n\n<p>Si SALES muestra una discrepancia, revise el recuento de filas de particiones individuales:<\/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 resumen<\/h2>\n\n\n\n<p>La mayor\u00eda de los esquemas de almac\u00e9n de datos de Oracle se parecen a SH.<\/p>\n\n\n\n<p>Tablas de hechos particionadas, \u00edndices de mapa de bits en columnas FK, vistas materializadas reconstruidas seg\u00fan un calendario y metadatos de dimensiones que nadie ha mirado en a\u00f1os.<\/p>\n\n\n\n<p>Ninguno de estos son bloqueadores \u2014 todos tienen equivalentes limpios en PostgreSQL.<\/p>\n\n\n\n<p>Pero la migraci\u00f3n no es autom\u00e1tica.<\/p>\n\n\n\n<p><code>ora2pg<\/code> te lleva el 80 de camino%.<\/p>\n\n\n\n<p>Las 20% restantes son un conjunto de decisiones espec\u00edficas: ejecutar TABLE y PARTITION como exportaciones separadas y cargarlas en el orden correcto; sobrescribir cada columna NUMBER(38) a bigint usando MODIFY_TYPE; establecer BITMAP_AS_GIN en 0 y revisar cada \u00edndice individualmente; anteponer SET search_path al cargar vistas materializadas; y volver a aplicar las restricciones FK despu\u00e9s de la carga de datos porque ora2pg las elimina y nunca las vuelve a poner.<\/p>\n\n\n\n<p>Los objetos DIMENSION son los \u00fanicos sin equivalente en PostgreSQL.<\/p>\n\n\n\n<p>Son metadatos del optimizador; eliminarlos no tiene ning\u00fan efecto en la correcci\u00f3n de la consulta.<\/p>\n\n\n\n<p>Documentarlas como ca\u00eddas y seguir adelante.<\/p>\n\n\n\n<p>En cada migraci\u00f3n de clase SH que ejecuto, surgen los mismos siete problemas.<\/p>\n\n\n\n<p>Conocerlos antes de la primera exportaci\u00f3n es lo que separa una migraci\u00f3n limpia de un d\u00eda de depuraci\u00f3n.<\/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\">Pr\u00f3ximos pasos<\/h2>\n\n\n\n<p>Si tienes un esquema de data warehouse de Oracle y quieres entender el alcance de la migraci\u00f3n antes de comprometerte a una evaluaci\u00f3n completa, comienza con la <a href=\"https:\/\/rootfan.com\/es\/servicios\/\">auditor\u00eda de migraci\u00f3n gratuita en 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\/es\/ora2pg-migration-oracle-to-postgres-step-by-step\/\" class=\"more-link\">Seguir leyendo<span class=\"screen-reader-text\"> &#8220;Oracle to Postgres Migration Step by Step: The SH Schema (Partitions, Materialized Views, Bitmap Indexes)&#8221;<\/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\/es\/wp-json\/wp\/v2\/posts\/6744","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/comments?post=6744"}],"version-history":[{"count":9,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6744\/revisions"}],"predecessor-version":[{"id":6766,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6744\/revisions\/6766"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media\/6763"}],"wp:attachment":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media?parent=6744"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/categories?post=6744"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/tags?post=6744"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}