{"id":6751,"date":"2026-04-20T13:19:11","date_gmt":"2026-04-20T11:19:11","guid":{"rendered":"https:\/\/rootfan.com\/?p=6751"},"modified":"2026-04-20T14:29:39","modified_gmt":"2026-04-20T12:29:39","slug":"ejemplo-de-migracion-con-ora2pg","status":"publish","type":"post","link":"https:\/\/rootfan.com\/es\/ora2pg-migration-example\/","title":{"rendered":"Ejemplo de migraci\u00f3n de ora2pg: Esquema HR de Oracle a PostgreSQL"},"content":{"rendered":"<p>El esquema HR de Oracle es lo m\u00e1s parecido que tiene el mundo de las bases de datos a una referencia universal.<\/p>\n\n\n\n<p>Todo DBA de Oracle lo ha visto. <\/p>\n\n\n\n<p>Todo consultor de migraci\u00f3n lo utiliza para explicar c\u00f3mo funciona el proceso.<\/p>\n\n\n\n<p>Lo que lo hace verdaderamente \u00fatil como caso de estudio no es su tama\u00f1o \u2014 son siete mesas y 107 empleados. <\/p>\n\n\n\n<p>Lo que lo hace \u00fatil es que contiene, en forma m\u00ednima, los patrones exactos que hacen triviales las migraciones de Oracle a PostgreSQL: secuencias conectadas a disparadores, procedimientos almacenados, discrepancias de tipos que compilan limpiamente y fallan en tiempo de ejecuci\u00f3n.<\/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 las cinco cosas que requirieron intervenci\u00f3n manual \u2014 y por qu\u00e9 cada una de ellas importa mucho m\u00e1s en un esquema de producci\u00f3n que en una demostraci\u00f3n.<\/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-hr-contains\">\u00bfQu\u00e9 contiene RR. HH.?<\/a><ul><li><a href=\"#1-number-type-mapping-the-monetary-column-trap\">1. Mapeo de Tipos NUM\u00c9RICOS: La Trampa de la Columna Monetaria<\/a><\/li><li><a href=\"#2-the-sequence-trigger-pattern\">2. El Patr\u00f3n del Detector de Secuencia<\/a><\/li><li><a href=\"#3-type-in-procedure-parameter-signatures\">3. %TIPO en Firmas de Par\u00e1metros de Procedimiento<\/a><\/li><li><a href=\"#4-the-statement_timestamp-type-mismatch\">4. Incompatibilidad de tipos de statement_timestamp()<\/a><\/li><li><a href=\"#5-fk-constraints-are-not-re-applied-after-data-load\">5. Las restricciones FK no se vuelven a aplicar despu\u00e9s de la carga de datos<\/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=\"#environment\">Medio ambiente<\/a><\/li><li><a href=\"#step-1-collect-oracle-statistics\">Paso 1 \u2014 Recopilar estad\u00edsticas de Oracle<\/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-minimal-ora2pg-conf\">Paso 3 \u2014 ora2pg.conf m\u00ednimo<\/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-exports\">Paso 7 \u2014 Ejecutar las exportaciones<\/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-create-the-database\">Paso 9 \u2014 Crear la base de datos<\/a><\/li><li><a href=\"#step-10-load-in-dependency-order\">Paso 10: Cargar en orden de dependencia<\/a><\/li><li><a href=\"#step-11-re-apply-fk-constraints\">Paso 11 \u2014 Volver a aplicar restricciones FK<\/a><\/li><li><a href=\"#step-12-load-the-remaining-files\">Paso 12 \u2014 Carga los archivos restantes<\/a><\/li><li><a href=\"#step-13-static-analysis-with-plpgsql_check\">Paso 13 \u2014 An\u00e1lisis est\u00e1tico con plpgsql_check<\/a><\/li><li><a href=\"#step-14-run-the-test-comparison\">Paso 14 \u2014 Ejecutar la comparaci\u00f3n de PRUEBA<\/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-hr-contains\">\u00bfQu\u00e9 contiene RR. HH.?<\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/oracle-samples\/db-sample-schemas\/tree\/main\/human_resources\" rel=\"nofollow noopener\" target=\"_blank\">El esquema de RR. HH.<\/a> es el esquema de referencia est\u00e1ndar de Oracle, incluido con cada instalaci\u00f3n de Oracle y disponible en GitHub. <\/p>\n\n\n\n<p>Tiene siete tablas que cubren la jerarqu\u00eda est\u00e1ndar de RR. HH.: regiones, pa\u00edses, ubicaciones, departamentos, puestos, empleados e historial laboral. <\/p>\n\n\n\n<p>Tiene tres secuencias independientes (<code>secuencia_empleados<\/code>, <code>departamentos_seq<\/code>, <code>locations_seq<\/code>), una vista (<code>DETALLES_EMPLEADO_VER<\/code>), dos procedimientos almacenados (<code>dml_seguro<\/code>, <code>agregar_historial_laboral<\/code>), y cinco activadores. <\/p>\n\n\n\n<p>Tres de esos desencadenadores existen \u00fanicamente para llamar <code>sequence.NEXTVAL<\/code> en INSERT \u2014 un patr\u00f3n que los desarrolladores de Oracle usaban anteriormente <code>DEFAULT<\/code> con secuencias fue ampliamente adoptado. <\/p>\n\n\n\n<p>Los dos restantes implementan la l\u00f3gica de negocio: uno restringe las operaciones DML al horario de oficina, el otro escribe una fila de historial cuando un empleado cambia de puesto.<\/p>\n\n\n\n<p>ora2pg califica el esquema HR <strong>B-5<\/strong>: se requiere reescritura de c\u00f3digo, aproximadamente un d\u00eda-persona para un experto en PostgreSQL. <\/p>\n\n\n\n<p>La calificaci\u00f3n est\u00e1 impulsada completamente por los procedimientos almacenados y los triggers. <\/p>\n\n\n\n<p>Las tablas, vistas y datos migran sin intervenci\u00f3n.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1-number-type-mapping-the-monetary-column-trap\">1. Mapeo de Tipos NUM\u00c9RICOS: La Trampa de la Columna Monetaria<\/h3>\n\n\n\n<p>ora2pg tiene dos directivas que manejan la mayor parte <code>N\u00daMERO<\/code> columnas autom\u00e1ticamente.<\/p>\n\n\n\n<p><code>PG_INTEGER_TYPE 1<\/code> mapas <code>NUM\u00c9RICO(p)<\/code> columnas sin escala al tipo entero correcto: la precisi\u00f3n 1\u20134 se convierte en <code>smallint<\/code>, 5\u20139 se convierte en <code>entero<\/code>, 10\u201318 <code>entero largo<\/code>. <\/p>\n\n\n\n<p>Para el esquema de RR. HH. esto significa <code>ID_EMPLEADO NUMBER(6)<\/code> se convierte <code>ENTERO<\/code>, <code>DEPARTMENT_ID N\u00daMERO(4)<\/code> se convierte <code>INT PEQUE\u00d1O<\/code> \u2014 ning\u00fan trabajo manual necesario.<\/p>\n\n\n\n<p>La trampa es <code>TIPO_NUM\u00c9RICO_PG 1<\/code>, que maneja columnas que tienen una escala declarada. <\/p>\n\n\n\n<p>Esta directiva no solo mira el tipo declarado \u2013 consulta los valores de datos reales. <\/p>\n\n\n\n<p>Si a <code>NUMERO(8,2)<\/code> la columna actualmente solo contiene valores de n\u00fameros enteros, mapea la columna a <code>doble precisi\u00f3n<\/code> en lugar de <code>num\u00e9rico<\/code>.<\/p>\n\n\n\n<p><code>EMPLEADOS.SALARIO<\/code> es <code>NUMERO(8,2)<\/code> en Oracle. <\/p>\n\n\n\n<p>Cada salario en el conjunto de datos de RR.\u00a0HH.\u00a0es un n\u00famero entero:\u00a024000, 17000, 9000. Con <code>TIPO_NUM\u00c9RICO_PG 1<\/code> activo, ora2pg mapas <code>SUELDO<\/code> a <code>doble precisi\u00f3n<\/code>.<\/p>\n\n\n\n<p><code>doble precisi\u00f3n<\/code> es un tipo de punto flotante. <\/p>\n\n\n\n<p>Los tipos de punto flotante son incorrectos para los valores monetarios, ya que no pueden representar todas las fracciones decimales exactamente, lo que produce errores de redondeo en los c\u00e1lculos financieros. <\/p>\n\n\n\n<p>El tipo correcto para el salario es <code>num\u00e9rico<\/code>.<\/p>\n\n\n\n<p>La soluci\u00f3n es un <code>MODIFICAR_TIPO<\/code> anular<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE    EMPLOYEES:SALARY:numeric\n<\/pre><\/div>\n\n\n<p>En cada migraci\u00f3n real de producci\u00f3n que eval\u00fao, las columnas de salario y otras columnas monetarias similares requieren esta anulaci\u00f3n. <\/p>\n\n\n\n<p>En <code>PG_NUMERIC_TYPE<\/code> la directiva est\u00e1 haciendo su trabajo correctamente: est\u00e1 infiriendo tipos a partir de datos. <\/p>\n\n\n\n<p>La inferencia es simplemente incorrecta para el dinero, donde la precisi\u00f3n declarada importa m\u00e1s que lo que contienen los datos actuales.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-the-sequence-trigger-pattern\">2. El Patr\u00f3n del Detector de Secuencia<\/h3>\n\n\n\n<p>En Oracle, el patr\u00f3n est\u00e1ndar para claves primarias autoincrementales era:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Crea una secuencia independiente.<\/li>\n\n\n\n<li>Crear un disparador BEFORE INSERT que se active para cada fila y asigne <code>sequence.NEXTVAL<\/code> a la columna ID.<\/li>\n<\/ol>\n\n\n\n<p>As\u00ed se construye Recursos Humanos. <code>EMPLEADOS.ID_EMPLEADO<\/code> no tiene <code>DEFAULT<\/code> cl\u00e1usula en Oracle \u2014 el disparador la rellena en cada INSERT.<\/p>\n\n\n\n<p>En PostgreSQL, el equivalente es una columna <code>DEFAULT nextval('sequence_name')<\/code>. <\/p>\n\n\n\n<p>Cuando se inserta una fila sin especificar la columna ID, PostgreSQL llama <code>nextval()<\/code> autom\u00e1ticamente. No se necesita ning\u00fan desencadenador.<\/p>\n\n\n\n<p>ora2pg convierte los triggers de secuencia de Oracle; los exporta como funciones de trigger de PostgreSQL. <\/p>\n\n\n\n<p>Pero la exportaci\u00f3n de TABLA no a\u00f1ade <code>nextval(...) por omisi\u00f3n<\/code> a las definiciones de columna, porque Oracle nunca tuvo una. Despu\u00e9s de cargar el DDL exportado, las tablas no tienen generaci\u00f3n de ID autom\u00e1tica. Cualquier INSERT sin una inserci\u00f3n expl\u00edcita <code>id_empleado<\/code> fallar\u00e1 con <code>el valor nulo en la columna viola la restricci\u00f3n de no nulidad<\/code>.<\/p>\n\n\n\n<p>La soluci\u00f3n es a\u00f1adir los valores predeterminados despu\u00e9s de que se carguen los desencadenadores:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nALTER TABLE hr.employees   ALTER COLUMN employee_id   SET DEFAULT nextval(&#039;hr.employees_seq&#039;);\nALTER TABLE hr.departments ALTER COLUMN department_id SET DEFAULT nextval(&#039;hr.departments_seq&#039;);\nALTER TABLE hr.locations   ALTER COLUMN location_id   SET DEFAULT nextval(&#039;hr.locations_seq&#039;);\n<\/pre><\/div>\n\n\n<p>Tambi\u00e9n enlaza cada secuencia a su columna con <code>PROPIEDAD DE<\/code> para que al eliminar la tabla se elimine autom\u00e1ticamente la secuencia. <\/p>\n\n\n\n<p>Sin <code>PROPIEDAD DE<\/code>, las secuencias se convierten en hu\u00e9rfanas que deben desecharse por separado.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-type-in-procedure-parameter-signatures\">3. %TIPO en Firmas de Par\u00e1metros de Procedimiento<\/h3>\n\n\n\n<p><code>agregar_historial_laboral<\/code> es exportado por ora2pg con la sintaxis de tipo anclado de Oracle en su lista de par\u00e1metros:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- What ora2pg exports\nCREATE OR REPLACE PROCEDURE hr.add_job_history (\n    p_emp_id        job_history.employee_id%TYPE,\n    p_start_date    job_history.start_date%TYPE,\n    ...\n<\/pre><\/div>\n\n\n<p>PostgreSQL soporta <code>%TIPO<\/code> dentro del cuerpo de una funci\u00f3n o procedimiento para declaraciones de variables. No admite <code>%TIPO<\/code> en la firma de par\u00e1metros. <\/p>\n\n\n\n<p>La carga del archivo exportado falla inmediatamente con un error de sintaxis.<\/p>\n\n\n\n<p>La soluci\u00f3n es sustituir los tipos concretos:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE OR REPLACE PROCEDURE hr.add_job_history (\n    p_emp_id        integer,\n    p_start_date    timestamp,\n    p_end_date      timestamp,\n    p_job_id        varchar(10),\n    p_department_id smallint\n)\n<\/pre><\/div>\n\n\n<p>Los tipos correctos provienen del an\u00e1lisis de columnas realizado antes de la migraci\u00f3n. <code>EMPLOYEE_ID<\/code> es <code>ENTERO<\/code>, <code>FECHA_INICIO<\/code> es <code>MARCA DE TIEMPO<\/code>, y as\u00ed sucesivamente. <\/p>\n\n\n\n<p>Por eso el paso de an\u00e1lisis de tipos no es opcional: necesitas los tipos finales de PostgreSQL para escribir esta correcci\u00f3n.<\/p>\n\n\n\n<p>En un esquema de producci\u00f3n con docenas de procedimientos almacenados, este patr\u00f3n aparece con frecuencia. <\/p>\n\n\n\n<p>Cada procedimiento que utiliza par\u00e1metros de tipo anclado en Oracle necesita la misma sustituci\u00f3n manual antes de que pueda cargarse en PostgreSQL.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"4-the-statement_timestamp-type-mismatch\">4. Incompatibilidad de tipos de statement_timestamp()<\/h3>\n\n\n\n<p>En <code>actualizar_historial_laboral<\/code> detonar llamadas <code>agregar_historial_laboral<\/code> escribir una fila de historial cuando un empleado cambia de puesto. <\/p>\n\n\n\n<p>Necesita pasar la hora actual como el <code>fecha_fin<\/code> argumento.<\/p>\n\n\n\n<p>ora2pg convierte Oracle <code>FECHA_SISTEMA<\/code> a <code>statement_timestamp()<\/code>. <code>statement_timestamp()<\/code> devoluciones <code>marca de tiempo con zona horaria<\/code> en PostgreSQL. <code>agregar_historial_laboral<\/code> espera <code>marca de tiempo<\/code> (sin zona horaria) para <code>fecha_fin<\/code>.<\/p>\n\n\n\n<p>PostgreSQL no realiza conversiones impl\u00edcitas <code>timestamptz<\/code> a <code>marca de tiempo<\/code> en llamadas a procedimientos. <\/p>\n\n\n\n<p>La funci\u00f3n de activaci\u00f3n se carga sin errores. <code>CREAR FUNCI\u00d3N<\/code> tiene \u00e9xito. <\/p>\n\n\n\n<p>La incompatibilidad de tipos solo se manifiesta en tiempo de ejecuci\u00f3n, la primera vez que un empleado cambia de puesto o departamento.<\/p>\n\n\n\n<p>Esta es la categor\u00eda m\u00e1s insidiosa de errores de migraci\u00f3n: c\u00f3digo que compila limpiamente y falla en producci\u00f3n.<\/p>\n\n\n\n<p>Yo uso <code>plpgsql_check<\/code> para capturar exactamente esta clase de error. <\/p>\n\n\n\n<p>Realiza un an\u00e1lisis est\u00e1tico de los cuerpos de funciones compiladas y reporta incompatibilidades de tipos, variables faltantes y llamadas a procedimientos inv\u00e1lidas, todo antes de cualquier prueba de aplicaci\u00f3n.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCREATE EXTENSION plpgsql_check;\n<\/pre><\/div>\n\n\n<p>Ejecutar plpgsql_check contra la funci\u00f3n disparador en este laboratorio produjo:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ntrigger_fct_update_job_history() | error | procedure add_job_history(integer,\ntimestamp without time zone, timestamp with time zone, character varying, smallint)\ndoes not exist\n<\/pre><\/div>\n\n\n<p>La soluci\u00f3n es un solo molde:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCALL add_job_history(OLD.employee_id, OLD.hire_date, statement_timestamp()::TIMESTAMP,\n                     OLD.job_id, OLD.department_id);\n<\/pre><\/div>\n\n\n<p><code>plpgsql_check<\/code> no es una parte est\u00e1ndar del flujo de trabajo de ora2pg \u2014 no se ejecuta por defecto, no se menciona en la documentaci\u00f3n de ora2pg, y no se incluye en ning\u00fan paquete de PostgreSQL por defecto. <\/p>\n\n\n\n<p>Lo ejecuto en cada migraci\u00f3n antes de entregar el c\u00f3digo migrado al equipo de desarrollo. Ha detectado errores en tiempo de ejecuci\u00f3n en cada esquema de producci\u00f3n que he migrado.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"5-fk-constraints-are-not-re-applied-after-data-load\">5. Las restricciones FK no se vuelven a aplicar despu\u00e9s de la carga de datos<\/h3>\n\n\n\n<p>La exportaci\u00f3n con COPY de ora2pg elimina todas las restricciones de clave externa al inicio del archivo de datos, carga los datos y luego confirma. <\/p>\n\n\n\n<p>No vuelve a a\u00f1adir las restricciones FK despu\u00e9s del commit. <\/p>\n\n\n\n<p>Este es un error conocido. <a href=\"https:\/\/github.com\/darold\/ora2pg\/issues\" rel=\"nofollow noopener\" target=\"_blank\">problema #1960 en el repositorio darold\/ora2pg<\/a>.<\/p>\n\n\n\n<p>Despu\u00e9s de cargar el archivo de datos, la base de datos migrada no tiene restricciones de FK. <\/p>\n\n\n\n<p>En <code>ora2pg -t TEST<\/code> La comparaci\u00f3n mostrar\u00e1 que todas las tablas tienen cero FKs en PostgreSQL, aunque las definiciones de FK existan en el DDL exportado.<\/p>\n\n\n\n<p>La soluci\u00f3n es volver a aplicar las restricciones FK de la exportaci\u00f3n de la tabla despu\u00e9s de la carga de datos:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ngrep &#039;ADD CONSTRAINT.*FOREIGN KEY&#039; HR_tables.sql | \\\n  PGPASSWORD=hr psql -U hr -d hrdb -h localhost\n<\/pre><\/div>\n\n\n<p>Para el esquema de RR. HH., esto restaura 10 restricciones de clave for\u00e1nea en cinco tablas. <\/p>\n\n\n\n<p>En un esquema de producci\u00f3n con cientos de tablas, el recuento de claves for\u00e1neas (FK) es un punto de control de validaci\u00f3n cr\u00edtico: si no coincide con el recuento de Oracle despu\u00e9s de la migraci\u00f3n, el modelo de datos est\u00e1 incompleto.<\/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) ejecuta Oracle 19c con el esquema HR en 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-collect-oracle-statistics\">Paso 1 \u2014 Recopilar estad\u00edsticas de Oracle<\/h3>\n\n\n\n<p>Ejecutar esto en <code>srv1<\/code> antes de generar el informe de evaluaci\u00f3n. las estimaciones de costos de ora2pg se basan en las estad\u00edsticas almacenadas de Oracle. las estad\u00edsticas obsoletas producen recuentos de filas y estimaciones de unidades de migraci\u00f3n inexactos.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- On Oracle (srv1)\nBEGIN\n  DBMS_STATS.GATHER_SCHEMA_STATS(&#039;HR&#039;);\n  DBMS_STATS.GATHER_DICTIONARY_STATS;\nEND;\n\/\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-2-create-the-output-directory\">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-hr\/output\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-3-minimal-ora2pg-conf\">Paso 3 \u2014 ora2pg.conf m\u00ednimo<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# \/etc\/ora2pg\/ora2pg.conf\nORACLE_DSN      dbi:Oracle:host=192.168.0.180;service_name=pdb1;port=1521\nORACLE_USER     system\nORACLE_PWD      &lt;system password&gt;\nSCHEMA          HR\nEXPORT_SCHEMA   1\nOUTPUT_DIR      \/home\/fernando\/ora2pg-hr\/output\n<\/pre><\/div>\n\n\n<p>Utilice <code>sistema<\/code>, no <code>RRHH<\/code> \u2014 la exportaci\u00f3n de COPIA dice <code>v$base de datos<\/code> que requiere privilegios de DBA.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-4-generate-the-assessment-report\">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-hr\/output\/report-hr.html\n<\/pre><\/div>\n\n\n<p>Abre el HTML en un navegador. <\/p>\n\n\n\n<p>Puntuaciones de RRHH <strong>B-5<\/strong>, aproximadamente una persona-d\u00eda. <\/p>\n\n\n\n<p>Los impulsores de costos son los dos procedimientos almacenados y dos disparadores de l\u00f3gica de negocio.<\/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-hr\/output\/columns-hr.txt\n<\/pre><\/div>\n\n\n<p>Luego consulta rangos de datos reales en Oracle para cualquier <code>N\u00daMERO<\/code> columna sin precisi\u00f3n declarada<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n-- On Oracle (srv1)\nSELECT &#039;REGIONS.REGION_ID&#039;   AS col, MIN(region_id), MAX(region_id) FROM regions\nUNION ALL\nSELECT &#039;COUNTRIES.REGION_ID&#039;,         MIN(region_id), MAX(region_id) FROM countries;\n<\/pre><\/div>\n\n\n<p>Ambos al m\u00e1ximo a 50 \u2014 <code>ENTERO<\/code> es la correcta asignaci\u00f3n.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-6-add-modify_type-and-re-save-the-config\">Paso 6: A\u00f1adir MODIFY_TYPE y volver a guardar la configuraci\u00f3n<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nMODIFY_TYPE    REGIONS:REGION_ID:integer,COUNTRIES:REGION_ID:integer,EMPLOYEES:SALARY:numeric\n<\/pre><\/div>\n\n\n<p><code>EMPLEADOS:SALARIO:num\u00e9rico<\/code> se requiere porque <code>TIPO_NUM\u00c9RICO_PG 1<\/code> inspecciona datos reales, encuentra solo valores de salario de n\u00fameros enteros y mapea <code>NUMERO(8,2)<\/code> a <code>doble precisi\u00f3n<\/code> \u2014 incorrecto para una columna monetaria. <\/p>\n\n\n\n<p>Las tres anulaciones en una sola l\u00ednea <code>MODIFICAR_TIPO<\/code> ignora todo despu\u00e9s de la primera l\u00ednea.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-7-run-the-exports\">Paso 7 \u2014 Ejecutar las exportaciones<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t TABLE           -o HR_tables.sql           2&gt;&amp;1 | tee output\/table-export-hr.log\nora2pg -t VIEW            -o HR_views.sql            2&gt;&amp;1 | tee output\/view-export-hr.log\nora2pg -t SEQUENCE        -o HR_sequences.sql        2&gt;&amp;1 | tee output\/sequence-export-hr.log\nora2pg -t SEQUENCE_VALUES -o HR_sequence_values.sql  2&gt;&amp;1 | tee output\/seqval-export-hr.log\nora2pg -t TRIGGER         -o HR_triggers.sql         2&gt;&amp;1 | tee output\/trigger-export-hr.log\nora2pg -t PROCEDURE       -o HR_procedures.sql       2&gt;&amp;1 | tee output\/procedure-export-hr.log\nora2pg -t COPY            -o HR_data.sql             2&gt;&amp;1 | tee output\/copy-export-hr.log\n<\/pre><\/div>\n\n\n<p>La exportaci\u00f3n de TABLA hace <strong>no<\/strong> incluir secuencias \u2014 la exportaci\u00f3n SEPARADA de SEQUENCE es obligatoria. Sin ella, la carga de sequence_values en el Paso 12 falla con el mensaje \u201cla relaci\u00f3n no existe\u201d.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-8-write-the-manual-fix-files\">Paso 8 \u2014 Escribir los archivos de correcci\u00f3n manual<\/h3>\n\n\n\n<p>Dos archivos que no se pueden generar de la salida de ora2pg.<\/p>\n\n\n\n<p><code>HR_procedimientos_fijos.sql<\/code> \u2014 reemplaza <code>%TIPO<\/code> par\u00e1metros anclados con tipos concretos:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSET search_path = hr,public;\n\nCREATE OR REPLACE PROCEDURE hr.add_job_history (\n    p_emp_id        integer,\n    p_start_date    timestamp,\n    p_end_date      timestamp,\n    p_job_id        varchar(10),\n    p_department_id smallint\n) AS $body$\nBEGIN\n  INSERT INTO job_history(employee_id, start_date, end_date, job_id, department_id)\n    VALUES (p_emp_id, p_start_date, p_end_date, p_job_id, p_department_id);\nEND;\n$body$\nLANGUAGE PLPGSQL SECURITY DEFINER;\n\nCREATE OR REPLACE PROCEDURE hr.secure_dml () AS $body$\nBEGIN\n  IF TO_CHAR(clock_timestamp(), &#039;HH24:MI&#039;) NOT BETWEEN &#039;08:00&#039; AND &#039;18:00&#039;\n      OR TO_CHAR(clock_timestamp(), &#039;DY&#039;) IN (&#039;SAT&#039;, &#039;SUN&#039;) THEN\n      RAISE EXCEPTION &#039;%&#039;, &#039;You may only make changes during normal office hours&#039;\n        USING ERRCODE = &#039;45205&#039;;\n  END IF;\nEND;\n$body$\nLANGUAGE PLPGSQL SECURITY DEFINER;\n<\/pre><\/div>\n\n\n<p><code>HR_trigger_fixes.sql<\/code> \u2014 lanzamiento <code>statement_timestamp()<\/code> para evitar <code>timestamptz<\/code> incoherencia, y agrega valores predeterminados de secuencia a las columnas de ID:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSET search_path = hr,public;\n\nCREATE OR REPLACE FUNCTION hr.trigger_fct_update_job_history() RETURNS trigger AS $BODY$\nBEGIN\n  CALL add_job_history(OLD.employee_id, OLD.hire_date,\n                       statement_timestamp()::TIMESTAMP,\n                       OLD.job_id, OLD.department_id);\n  RETURN NEW;\nEND\n$BODY$\nLANGUAGE &#039;plpgsql&#039; SECURITY DEFINER;\n\nALTER TABLE hr.employees   ALTER COLUMN employee_id   SET DEFAULT nextval(&#039;hr.employees_seq&#039;);\nALTER TABLE hr.departments ALTER COLUMN department_id SET DEFAULT nextval(&#039;hr.departments_seq&#039;);\nALTER TABLE hr.locations   ALTER COLUMN location_id   SET DEFAULT nextval(&#039;hr.locations_seq&#039;);\n\nALTER SEQUENCE hr.employees_seq   OWNED BY hr.employees.employee_id;\nALTER SEQUENCE hr.departments_seq OWNED BY hr.departments.department_id;\nALTER SEQUENCE hr.locations_seq   OWNED BY hr.locations.location_id;\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-9-create-the-database\">Paso 9 \u2014 Crear la base de datos<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;CREATE USER hr WITH PASSWORD &#039;hr&#039;;&quot;\nsudo -u postgres psql -c &quot;CREATE DATABASE hrdb OWNER hr;&quot;\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-10-load-in-dependency-order\">Paso 10: Cargar en orden de dependencia<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Tables first \u2014 everything else depends on them\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_tables.sql\n\n# Sequences \u2014 must exist before data load and before sequence_values\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_sequences.sql\n\n# Data \u2014 loads rows via COPY FROM stdin; FKs are dropped inside the file before load\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_data.sql\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-11-re-apply-fk-constraints\">Paso 11 \u2014 Volver a aplicar restricciones FK<\/h3>\n\n\n\n<p>El archivo COPY de ora2pg elimina todas las restricciones de FK antes de la carga de datos y nunca las vuelve a agregar. Sin este paso, la base de datos no tiene restricciones de FK despu\u00e9s del Paso 10.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(echo &quot;SET search_path TO hr;&quot;; \\\n grep &#039;ADD CONSTRAINT.*FOREIGN KEY&#039; \\\n   \/home\/fernando\/ora2pg-hr\/output\/HR_tables.sql) | \\\n  PGPASSWORD=hr psql -U hr -d hrdb -h localhost\n<\/pre><\/div>\n\n\n<p>Verifica el conteo:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost -c &quot;\nSELECT COUNT(*) FROM information_schema.table_constraints\nWHERE constraint_schema = &#039;hr&#039; AND constraint_type = &#039;FOREIGN KEY&#039;;&quot;\n<\/pre><\/div>\n\n\n<p>Esperado: <code>10<\/code><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-12-load-the-remaining-files\">Paso 12 \u2014 Carga los archivos restantes<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Reset sequences to Oracle&#039;s last values \u2014 run after data, not before\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_sequence_values.sql\n\n# Procedures \u2014 must load before triggers (triggers call these procedures)\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_procedures_fixed.sql\n\n# Triggers \u2014 after procedures\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_triggers.sql\n\n# Trigger fixes \u2014 replaces the timestamptz trigger function, adds sequence defaults\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_trigger_fixes.sql\n\n# View \u2014 after all tables are in place\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost \\\n  -f \/home\/fernando\/ora2pg-hr\/output\/HR_views.sql\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-13-static-analysis-with-plpgsql_check\">Paso 13 \u2014 An\u00e1lisis est\u00e1tico con plpgsql_check<\/h3>\n\n\n\n<p>Instala la extensi\u00f3n, luego revisa todo el c\u00f3digo almacenado antes de cualquier prueba de aplicaci\u00f3n.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsudo apt install -y postgresql-18-plpgsql-check\nsudo -u postgres psql -d hrdb -c &quot;CREATE EXTENSION plpgsql_check;&quot;\n<\/pre><\/div>\n\n\n<p>Revisar procedimientos y funciones:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost -c &quot;\nSELECT pg_proc.oid::regprocedure AS function, level, message\nFROM pg_proc,\n     plpgsql_check_function_tb(pg_proc.oid) AS pcf\nWHERE pronamespace = &#039;hr&#039;::regnamespace\n  AND prolang = (SELECT oid FROM pg_language WHERE lanname = &#039;plpgsql&#039;)\n  AND prorettype &lt;&gt; &#039;trigger&#039;::regtype\nORDER BY 1, 2;&quot;\n<\/pre><\/div>\n\n\n<p>Verificar funciones de activaci\u00f3n:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPGPASSWORD=hr psql -U hr -d hrdb -h localhost -c &quot;\nSELECT pg_proc.oid::regprocedure AS function, level, message\nFROM pg_proc\nJOIN pg_trigger ON pg_trigger.tgfoid = pg_proc.oid,\n     plpgsql_check_function_tb(pg_proc.oid, pg_trigger.tgrelid) AS pcf\nWHERE pg_proc.pronamespace = &#039;hr&#039;::regnamespace\n  AND pg_proc.prolang = (SELECT oid FROM pg_language WHERE lanname = &#039;plpgsql&#039;)\n  AND pg_proc.prorettype = &#039;trigger&#039;::regtype\nORDER BY 1, 2;&quot;\n<\/pre><\/div>\n\n\n<p>Esperado despu\u00e9s de aplicar todas las correcciones: <code>(0 filas)<\/code> en ambas consultas.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-14-run-the-test-comparison\">Paso 14 \u2014 Ejecutar la comparaci\u00f3n de PRUEBA<\/h3>\n\n\n\n<p>Agrega la conexi\u00f3n de PostgreSQL a <code>ora2pg.conf<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPG_DSN      dbi:Pg:dbname=hrdb;host=localhost;port=5432\nPG_USER     hr\nPG_PWD      hr\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nora2pg -t TEST 2&gt;&amp;1 | tee \/home\/fernando\/ora2pg-hr\/output\/migration_diff-hr.txt\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"what-the-final-test-report-looks-like\">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> compara el origen de Oracle y el destino de PostgreSQL lado a lado. Cada secci\u00f3n debe mostrar OK, excepto dos falsos positivos conocidos:<\/p>\n\n\n\n<p><strong>VALOR PREDETERMINADO DE COLUMNA DIFERENCIA<\/strong> \u2014 Oracle us\u00f3 triggers para poblar columnas ID; PostgreSQL usa <code>nextval(...) por omisi\u00f3n<\/code>. Las tres tablas donde agregamos valores predeterminados mostrar\u00e1n una diferencia en el recuento. Este es el dise\u00f1o correcto de PostgreSQL, no un error.<\/p>\n\n\n\n<p><strong>FUNCI\u00d3N CONTAR DIFERENCIA<\/strong> \u2014 PostgreSQL almacena la l\u00f3gica de los disparadores en un objeto de funci\u00f3n separado que no tiene equivalente en el diccionario de datos de Oracle. Las dos funciones de disparador (<code>activar_fct_empleados_seguros<\/code>, <code>trigger_fct_update_job_history<\/code>) aparecen como funciones adicionales de PostgreSQL sin contraparte en Oracle. Esto tambi\u00e9n es esperado.<\/p>\n\n\n\n<p>Cualquier otra l\u00ednea de DIFF en la salida de TEST es un problema real que debe resolverse antes de la puesta en marcha.<\/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>RR es un esquema peque\u00f1o. Solucionar cinco problemas en un conjunto de datos de 107 filas es medio d\u00eda de trabajo.<\/p>\n\n\n\n<p>En un esquema de producci\u00f3n real \u2014 50 tablas, 30 procedimientos almacenados, cadenas de disparadores complejas, 10 millones de filas \u2014 existen las mismas cinco categor\u00edas de problemas, con 10 veces el volumen.<\/p>\n\n\n\n<p>El error de la columna monetaria afecta a todos los esquemas que almacenan precios, salarios o cifras financieras. <\/p>\n\n\n\n<p>En <code>%TIPO<\/code> El problema del par\u00e1metro afecta a todos los esquemas con procedimientos almacenados. <\/p>\n\n\n\n<p>En <code>statement_timestamp()<\/code> error de tipo afecta a cada disparador que registra una marca de tiempo actual. <\/p>\n\n\n\n<p>El problema de la re-aplicaci\u00f3n de FK afecta a todos los esquemas con claves for\u00e1neas. <\/p>\n\n\n\n<p>El patr\u00f3n sequence-trigger-to-default afecta a todos los esquemas de Oracle creados antes de la versi\u00f3n 12c.<\/p>\n\n\n\n<p>El valor de ejecutar la migraci\u00f3n en RRHH primero no es la migraci\u00f3n en s\u00ed, sino la creaci\u00f3n de la lista de verificaci\u00f3n que se aplica a cada esquema posterior.<\/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 HR schema is the closest thing the database world has to a universal reference. Every Oracle DBA has seen it. Every migration consultant uses it to explain how the process works. What makes it genuinely useful as a case study is not its size \u2014 it is seven tables and 107 employees. What makes &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/rootfan.com\/es\/ora2pg-migration-example\/\" class=\"more-link\">Seguir leyendo<span class=\"screen-reader-text\"> &#8220;ora2pg Migration Example: Oracle HR Schema to PostgreSQL&#8221;<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":6758,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_focus_keyword":"ora2pg migration oracle hr schema postgresql","rank_math_title":"","rank_math_description":"Step-by-step ora2pg migration of Oracle HR schema to PostgreSQL. Covers NUMBER type mapping, sequence-trigger patterns, %TYPE parameters, and the FK re-apply bug.","rank_math_robots":"","rank_math_og_title":"","rank_math_og_description":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[146],"tags":[143,141,148,137,147,142,149,150],"class_list":["post-6751","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oracle-to-postgresql","tag-data-migration","tag-data-types","tag-hr-schema","tag-migration","tag-ora2pg","tag-pl-sql","tag-sequences","tag-triggers"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rootfan.com\/wp-content\/uploads\/pexels-photo-34191411.jpeg?fit=678%2C1300&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6751","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=6751"}],"version-history":[{"count":6,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6751\/revisions"}],"predecessor-version":[{"id":6768,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6751\/revisions\/6768"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media\/6758"}],"wp:attachment":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media?parent=6751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/categories?post=6751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/tags?post=6751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}