{"id":6754,"date":"2026-04-20T14:09:36","date_gmt":"2026-04-20T12:09:36","guid":{"rendered":"https:\/\/rootfan.com\/?p=6754"},"modified":"2026-04-20T14:29:44","modified_gmt":"2026-04-20T12:29:44","slug":"problemas-y-soluciones-en-la-migracion-de-oracle-a-postgresql","status":"publish","type":"post","link":"https:\/\/rootfan.com\/es\/oracle-to-postgres-migration-issues-and-fixes\/","title":{"rendered":"Migraci\u00f3n de Oracle a Postgres: Problemas y Soluciones del Esquema CO"},"content":{"rendered":"<p>El esquema CO (Customer Orders) de Oracle es el reemplazo moderno del esquema OE m\u00e1s antiguo. <\/p>\n\n\n\n<p>Viene con Oracle 19c, se mantiene activamente y est\u00e1 construido de la manera en que se construyen la mayor\u00eda de las aplicaciones Oracle reales hoy en d\u00eda: columnas IDENTITY en lugar de pares secuencia-disparador, JSON almacenado en columnas BLOB y vistas que utilizan funciones SQL espec\u00edficas de Oracle.<\/p>\n\n\n\n<p>Ejecut\u00e9 la migraci\u00f3n completa usando ora2pg 25.0 con Oracle 19c como origen y PostgreSQL 18 como destino. <\/p>\n\n\n\n<p>Esta publicaci\u00f3n cubre los cinco problemas que requirieron intervenci\u00f3n manual y por qu\u00e9 cada uno aparecer\u00e1 en casi todos los esquemas de producci\u00f3n que migre.<\/p>\n\n\n\n<p>Esta es la tercera publicaci\u00f3n de la serie. <\/p>\n\n\n\n<p>En <a href=\"https:\/\/rootfan.com\/es\/ejemplo-de-migracion-con-ora2pg\/\">Publicaci\u00f3n del esquema de RR. HH.<\/a> cubri\u00f3 patrones de secuencia de activaci\u00f3n, <code>%TIPO<\/code> par\u00e1metros, y el error de reaplicaci\u00f3n de FK. <\/p>\n\n\n\n<p>En <a href=\"https:\/\/rootfan.com\/es\/migracion-de-ora2pg-de-oracle-a-postgresql-paso-a-paso\/\">Publicaci\u00f3n de esquema SH<\/a> tablas particionadas cubiertas, \u00edndices de mapa de bits y vistas materializadas.<\/p>\n\n\n\n<p>CO introduce tres nuevas categor\u00edas de problemas que no se ven en ninguna de esas.<\/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-co-contains\">What CO Contains<\/a><ul><li><a href=\"#problem-1-identity-columns-have-no-explicit-sequences\">Problem 1: IDENTITY Columns Have No Explicit Sequences<\/a><\/li><li><a href=\"#problem-2-blob-storing-json-must-become-jsonb-not-bytea\">Problem 2: BLOB Storing JSON Must Become JSONB, Not BYTEA<\/a><\/li><li><a href=\"#problem-3-fk-columns-are-number38-pks-are-bigint-type-mismatch\">Problem 3: FK Columns Are NUMBER(38), PKs Are bigint \u2014 Type Mismatch<\/a><\/li><li><a href=\"#problem-4-the-is-json-check-constraint-uses-oracle-syntax\">Problem 4: The IS JSON Check Constraint Uses Oracle Syntax<\/a><\/li><li><a href=\"#problem-5-three-views-use-oracle-specific-sql-functions\">Problem 5: Three Views Use Oracle-Specific SQL Functions<\/a><ul><li><a href=\"#customer_order_products-listagg-with-on-overflow\">customer_order_products \u2014 LISTAGG with ON OVERFLOW<\/a><\/li><li><a href=\"#store_orders-grouping_id\">store_orders \u2014 GROUPING_ID<\/a><\/li><li><a href=\"#product_reviews-json_table\">product_reviews \u2014 JSON_TABLE<\/a><\/li><\/ul><\/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=\"#environment\">Medio ambiente<\/a><\/li><li><a href=\"#step-1-gather-oracle-statistics\">Step 1 \u2014 Gather Oracle statistics<\/a><\/li><li><a href=\"#step-2-create-the-output-directory\">Paso 2: cree el directorio de salida<\/a><\/li><li><a href=\"#step-3-base-ora2pg-conf\">Step 3 \u2014 Base ora2pg.conf<\/a><\/li><li><a href=\"#step-4-generate-the-assessment-report\">Paso 4: Generar el informe de evaluaci\u00f3n<\/a><\/li><li><a href=\"#step-5-analyse-column-types\">Paso 5 \u2014 Analizar tipos de columna<\/a><\/li><li><a href=\"#step-6-add-modify_type-and-re-save-the-config\">Paso 6: A\u00f1adir MODIFY_TYPE y volver a guardar la configuraci\u00f3n<\/a><\/li><li><a href=\"#step-7-run-the-schema-exports\">Step 7 \u2014 Run the schema exports<\/a><\/li><li><a href=\"#step-8-write-the-manual-fix-files\">Paso 8 \u2014 Escribir los archivos de correcci\u00f3n manual<\/a><\/li><li><a href=\"#step-9-export-the-data\">Step 9 \u2014 Export the data<\/a><\/li><li><a href=\"#step-10-create-the-database\">Step 10 \u2014 Create the database<\/a><\/li><li><a href=\"#step-11-load-in-dependency-order\">Step 11 \u2014 Load in dependency order<\/a><\/li><li><a href=\"#step-12-re-apply-fk-constraints\">Step 12 \u2014 Re-apply FK constraints<\/a><\/li><li><a href=\"#step-13-advance-identity-sequences\">Step 13 \u2014 Advance IDENTITY sequences<\/a><\/li><li><a href=\"#step-14-load-the-views\">Step 14 \u2014 Load the views<\/a><\/li><li><a href=\"#step-15-run-the-test-comparison\">Step 15 \u2014 Run the TEST comparison<\/a><\/li><li><a href=\"#what-the-final-test-report-looks-like\">C\u00f3mo se ve el Informe de Prueba Final<\/a><\/li><\/ul><\/li><li><a href=\"#summary\">En resumen<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-co-contains\">What CO Contains<\/h2>\n\n\n\n<p>En <a href=\"https:\/\/github.com\/oracle-samples\/db-sample-schemas\" rel=\"nofollow noopener\" target=\"_blank\">CO schema<\/a> is Oracle's retail order management reference schema: customers, stores, products, orders, shipments, order items, and inventory.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Object type<\/th><th>Count<\/th><\/tr><\/thead><tbody><tr><td>Tablas<\/td><td>7<\/td><\/tr><tr><td>Views<\/td><td>4<\/td><\/tr><tr><td>Secuencias<\/td><td>0 (IDENTITY columns only)<\/td><\/tr><tr><td>Stored procedures<\/td><td>0<\/td><\/tr><tr><td>Triggers<\/td><td>0<\/td><\/tr><tr><td>Rows (total)<\/td><td>~8,800<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>The absence of stored procedures and triggers keeps the PL\/SQL conversion effort at zero. The complexity comes from three places: how Oracle handles IDENTITY columns, a BLOB column that stores JSON, and four views \u2014 three of which use Oracle-specific SQL functions that PostgreSQL does not support.<\/p>\n\n\n\n<p>ora2pg rates the CO schema <strong>A-3<\/strong>: straightforward migration, estimated three person-hours. The cost drivers are the three non-portable views.<\/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-identity-columns-have-no-explicit-sequences\">Problem 1: IDENTITY Columns Have No Explicit Sequences<\/h3>\n\n\n\n<p>CO uses <code>GENERATED BY DEFAULT ON NULL AS IDENTITY<\/code> on every primary key column. Oracle manages the underlying sequences internally \u2014 there are no <code>CREATE SEQUENCE<\/code> statements in the schema, and no sequence-trigger pairs like the HR schema used.<\/p>\n\n\n\n<p>ora2pg converts the Oracle syntax correctly: <code>GENERATED BY DEFAULT ON NULL AS IDENTITY<\/code> se convierte <code>GENERATED BY DEFAULT AS IDENTITY<\/code> in PostgreSQL. That part requires no manual work.<\/p>\n\n\n\n<p>The problem comes after the data load.<\/p>\n\n\n\n<p>When ora2pg exports data via COPY, it exports the actual row IDs from Oracle \u2014 1, 2, 3, &#8230; up to whatever the highest ID is. PostgreSQL's COPY protocol accepts those values and inserts them directly into the IDENTITY columns. The IDENTITY sequence is not called during a COPY load. After the load completes, each IDENTITY sequence still sits at its initial value of 1.<\/p>\n\n\n\n<p>The first INSERT into <code>customers<\/code> after migration will attempt to use <code>customer_id = 1<\/code>. That row already exists. The insert fails with <code>duplicate key value violates unique constraint<\/code>.<\/p>\n\n\n\n<p>The fix is to advance each IDENTITY sequence past the highest loaded value:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSET search_path = co, public;\nSELECT setval(pg_get_serial_sequence(&#039;co.customers&#039;,  &#039;customer_id&#039;),  (SELECT MAX(customer_id)  FROM co.customers));\nSELECT setval(pg_get_serial_sequence(&#039;co.orders&#039;,     &#039;order_id&#039;),     (SELECT MAX(order_id)     FROM co.orders));\nSELECT setval(pg_get_serial_sequence(&#039;co.shipments&#039;,  &#039;shipment_id&#039;),  (SELECT MAX(shipment_id)  FROM co.shipments));\nSELECT setval(pg_get_serial_sequence(&#039;co.stores&#039;,     &#039;store_id&#039;),     (SELECT MAX(store_id)     FROM co.stores));\nSELECT setval(pg_get_serial_sequence(&#039;co.products&#039;,   &#039;product_id&#039;),   (SELECT MAX(product_id)   FROM co.products));\n<\/pre><\/div>\n\n\n<p><code>pg_get_serial_sequence<\/code> resolves the sequence name from the table and column names. This avoids hardcoding sequence names, which vary between ora2pg versions.<\/p>\n\n\n\n<p>Run this block immediately after the data load, before any application touches the migrated database. On a production schema, missed sequences cause INSERT failures that are easy to miss in testing if the test data set uses different IDs than production.<\/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-blob-storing-json-must-become-jsonb-not-bytea\">Problem 2: BLOB Storing JSON Must Become JSONB, Not BYTEA<\/h3>\n\n\n\n<p><code>products.product_details<\/code> is declared as <code>BOLO<\/code> in Oracle with a <code>CHECK (product_details IS JSON)<\/code> constraint. <\/p>\n\n\n\n<p>Oracle databases before 21c store JSON as BLOB or CLOB \u2014 there is no native JSON type.<\/p>\n\n\n\n<p>ora2pg maps all BLOB columns to <code>BYTEA<\/code> by default. <\/p>\n\n\n\n<p>Para <code>product_image<\/code> y <code>stores.logo<\/code>, that is correct \u2014 they store binary image data. <\/p>\n\n\n\n<p>Para <code>product_details<\/code>, it is wrong.<\/p>\n\n\n\n<p><code>BYTEA<\/code> in PostgreSQL stores raw bytes. There are no JSON operators, no JSON functions, no validation on insert. The <code>product_reviews<\/code> view queries <code>product_details<\/code> as JSON using <code>jsonb_array_elements<\/code> after the rewrite (see Problem 5 below). That function requires a <code>JSONB<\/code> argument \u2014 it will fail with <code>function jsonb_array_elements(bytea) does not exist<\/code> if the column is <code>BYTEA<\/code>.<\/p>\n\n\n\n<p>La soluci\u00f3n es un <code>MODIFICAR_TIPO<\/code> override before running the TABLE export:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE   PRODUCTS:PRODUCT_DETAILS:jsonb\n<\/pre><\/div>\n\n\n<p>This is part of a longer <code>MODIFICAR_TIPO<\/code> line \u2014 see Step 6 below for the complete directive.<\/p>\n\n\n\n<p><code>JSONB<\/code> enforces valid JSON on every insert automatically. The <code>CHECK (product_details IS JSON)<\/code> constraint is therefore redundant after the column type change. It also uses Oracle syntax \u2014 PostgreSQL has no <code>IS JSON<\/code> predicate \u2014 so it must be removed from the exported DDL before loading. See Problem 4.<\/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-3-fk-columns-are-number38-pks-are-bigint-type-mismatch\">Problem 3: FK Columns Are NUMBER(38), PKs Are bigint \u2014 Type Mismatch<\/h3>\n\n\n\n<p><code>FORCE_IDENTITY_BIGINT 1<\/code> causes ora2pg to export IDENTITY primary key columns as <code>entero largo<\/code>. <code>PG_INTEGER_TYPE 1<\/code> convierte desnudo <code>N\u00daMERO<\/code> (no precision) columns to <code>entero largo<\/code>.<\/p>\n\n\n\n<p>The FK columns in CO \u2014 <code>orders.customer_id<\/code>, <code>orders.store_id<\/code>, <code>order_items.order_id<\/code>, and others \u2014 are declared as <code>N\u00daMERO(38)<\/code> in Oracle. That is Oracle's internal representation of an integer column declared without precision. Because the precision is explicitly <code>38<\/code>, <code>PG_INTEGER_TYPE 1<\/code> does not apply. ora2pg maps them to <code>num\u00e9rico(38)<\/code>.<\/p>\n\n\n\n<p>The result: primary key columns are <code>entero largo<\/code>, foreign key columns referencing them are <code>num\u00e9rico(38)<\/code>. PostgreSQL requires that FK and PK columns have the same type. Loading the DDL succeeds, but creating the FK constraints fails:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nERROR:  foreign key constraint &quot;orders_customer_id_fk&quot; cannot be implemented\nDETAIL:  Key columns &quot;customer_id&quot; and &quot;customer_id&quot; are of incompatible types: numeric and bigint.\n<\/pre><\/div>\n\n\n<p>The fix is to add all FK columns and other integer columns to <code>MODIFICAR_TIPO<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE   INVENTORY:STORE_ID:bigint,INVENTORY:PRODUCT_ID:bigint,INVENTORY:PRODUCT_INVENTORY:integer,ORDERS:CUSTOMER_ID:bigint,ORDERS:STORE_ID:bigint,ORDER_ITEMS:ORDER_ID:bigint,ORDER_ITEMS:LINE_ITEM_ID:integer,ORDER_ITEMS:PRODUCT_ID:bigint,ORDER_ITEMS:QUANTITY:integer,ORDER_ITEMS:SHIPMENT_ID:bigint,SHIPMENTS:STORE_ID:bigint,SHIPMENTS:CUSTOMER_ID:bigint\n<\/pre><\/div>\n\n\n<p><code>MODIFICAR_TIPO<\/code> reads only the first line of the directive. All overrides \u2014 including the JSONB override from Problem 2 and the <code>num\u00e9rico<\/code> overrides from below \u2014 must be on a single comma-separated line.<\/p>\n\n\n\n<p>Also: do not include type precision inside <code>MODIFICAR_TIPO<\/code> (write <code>num\u00e9rico<\/code>, no <code>numeric(10,2)<\/code>).<\/p>\n\n\n\n<p>The comma inside parentheses is treated as a delimiter and breaks parsing.<\/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-4-the-is-json-check-constraint-uses-oracle-syntax\">Problem 4: The IS JSON Check Constraint Uses Oracle Syntax<\/h3>\n\n\n\n<p>ora2pg exports the <code>products_json_c<\/code> constraint as:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCONSTRAINT products_json_c CHECK (product_details IS JSON)\n<\/pre><\/div>\n\n\n<p>PostgreSQL has no <code>IS JSON<\/code> predicate. Loading the table DDL fails immediately with a syntax error.<\/p>\n\n\n\n<p>The constraint is also redundant. <code>JSONB<\/code> in PostgreSQL validates JSON on every insert. Any row that fails JSON validation is rejected before it reaches storage. The <code>IS JSON<\/code> constraint is doing nothing that the column type does not already do.<\/p>\n\n\n\n<p>The fix: delete the constraint line from the exported DDL before loading.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsed -i &#039;\/products_json_c\/d&#039; \/home\/fernando\/ora2pg-co\/output\/CO_tables_fixed.sql\n<\/pre><\/div>\n\n\n<p>Verificar<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ngrep &#039;products_json_c&#039; \/home\/fernando\/ora2pg-co\/output\/CO_tables_fixed.sql\n# Expected: no output\n<\/pre><\/div>\n\n\n<p>The TEST report will show a CHECK CONSTRAINTS DIFF for the <code>products<\/code> table \u2014 one constraint in Oracle, zero in PostgreSQL. This is expected and correct.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"problem-5-three-views-use-oracle-specific-sql-functions\">Problem 5: Three Views Use Oracle-Specific SQL Functions<\/h3>\n\n\n\n<p>CO has four views. One (<code>product_orders<\/code>) uses standard SQL and converts without changes. Three use functions that have no direct PostgreSQL equivalent:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"customer_order_products-listagg-with-on-overflow\"><code>customer_order_products<\/code> \u2014 LISTAGG with ON OVERFLOW<\/h4>\n\n\n\n<p>Oracle <code>LISTAGG(expr, separator ON OVERFLOW TRUNCATE '...' WITH COUNT)<\/code> concatenates values into a delimited string with overflow handling. <\/p>\n\n\n\n<p>PostgreSQL <code>STRING_AGG(expr, separator ORDER BY ...)<\/code> is the equivalent \u2014 no overflow option.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- Oracle\nLISTAGG(p.product_name, &#039;, &#039; ON OVERFLOW TRUNCATE &#039;...&#039; WITH COUNT)\n  WITHIN GROUP (ORDER BY oi.line_item_id) items\n\n-- PostgreSQL\nSTRING_AGG(p.product_name, &#039;, &#039; ORDER BY oi.line_item_id) items\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"store_orders-grouping_id\"><code>store_orders<\/code> \u2014 GROUPING_ID<\/h4>\n\n\n\n<p>Oracle <code>GROUPING_ID(col1, col2)<\/code> returns an integer bit vector encoding which columns are aggregated in a <code>GROUPING SETS<\/code> query. <\/p>\n\n\n\n<p>PostgreSQL <code>GROUPING(col1, col2)<\/code> does exactly the same thing \u2014 the function name differs, the semantics are identical. <\/p>\n\n\n\n<p>The replacement is one-for-one.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- Oracle\nCASE grouping_id(store_name, order_status)\n  WHEN 1 THEN &#039;STORE TOTAL&#039;\n  ...\n\n-- PostgreSQL\nCASE grouping(s.store_name, o.order_status)\n  WHEN 1 THEN &#039;STORE TOTAL&#039;\n  ...\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"product_reviews-json_table\"><code>product_reviews<\/code> \u2014 JSON_TABLE<\/h4>\n\n\n\n<p>Oracle <code>JSON_TABLE<\/code> expands a JSON array into rows using a relational projection.<\/p>\n\n\n\n<p>PostgreSQL has no <code>JSON_TABLE<\/code> equivalent before version 15 (it was added in PostgreSQL 15 as part of the SQL\/JSON standard, but with different syntax). <\/p>\n\n\n\n<p>For PostgreSQL 14 and earlier \u2014 or for consistent cross-version behaviour \u2014 rewrite using <code>jsonb_array_elements<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- Oracle\nFROM products p,\n     JSON_TABLE(p.product_details, &#039;$&#039;\n       COLUMNS (NESTED PATH &#039;$.reviews&#x5B;*]&#039;\n         COLUMNS (rating INTEGER PATH &#039;$.rating&#039;,\n                  review VARCHAR2(4000) PATH &#039;$.review&#039;))) r\n\n-- PostgreSQL\nFROM products p,\n     jsonb_array_elements(p.product_details-&gt;&#039;reviews&#039;) AS review_row\n<\/pre><\/div>\n\n\n<p>In the PostgreSQL version, each JSON array element is exposed as a <code>jsonb<\/code> value named <code>review_row<\/code>. <\/p>\n\n\n\n<p>Column values are extracted with the <code>->><\/code> operator: <code>review_row->>'rating'<\/code> y <code>review_row->>'review'<\/code>. <\/p>\n\n\n\n<p>The cast <code>::INTEGER<\/code> on the rating field is required because <code>->><\/code> always returns <code>texto<\/code>.<\/p>\n\n\n\n<p>This rewrite depends on <code>product_details<\/code> being <code>JSONB<\/code> \u2014 if the column is <code>BYTEA<\/code> (Problem 2 not fixed), the view fails immediately.<\/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=\"environment\">Medio ambiente<\/h3>\n\n\n\n<p>Dos m\u00e1quinas virtuales en la misma red. <code>srv1<\/code> (192.168.0.180) runs Oracle 19c with the CO schema in PDB <code>pdb1<\/code>. <code>srv2<\/code> (192.168.0.181) ejecuta Ubuntu con PostgreSQL 18 y ora2pg instalado. Todos los comandos a continuaci\u00f3n se ejecutan en <code>srv2<\/code> a menos que se indique lo contrario.<\/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-gather-oracle-statistics\">Step 1 \u2014 Gather Oracle statistics<\/h3>\n\n\n\n<p>Run on <code>srv1<\/code> before generating the report. ora2pg's effort estimates use Oracle's stored statistics \u2014 stale stats produce inaccurate counts.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- On Oracle (srv1)\nBEGIN\n  DBMS_STATS.GATHER_SCHEMA_STATS(&#039;CO&#039;);\n  DBMS_STATS.GATHER_DICTIONARY_STATS;\nEND;\n\/\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-2-create-the-output-directory\">Paso 2: cree el directorio de salida<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nmkdir -p \/home\/fernando\/ora2pg-co\/output\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-3-base-ora2pg-conf\">Step 3 \u2014 Base ora2pg.conf<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# \/etc\/ora2pg\/ora2pg.conf\nORACLE_DSN    dbi:Oracle:host=192.168.0.180;service_name=pdb1;port=1521\nORACLE_USER   system\nORACLE_PWD    &lt;system password&gt;\nSCHEMA        CO\nEXPORT_SCHEMA 1\nCREATE_SCHEMA 1\nOUTPUT_DIR    \/home\/fernando\/ora2pg-co\/output\n<\/pre><\/div>\n\n\n<p>Utilice <code>sistema<\/code>, no <code>co<\/code> \u2014 la exportaci\u00f3n de COPIA dice <code>v$base de datos<\/code>, which requires DBA privilege.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-4-generate-the-assessment-report\">Paso 4: Generar el informe de evaluaci\u00f3n<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t SHOW_REPORT --estimate_cost --dump_as_html \\\n  &gt; \/home\/fernando\/ora2pg-co\/output\/report-co.html\n<\/pre><\/div>\n\n\n<p>Open the HTML in a browser. CO should score <strong>A-3<\/strong>. The cost drivers are the three non-portable views.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-5-analyse-column-types\">Paso 5 \u2014 Analizar tipos de columna<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t SHOW_COLUMN &gt; \/home\/fernando\/ora2pg-co\/output\/columns-co.txt\n<\/pre><\/div>\n\n\n<p>Key columns to check:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Column<\/th><th>ora2pg default<\/th><th>Correct type<\/th><th>Reason<\/th><\/tr><\/thead><tbody><tr><td><code>PRODUCTS.UNIT_PRICE<\/code><\/td><td><code>doble precisi\u00f3n<\/code><\/td><td><code>num\u00e9rico<\/code><\/td><td>Monetary \u2014 floating point causes rounding errors<\/td><\/tr><tr><td><code>ORDER_ITEMS.UNIT_PRICE<\/code><\/td><td><code>doble precisi\u00f3n<\/code><\/td><td><code>num\u00e9rico<\/code><\/td><td>Mismo<\/td><\/tr><tr><td><code>PRODUCTS.PRODUCT_DETAILS<\/code><\/td><td><code>bytea<\/code><\/td><td><code>jsonb<\/code><\/td><td>Stores JSON<\/td><\/tr><tr><td><code>ORDERS.CUSTOMER_ID<\/code><\/td><td><code>num\u00e9rico(38)<\/code><\/td><td><code>entero largo<\/code><\/td><td>FK to <code>entero largo<\/code> PK \u2014 type mismatch<\/td><\/tr><tr><td>All other <code>N\u00daMERO(38)<\/code> FK\/integer columns<\/td><td><code>num\u00e9rico(38)<\/code><\/td><td><code>entero largo<\/code> o <code>entero<\/code><\/td><td>Same reason<\/td><\/tr><tr><td><code>STORES.LATITUDE<\/code>, <code>STORES.LONGITUDE<\/code><\/td><td><code>doble precisi\u00f3n<\/code><\/td><td><code>doble precisi\u00f3n<\/code><\/td><td>Geographic coordinates \u2014 float is correct<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-6-add-modify_type-and-re-save-the-config\">Paso 6: A\u00f1adir MODIFY_TYPE y volver a guardar la configuraci\u00f3n<\/h3>\n\n\n\n<p>All overrides on a single line \u2014 <code>MODIFICAR_TIPO<\/code> ignora todo despu\u00e9s de la primera l\u00ednea. <\/p>\n\n\n\n<p>Do not use type precision (write <code>num\u00e9rico<\/code>, no <code>numeric(10,2)<\/code>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE   PRODUCTS:PRODUCT_DETAILS:jsonb,PRODUCTS:UNIT_PRICE:numeric,ORDER_ITEMS:UNIT_PRICE:numeric,INVENTORY:STORE_ID:bigint,INVENTORY:PRODUCT_ID:bigint,INVENTORY:PRODUCT_INVENTORY:integer,ORDERS:CUSTOMER_ID:bigint,ORDERS:STORE_ID:bigint,ORDER_ITEMS:ORDER_ID:bigint,ORDER_ITEMS:LINE_ITEM_ID:integer,ORDER_ITEMS:PRODUCT_ID:bigint,ORDER_ITEMS:QUANTITY:integer,ORDER_ITEMS:SHIPMENT_ID:bigint,SHIPMENTS:STORE_ID:bigint,SHIPMENTS:CUSTOMER_ID:bigint\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-7-run-the-schema-exports\">Step 7 \u2014 Run the schema exports<\/h3>\n\n\n\n<p>CO has no explicit sequences and no stored procedures or triggers. <\/p>\n\n\n\n<p>Only TABLE and VIEW exports are needed.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t TABLE -o CO_tables.sql 2&gt;&amp;1 | tee \/home\/fernando\/ora2pg-co\/output\/table-export-co.log\nora2pg -t VIEW  -o CO_views.sql  2&gt;&amp;1 | tee \/home\/fernando\/ora2pg-co\/output\/view-export-co.log\n<\/pre><\/div>\n\n\n<p>After the TABLE export, verify the critical columns:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ngrep -E &#039;product_details|UNIT_PRICE|IDENTITY&#039; \/home\/fernando\/ora2pg-co\/output\/CO_tables.sql\n<\/pre><\/div>\n\n\n<p><code>product_details<\/code> must show <code>JSONB<\/code>. Both <code>UNIT_PRICE<\/code> columns must show <code>NUM\u00c9RICO<\/code>. IDENTITY columns must show <code>GENERATED BY DEFAULT AS IDENTITY<\/code>. <\/p>\n\n\n\n<p>If any column shows the wrong type, the <code>MODIFICAR_TIPO<\/code> directive did not take effect \u2014 check the config.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-8-write-the-manual-fix-files\">Paso 8 \u2014 Escribir los archivos de correcci\u00f3n manual<\/h3>\n\n\n\n<p>Two fix files cannot be generated from the ora2pg output.<\/p>\n\n\n\n<p><code>CO_tables_fixed.sql<\/code> \u2014 removes the IS JSON constraint:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncp \/home\/fernando\/ora2pg-co\/output\/CO_tables.sql \\\n   \/home\/fernando\/ora2pg-co\/output\/CO_tables_fixed.sql\nsed -i &#039;\/products_json_c\/d&#039; \/home\/fernando\/ora2pg-co\/output\/CO_tables_fixed.sql\n<\/pre><\/div>\n\n\n<p><code>CO_views_fixed.sql<\/code> \u2014 rewrites the three Oracle-specific views:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSET search_path = co,public;\n\n-- product_orders: standard SQL, no changes needed\nCREATE OR REPLACE VIEW product_orders AS\n  SELECT p.product_name, o.order_status,\n         SUM(oi.quantity * oi.unit_price) total_sales,\n         COUNT(*) order_count\n  FROM   orders o\n  JOIN   order_items oi ON o.order_id = oi.order_id\n  JOIN   customers c    ON o.customer_id = c.customer_id\n  JOIN   products p     ON oi.product_id = p.product_id\n  GROUP  BY p.product_name, o.order_status;\n\n-- customer_order_products: LISTAGG \u2192 STRING_AGG\nCREATE OR REPLACE VIEW customer_order_products AS\n  SELECT o.order_id, o.order_tms, o.order_status,\n         c.customer_id, c.email_address, c.full_name,\n         SUM(oi.quantity * oi.unit_price) order_total,\n         STRING_AGG(p.product_name, &#039;, &#039; ORDER BY oi.line_item_id) items\n  FROM   orders o\n  JOIN   order_items oi ON o.order_id = oi.order_id\n  JOIN   customers c    ON o.customer_id = c.customer_id\n  JOIN   products p     ON oi.product_id = p.product_id\n  GROUP  BY o.order_id, o.order_tms, o.order_status,\n            c.customer_id, c.email_address, c.full_name;\n\n-- store_orders: GROUPING_ID() \u2192 GROUPING()\nCREATE OR REPLACE VIEW store_orders AS\n  SELECT CASE grouping(s.store_name, o.order_status)\n           WHEN 1 THEN &#039;STORE TOTAL&#039;\n           WHEN 2 THEN &#039;STATUS TOTAL&#039;\n           WHEN 3 THEN &#039;GRAND TOTAL&#039;\n         END total,\n         s.store_name,\n         COALESCE(s.web_address, s.physical_address) address,\n         s.latitude, s.longitude,\n         o.order_status,\n         COUNT(DISTINCT o.order_id) order_count,\n         SUM(oi.quantity * oi.unit_price) total_sales\n  FROM   stores s\n  JOIN   orders o    ON s.store_id = o.store_id\n  JOIN   order_items oi ON o.order_id = oi.order_id\n  GROUP  BY GROUPING SETS (\n    (s.store_name, COALESCE(s.web_address, s.physical_address), s.latitude, s.longitude),\n    (s.store_name, COALESCE(s.web_address, s.physical_address), s.latitude, s.longitude, o.order_status),\n    o.order_status,\n    ()\n  );\n\n-- product_reviews: JSON_TABLE \u2192 jsonb_array_elements\nCREATE OR REPLACE VIEW product_reviews AS\n  SELECT p.product_name,\n         (review_row-&gt;&gt;&#039;rating&#039;)::INTEGER AS rating,\n         ROUND(\n           AVG((review_row-&gt;&gt;&#039;rating&#039;)::INTEGER)\n             OVER (PARTITION BY p.product_name),\n           2\n         ) avg_rating,\n         review_row-&gt;&gt;&#039;review&#039; AS review\n  FROM   products p,\n         jsonb_array_elements(p.product_details-&gt;&#039;reviews&#039;) AS review_row;\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-9-export-the-data\">Step 9 \u2014 Export the data<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t COPY -o CO_data.sql 2&gt;&amp;1 | tee \/home\/fernando\/ora2pg-co\/output\/copy-export-co.log\n<\/pre><\/div>\n\n\n<p>After the export, check for embedded newlines in the JSON data. ora2pg does not escape literal newline characters inside JSON string values. <\/p>\n\n\n\n<p>If any <code>product_details<\/code> value contains an actual newline, the COPY parser treats it as a row separator and fails with <code>invalid input syntax for type json<\/code>.<\/p>\n\n\n\n<p>Fix embedded newlines before loading:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\npython3 &lt;&lt; &#039;EOF&#039;\nimport re\n\nwith open(&#039;\/home\/fernando\/ora2pg-co\/output\/CO_data.sql&#039;, &#039;r&#039;) as f:\n    lines = f.read().split(&#039;\\n&#039;)\n\nresult = &#x5B;]\nin_products = False\npending = None\n\nfor line in lines:\n    if re.match(r&#039;COPY products\\b&#039;, line):\n        in_products = True\n        result.append(line)\n        continue\n    if in_products:\n        if line == &#039;\\\\.&#039;:\n            if pending is not None:\n                result.append(pending)\n                pending = None\n            result.append(line)\n            in_products = False\n            continue\n        if re.match(r&#039;^\\d+\\t&#039;, line):\n            if pending is not None:\n                result.append(pending)\n            pending = line\n        else:\n            pending = (pending + &#039;\\\\n&#039; + line) if pending else line\n    else:\n        result.append(line)\n\nwith open(&#039;\/home\/fernando\/ora2pg-co\/output\/CO_data_fixed.sql&#039;, &#039;w&#039;) as f:\n    f.write(&#039;\\n&#039;.join(result))\nprint(&quot;Done&quot;)\nEOF\n<\/pre><\/div>\n\n\n<p>Verify the row count is correct:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ngrep -c $&#039;\\t&#039; \/home\/fernando\/ora2pg-co\/output\/CO_data_fixed.sql\n# Expected: 8783\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-10-create-the-database\">Step 10 \u2014 Create the database<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;CREATE ROLE co WITH LOGIN PASSWORD &#039;co&#039;;&quot;\nsudo -u postgres psql -c &quot;CREATE DATABASE codb OWNER co;&quot;\nPGPASSWORD=co psql -U co -d codb -h localhost -c &quot;CREATE SCHEMA co;&quot;\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-11-load-in-dependency-order\">Step 11 \u2014 Load in dependency order<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Tables first\nPGPASSWORD=co psql -U co -d codb -h localhost \\\n  -f \/home\/fernando\/ora2pg-co\/output\/CO_tables_fixed.sql\n\n# Data \u2014 FKs are dropped inside the file before load, not re-added after\nPGPASSWORD=co psql -U co -d codb -h localhost \\\n  -f \/home\/fernando\/ora2pg-co\/output\/CO_data_fixed.sql\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-12-re-apply-fk-constraints\">Step 12 \u2014 Re-apply FK constraints<\/h3>\n\n\n\n<p>ora2pg's COPY file drops all FK constraints before the data load and never re-adds them.<\/p>\n\n\n\n<p>This is the same bug as in the HR lab (<a href=\"https:\/\/github.com\/darold\/ora2pg\/issues\" rel=\"nofollow noopener\" target=\"_blank\">issue #1960<\/a>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(echo &quot;SET search_path TO co;&quot;; \\\n grep &#039;ADD CONSTRAINT.*FOREIGN KEY&#039; \\\n   \/home\/fernando\/ora2pg-co\/output\/CO_tables_fixed.sql) | \\\n  PGPASSWORD=co psql -U co -d codb -h localhost\n<\/pre><\/div>\n\n\n<p>Verifica el conteo:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=co psql -U co -d codb -h localhost -c &quot;\nSELECT COUNT(*) FROM information_schema.table_constraints\nWHERE constraint_schema = &#039;co&#039; AND constraint_type = &#039;FOREIGN KEY&#039;;&quot;\n# Expected: 9\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-13-advance-identity-sequences\">Step 13 \u2014 Advance IDENTITY sequences<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=co psql -U co -d codb -h localhost &lt;&lt; &#039;EOF&#039;\nSET search_path = co, public;\nSELECT setval(pg_get_serial_sequence(&#039;co.customers&#039;,  &#039;customer_id&#039;),  (SELECT MAX(customer_id)  FROM co.customers));\nSELECT setval(pg_get_serial_sequence(&#039;co.orders&#039;,     &#039;order_id&#039;),     (SELECT MAX(order_id)     FROM co.orders));\nSELECT setval(pg_get_serial_sequence(&#039;co.shipments&#039;,  &#039;shipment_id&#039;),  (SELECT MAX(shipment_id)  FROM co.shipments));\nSELECT setval(pg_get_serial_sequence(&#039;co.stores&#039;,     &#039;store_id&#039;),     (SELECT MAX(store_id)     FROM co.stores));\nSELECT setval(pg_get_serial_sequence(&#039;co.products&#039;,   &#039;product_id&#039;),   (SELECT MAX(product_id)   FROM co.products));\nEOF\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-14-load-the-views\">Step 14 \u2014 Load the views<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=co psql -U co -d codb -h localhost \\\n  -f \/home\/fernando\/ora2pg-co\/output\/CO_views_fixed.sql\n# Expected: CREATE VIEW \u00d7 4 \u2014 no errors\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-15-run-the-test-comparison\">Step 15 \u2014 Run the TEST comparison<\/h3>\n\n\n\n<p>Add PostgreSQL connection to <code>ora2pg.conf<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPG_DSN    dbi:Pg:dbname=codb;host=localhost;port=5432\nPG_USER   co\nPG_PWD    co\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t TEST 2&gt;&amp;1 | tee \/home\/fernando\/ora2pg-co\/output\/CO_test.txt\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"what-the-final-test-report-looks-like\">C\u00f3mo se ve el Informe de Prueba Final<\/h3>\n\n\n\n<p>Despu\u00e9s de aplicar las cinco correcciones, <code>ora2pg -t TEST<\/code> should show one DIFF and everything else OK.<\/p>\n\n\n\n<p><strong>CHECK CONSTRAINTS DIFF on <code>products<\/code><\/strong> \u2014 Oracle has one check constraint (<code>products_json_c<\/code>), PostgreSQL has zero. <\/p>\n\n\n\n<p>This is expected: the constraint was intentionally removed because it uses Oracle syntax and is redundant with <code>JSONB<\/code> column type validation.<\/p>\n\n\n\n<p>Every other section \u2014 columns, indexes, PKs, FKs, tables, views, sequences, row counts \u2014 should show OK.<\/p>\n\n\n\n<p>Any other DIFF line is a real problem that must be resolved before cutover.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"summary\">En resumen<\/h2>\n\n\n\n<p>CO is a cleaner migration than HR or SH in some ways \u2014 no stored procedures, no triggers, no bitmap indexes, no partitioned tables. The problems it introduces are different in character.<\/p>\n\n\n\n<p>IDENTITY sequences failing after a data load is invisible until the first INSERT in production \u2014 there is no warning during the migration itself. The <code>setval<\/code> step is easy to miss because the load succeeds cleanly without it.<\/p>\n\n\n\n<p>The BLOB-as-JSON pattern is common in Oracle databases built before 21c, where there was no native JSON type. Every Oracle schema that stores JSON needs the same MODIFY_TYPE override and the same IS JSON constraint removal. The view rewrites are one-time manual work, but the <code>STRING_AGG<\/code>, <code>GROUPING<\/code>y <code>jsonb_array_elements<\/code> substitutions are patterns you will see again on any Oracle schema that has analytical or JSON-querying views.<\/p>\n\n\n\n<p>The FK type mismatch from <code>N\u00daMERO(38)<\/code> columns is a systematic problem. <code>PG_INTEGER_TYPE 1<\/code> handles bare <code>N\u00daMERO<\/code> columns correctly, but anything declared with precision \u2014 even <code>N\u00daMERO(38)<\/code>, which is how Oracle represents a plain integer in its data dictionary \u2014 falls outside the rule. On a production schema, the SHOW_COLUMN output will contain many such columns. Each one needs a MODIFY_TYPE entry before the TABLE export.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><em>Si usted est\u00e1 planeando una migraci\u00f3n de Oracle a PostgreSQL y desea una evaluaci\u00f3n independiente de la complejidad, el esfuerzo y el riesgo antes de comprometerse a un cronograma, <a href=\"https:\/\/rootfan.com\/es\/servicios\/\">Ofrezco una auditor\u00eda de migraci\u00f3n a precio fijo<\/a> que produce exactamente eso.<\/em><\/p>","protected":false},"excerpt":{"rendered":"<p>Oracle&#8217;s CO (Customer Orders) schema is the modern replacement for the older OE schema. It ships with Oracle 19c, it is actively maintained, and it is built the way most real Oracle applications are built today: IDENTITY columns instead of sequence-trigger pairs, JSON stored in BLOB columns, and views that use Oracle-specific SQL functions. I &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/rootfan.com\/es\/oracle-to-postgres-migration-issues-and-fixes\/\" class=\"more-link\">Seguir leyendo<span class=\"screen-reader-text\"> &#8220;Oracle to Postgres Migration: CO Schema Issues and Fixes&#8221;<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":6772,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_focus_keyword":"oracle to postgres migration issues","rank_math_title":"","rank_math_description":"Oracle to PostgreSQL migration issues and fixes for the CO schema: IDENTITY sequences, BLOB-to-JSONB conversion, NUMBER(38) FK type mismatches, IS JSON constraint removal, and Oracle view rewrites.","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":[155,143,141,157,156,137,147],"class_list":["post-6754","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oracle-to-postgresql","tag-co-schema","tag-data-migration","tag-data-types","tag-identity-columns","tag-jsonb","tag-migration","tag-ora2pg"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rootfan.com\/wp-content\/uploads\/6151061591_1726931f23_b-1.jpg?fit=1024%2C683&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6754","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=6754"}],"version-history":[{"count":9,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6754\/revisions"}],"predecessor-version":[{"id":6777,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6754\/revisions\/6777"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media\/6772"}],"wp:attachment":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media?parent=6754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/categories?post=6754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/tags?post=6754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}