El Show de la Comunidad Hispana. Podcast de Junio 2014.

Ya ha salido el programa del mes de junio del Show de la Comunidad Hispana!

¡Episodio con premios!

El Show de la Comunidad Oracle Hispana sigue dándote sorpresas. Escucha el programa y entérate de los nuevos concursos y premios que preparamos para tí. Te contamos quienes son los ganadores de la Trivia y de los Indicios del programa anterior. Traemos mas Indicios y una nueva Trivia. Jugamos el mundial con una aplicación APEX. Javier Morales nos invita a participar en un nuevo concurso. En la entrevista del Show, Deiby Gomez te cuenta todo acerca de Exadata, las nuevas prestaciones de Oracle 12c, y su ambicioso emprendimiento OraWorld. En el segmento de APEX Clarisa te cuenta cómo funciona esta tecnología dentro de la nueva arquitectura multitenant de Oracle 12c. ¡Programa imperdible!

Además, en mi sección sobre optimización SQL cuento por qué Oracle, en ocasiones, no utiliza los índices que nosotros creemos más convenientes! La respuesta a una de las preguntas más frecuentes sobre temas de optimización SQL!!

El Show de la Comunidad Hispana. Podcast de Mayo 2014.

Ya ha salido el programa del mes de mayo del Show de la Comunidad Hispana!

Si quieres conocer más sobre los productos cartesianos (contado por un servidor), las nuevas funcionalidades de Oracle12c o el programa ACE, no puedes perdértelo de ningún modo!!

El ¿auto deadlock?

¡Últimamente me pasan unas cosas muy curiosas!

¿Qué puede causar que una sesión aparezca en la vista DBA_WAITERS como bloqueadora y como en espera? Fernando García sabe la respuesta, pues él «estaba allí» cuando sucedió.

Se trata de la sesión 80 y, como podéis ver, los bloqueos son todos sobre el objeto 524308 (una tabla).

Se admiten apuestas!!! La base de datos es una Oracle12c y hay tres sesiones en el juego.
PISTA: No hay, ni hubo, ni habrá en este ejemplo un deadlock ORA-00060.

PDB1@ORCL> select * from dba_waiters;

WAITING_SESSION HOLDING_SESSION LOCK_TYPE
————— ————— ————————–
MODE_HELD
—————————————-
MODE_REQUESTED   LOCK_ID1   LOCK_ID2
—————————————- ———- ———-
    80     44 Transaction
Exclusive
Exclusive     524308  2374

    72     44 Transaction
Exclusive
Share     524308  2374

    80     80 Transaction
None
Exclusive     524308  2374

    72     80 Transaction
None
Share     524308  2374

Resuelto el misterio del año 0000!.

Hoy, gracias a una discusión a tres bandas en twitter con Tony Doval @tonydoval, Xavier Picamal @Condebond y Elias Fernández @sailefm se ha resuelto por fin el misterio del año 0000 en algunas de mis bases de datos.
¿Quién localizó el bug? El premio es para Elias Fernández! (que se lleva mi más sincero «me quito el sombrero»).
La cuestión es que el año 0000 no existe, aunque en algunas bases de datos he visto lo siguiente:

SQL> select * from zero_leap_year
  2  where to_char(date_year,’yyyy’)=’0000′ and rownum<6;

DATE_YEAR
——————–
30-DEC-0000 00:00:00
30-JAN-0000 00:00:00
30-DEC-0000 00:00:00
29-FEB-0000 00:00:00
30-JAN-0000 00:00:00

El año 0000 no existe. Del año 1 antes de Cristo se pasa al año 1 después de Cristo. Cualquier forma de insertar un año 0000 o una fecha 29-febrero en un año no bisiesto dará los siguientes errores:

SQL> insert into zero_leap_year values (to_date(’20-02-0000 00:00:00′,’dd-mm-yyyy hh24:mi:ss’));
insert into zero_leap_year values (to_date(’20-02-0000 00:00:00′,’dd-mm-yyyy hh24:mi:ss’))
                                           *
ERROR at line 1
ORA-01841 :(full) year must be between -4713 and +9999, and not be 0
SQL> insert into zero_leap_year values (to_date(’29-02-2007 00:00:00′,’dd-mm-yyyy hh24:mi:ss’));
insert into zero_leap_year values (to_date(’29-02-2007 00:00:00′,’dd-mm-yyyy hh24:mi:ss’))
                                           *
ERROR at line 1:
ORA-01839: date not valid for month specified

No obstante, estas filas misteriosas seguían apareciendo. Tanto en versión Oracle9i, Oracle10g y Oracle11g. ¿Cómo han podido colarse? Muy probablemente como Elias Fernandez encontró: partiendo de una fecha como, por ejemplo, 1-enero del año 1, restarle 1 día. Voilà! 

SQL> select (TO_DATE(’01/01/0001 00:00:00′, ‘DD/MM/YYYY HH24:MI:SS’) – 1) from dual;

(TO_DATE(’01/01/0001
——————–
31-DIC-0000 00:00:00

No sólo eso… ese año 0000 que no existe en la historia, según Oracle, es bisiesto!

SQL> select (TO_DATE(’01/01/0001 00:00:00′, ‘DD/MM/YYYY HH24:MI:SS’) – 307) from dual;

(TO_DATE(’01/01/0001
——————–
29-FEB-0000 00:00:00

Esto es un bug en toda regla!

Blog de Clarisa Mamán

Gracias a la Comunidad Oracle Hispana estoy teniendo la oportunidad de conocer otros blogs de tecnología Oracle en español y después del de Andrew Reid me gustaría mencionar el de Clarisa Mamán.
Su blog está lleno de videotutoriales sobre APEX y desarrollo SQL sobre Oracle. Los videos son claros y precisos, tanto que invitan a matricularse en su curso sobre APEX en que enseña cómo desarrollar una aplicación completa, desde la instalación a los detalles finales.

 Blog de Clarisa Mamán

Blog interesante: Andrew Reid.

Hace poco he descubierto el blog de Andrew Reid que no conocía y me ha parecido muy interesante. He leído algunos artículos y tienen muy buena pinta. Trata tanto temas de rendimiento como asuntos de administración, con scripts detallados y tests hechos a conciencia!

Totalmente recomendable!!

Si quieres conocer más sobre el trabajo de Andrew en la red, quizás quieras echarle un ojo a su blog en inglés: http://international-dba.blogspot.com.es/

La autoridad de ORDER BY

Para que el resultado de una consulta SQL esté ordenado, la única cláusula válida es ORDER BY.


Oracle únicamente garantiza la devolución ordenada de las filas cuando se establece ORDER BY como criterio de ordenación.

He repetido estas dos frases como un mantra miles de veces.

Cualquier otra forma de obtener los datos ordenados es:

  • Una casualidad.
  • Una ilusión.
  • Una circunstancia temporal.
  • Una combinación de las anteriores.

Los ejemplos que mostraré a continuación, de resultados ordenados sin cláusula ORDER BY en el comando SELECT, son fruto de su imaginación

FALSO MITO #1 – Si las filas se insertaron de forma ordenada.

SQL> create table objetos as select object_id, object_name, object_type 
   2  from dba_objects order by object_id;

Table created.

SQL> select * from objetos where rownum<10 font="">


 OBJECT_ID OBJECT_NAME          OBJECT_TYPE
———- ——————– ——————-
         2 C_OBJ#               CLUSTER
         3 I_OBJ#               INDEX
         4 TAB$                 TABLE
         5 CLU$                 TABLE
         6 C_TS#                CLUSTER
         7 I_TS#                INDEX
         8 C_FILE#_BLOCK#       CLUSTER
         9 I_FILE#_BLOCK#       INDEX
        10 C_USER#              CLUSTER

9 rows selected.

FALSO MITO #2 – Si se accede a las filas mediante un índice.

SQL> create table objetos_indice as select object_id, object_name, object_type 
   2  from dba_objects;

Table created.

SQL> create index idx_object_id on objetos_indice (object_id);

Index created.

SQL> select * from objetos_indice where object_id between 11256 and 11260;

 OBJECT_ID OBJECT_NAME          OBJECT_TYPE
———- ——————– ——————-
     11256 ALL_APPLY            SYNONYM
     11257 DBA_APPLY_PARAMETERS VIEW
     11258 DBA_APPLY_PARAMETERS SYNONYM
     11259 ALL_APPLY_PARAMETERS VIEW
     11260 ALL_APPLY_PARAMETERS SYNONYM

FALSO MITO #3 – Si la tabla tiene estructura IOT.

SQL> create table objetos_iot
  2  (object_id, object_name, object_type,
  3   constraint pk_objetos_iot primary key (object_id))
  4  organization index
  5  as select object_id, object_name, object_type
  6  from dba_objects;

Table created.


SQL> select * from objetos_iot where object_id between 11256 and 11265;

 OBJECT_ID OBJECT_NAME                    OBJECT_TYPE
———- —————————— ——————-
     11256 ALL_APPLY                      SYNONYM
     11257 DBA_APPLY_PARAMETERS           VIEW
     11258 DBA_APPLY_PARAMETERS           SYNONYM
     11259 ALL_APPLY_PARAMETERS           VIEW
     11260 ALL_APPLY_PARAMETERS           SYNONYM
     11261 _DBA_APPLY_SOURCE_SCHEMA       VIEW
     11262 _DBA_APPLY_SOURCE_OBJ          VIEW
     11263 DBA_APPLY_INSTANTIATED_OBJECTS VIEW
     11264 DBA_APPLY_INSTANTIATED_OBJECTS SYNONYM
     11265 ALL_APPLY_INSTANTIATED_OBJECTS VIEW

10 rows selected.
FALSO MITO #4 – Si la tabla tiene estructura de cluster.
SQL> create cluster clu_objetos (object_id number) tablespace test;

Cluster created.


SQL> create index idx_cluster_objetos on cluster clu_objetos;

Index created.

SQL> create table padre_objeto (id number constraint pk_objeto primary key) cluster clu_objetos(id);

Table created.

SQL> insert into padre_objeto values (1);

1 row created.

SQL> insert into padre_objeto values (2);

1 row created.

SQL> insert into padre_objeto values (3);

1 row created.

SQL> insert into padre_objeto values (4);

1 row created.

SQL> insert into padre_objeto values (5);

1 row created.

SQL> insert into padre_objeto values (6);

1 row created.

SQL> create table hijo_objeto (id number constraint fk_objeto_padre references padre_objeto(id)) cluster clu_objetos(id);

Table created.

SQL> insert into hijo_objeto values (3);

1 row created.

SQL> insert into hijo_objeto values (6);

1 row created.

SQL> insert into hijo_objeto values (5);

1 row created.

SQL> insert into hijo_objeto values (2);

1 row created.

SQL> select * from hijo_objeto;

        ID
———-
         2
         3
         5
         6
FALSO MITO #5 – Si la sentencia utiliza DISTINCT.

SQL> create table test_orden (texto varchar2(50));

Table created.

SQL> insert into test_orden values (‘PPPPP’);

1 row created.

SQL> insert into test_orden values (‘FFFFF’);

1 row created.

SQL> insert into test_orden values (‘ZZZZZ’);

1 row created.

SQL> insert into test_orden values (‘AAAAA’);


1 row created.

SQL> select texto from test_orden;

TEXTO
————————————————–
PPPPP
FFFFF
ZZZZZ
AAAAA


SQL> select distinct texto from test_orden;

TEXTO
————————————————–
AAAAA
FFFFF
PPPPP
ZZZZZ

FALSO MITO #6 – Si la sentencia utiliza GROUP BY.

SQL> select texto from test_orden;

TEXTO
————————————————–
PPPPP
FFFFF
ZZZZZ
AAAAA

SQL> select texto from test_orden group by texto;

TEXTO
————————————————–
AAAAA
FFFFF
PPPPP

ZZZZZ


FALSO MITO #7 – Si la sentencia utiliza realiza una join de tipo MERGE JOIN.

SQL> create table test_orden2 as select * from test_orden;


Table created.

SQL> select /*+USE_MERGE(t,t2) */ t.texto
  2  from test_orden t, test_orden2 t2
  3  where t.texto=t2.texto;

TEXTO
————————————————–
AAAAA
FFFFF
PPPPP
ZZZZZ
OLVIDEN ESTOS EJEMPLOS!!!!
NO TRATEN DE IMPLEMENTARLOS EN PRODUCCIÓN!!!!
Todas estas ordenaciones casuales, sin uso de la cláusula ORDER BY, son sólo fruto de su imaginación…



¿Te ha parecido interesante esta entrada? 
Si es así, échale un ojo a mi libro sobre Optimización SQL en Oracle.

Índices basados en funciones. Problemas en migraciones de versión.

Una base de datos Oracle 9i tenía una tabla con un campo fecha y un índice basado en función para localizar los valores nulos. La función NVL asignaba un valor ‘NULO’ a los campos vacíos, con el fin de localizar estas filas nulas, y para no dar un conflicto de tipos, convertía la fecha a TO_CHAR.

De este modo, la consulta se ejecutaba así:
Ejecución en Oracle 9i
SQL>  create index fbi_fecha on test(NVL(TO_CHAR(FECHA),’NULO’));

Índice creado.

SQL> explain plan for
  2  select * from test
  3  where NVL(TO_CHAR(FECHA),’NULO’) = ‘NULO’;

Explained.

SQL> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
—————————————————————————

—————————————————————————
| Id  | Operation                   |  Name       | Rows  | Bytes | Cost  |
—————————————————————————
|   0 | SELECT STATEMENT            |             |   130 |  1040 |     5 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST        |   130 |  1040 |     5 |
|*  2 |   INDEX RANGE SCAN          | FBI_FECHA   |   130 |       |     3 |
—————————————————————————

Predicate Information (identified by operation id):
—————————————————

   2 – access(NVL(TO_CHAR(«TEST».»FECHA»),’NULO’)=’NULO’)

Note: cpu costing is off

15 rows selected.
No obstante, al migrar esta base de datos a Oracle 11g, esta misma sentencia no usaba el índice basado en función, y hacía un acceso FULL SCAN.
Ejecución en Oracle 11g

SQL>  create index fbi_fecha on test(NVL(TO_CHAR(FECHA),’NULO’));

Índice creado.

SQL> explain plan for
  2  select * from test
  3  where NVL(TO_CHAR(FECHA),’NULO’) = ‘NULO’;

Explicado.

SQL> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
—————————————————————————-
Plan hash value: 1357081020

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      | 10681 | 85448 |   571   (9)| 00:00:07 |
|*  1 |  TABLE ACCESS FULL| TEST | 10681 | 85448 |   571   (9)| 00:00:07 |
————————————————————————–

Predicate Information (identified by operation id):
—————————————————

   1 – filter(NVL(TO_CHAR(INTERNAL_FUNCTION(«FECHA»)),’NULO’)=’NULO’)

13 filas seleccionadas.
El motivo: aunque la sintaxis de creación de los índices ha sido la misma, internamente su almacenamiento es ligeramente distinto. Mientras en Oracle9i se almacena la función TO_CHAR sin formato de máscara, en Oracle11g se define con un formato de máscara por defecto.
Ejecución en Oracle 9i
SQL> select index_name, column_expression
  2  from user_ind_expressions
  3  where index_name=’FBI_FECHA’;

INDEX_NAME                     COLUMN_EXPRESSION
—————————— ———————————————–
FBI_FECHA                      NVL(TO_CHAR(«FECHA»),’NULO’)


Ejecución en Oracle 11g
SQL> select index_name, column_expression
  2  from user_ind_expressions
  3  where index_name=’FBI_FECHA’;

INDEX_NAME                     COLUMN_EXPRESSION
—————————— ———————————————–
FBI_FECHA                      NVL(TO_CHAR(«FECHA»,’DD/MM/RR’),’NULO’)



De modo que, para que en Oracle 11g el optimizador considere el uso del íncide basado en función FBI_FECHA, la función de filtrado debe ser idéntica y debe incluir la máscara ‘DD/MM/RR’ que se ha añadido a la expresión del índice.
Ejecución en Oracle 11g

SQL> explain plan for
  2  select * from test
  3  where NVL(TO_CHAR(FECHA,’DD/MM/RR’),’NULO’) = ‘NULO’;

Explicado.

SQL> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
———————————————————————————
Plan hash value: 3576847778

—————————————————————————————–
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————–
|   0 | SELECT STATEMENT            |           |   130 |  2210 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST      |   130 |  2210 |     5   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | FBI_FECHA |   130 |       |     3   (0)| 00:00:01 |
—————————————————————————————–

Predicate Information (identified by operation id):
—————————————————

   2 – access(NVL(TO_CHAR(INTERNAL_FUNCTION(«FECHA»),’DD/MM/RR’),’NULO’)=’NULO’)

14 filas seleccionadas.



¿Te ha parecido interesante esta entrada? 
Si es así, échale un ojo a mi libro sobre Optimización SQL en Oracle.

Uso de índices basados en funciones con conversiones TIMESTAMP

Cuando un filtro por una columna se realiza mediante una función, el optimizador no utiliza los índices de esa columna ya que la función «transforma» los valores y hace que el índice no resulte válido. Para sortear ese obstáculo, Oracle dispone de los índices basados en funciones.

En ocasiones es el propio motor quien añade funciones a los filtros y eso puede volvernos un poco locos intentando averiguar por qué Oracle no usa los índices de la columna. Por ejemplo:

SQL> create table test (id number, dt date);

Tabla creada.

SQL> insert into test select rownum, to_date(’07/01/2014 13:00′,’DD/MM/YYYY HH24:MI’)+rownum/144 from dba_objects;

94874 filas creadas.

SQL>  select * from test where dt

        ID DT
———- ——————–
         1 07-ENE-2014 13:10:00
         2 07-ENE-2014 13:20:00
         3 07-ENE-2014 13:30:00
         4 07-ENE-2014 13:40:00
         5 07-ENE-2014 13:50:00

SQL> create index idx_test_fecha on test(dt);

Índice creado.


La tabla tiene 94874 filas numeradas con fechas a partir de 7 de enero de 2014 13:10 para cada 10 minutos consecutivamente. El índice creado sobre la columna de fecha no servirá si en el filtro de una sentencia SELECT se compara con un tipo de dato TIMESTAMP (pues causaría una conversión implícita de los valores de la columna DT de tipo fecha):


SQL> explain plan for select * from test where dt

Explicado.

SQL> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
—————————————————————————-
Plan hash value: 1357081020

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      |     3 |    66 |    71   (8)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST |     3 |    66 |    71   (8)| 00:00:01 |
————————————————————————–

Predicate Information (identified by operation id):
—————————————————

PLAN_TABLE_OUTPUT
—————————————————————————-

   1 – filter(INTERNAL_FUNCTION(«DT»)
              14:00:00′))

Note
—–
   – dynamic sampling used for this statement (level=2)

Esta conversión implícita nos lleva al FULL SCAN de la tabla TEST.

Para este caso, además, crear un índice basado en funciones tampoco serviría, pues la función de conversión TO_TIMESTAMP no es determinista. Las funciones deterministas devuelven siempre el mismo valor a un determinado paso de parámetros, pero TO_TIMESTAMP se apoya en las variables de NLS locales como el timezone.

SQL> create index idx_fb_test_fecha on test(to_timestamp(dt));
create index idx_fb_test_fecha on test(to_timestamp(dt))
                                       *
ERROR en línea 1:
ORA-01743: sólo se pueden indexar funciones puras

Supongamos que nuestra base de datos es local y siempre se consultará con TIMESTAMP sobre una misma zona horaria. En ese caso, podemos crear nuestra propia función TO_TIMESTAMP determinista e intentar crear el índice sobre ésta (y que las consultas incluyan nuestra función, claro!).

SQL> create or replace function to_timestamp_determinista(fecha timestamp) return timestamp deterministic is
  2  begin
  3     return to_timestamp(fecha);
  4  end;
  5  /

Función creada.

SQL> create index idx_fb_test_fecha on test(to_timestamp_determinista(dt));


Índice creado.

Ahora ya es posible que nuestra consulta pueda utilizar el índice basado en función para convertir a TIMESTAMP de forma determinística, y beneficiarnos del uso del índice para recuperar las 7 filas entre más de 94.000.
SQL> select * from test
  2  where to_timestamp_determinista(dt)<
  3        to_timestamp_determinista(to_date(’07/01/2014 14:00′,’DD/MM/YYYY HH24:MI’));

        ID DT
———- ——————–
         1 07-ENE-2014 13:10:00
         2 07-ENE-2014 13:20:00
         3 07-ENE-2014 13:30:00
         4 07-ENE-2014 13:40:00
         5 07-ENE-2014 13:50:00

SQL> explain plan for
  2  select * from test
  3  where to_timestamp_determinista(dt)<
  4        to_timestamp_determinista(to_date(’07/01/2014 14:00′,’DD/MM/YYYY HH24:MI’));

Explicado.

SQL> @?/rdbms/admin/utlxpls

PLAN_TABLE_OUTPUT
—————————————————————–
Plan hash value: 2253730852

————————————————————————————————-
| Id  | Operation                   | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————————————-
|   0 | SELECT STATEMENT            |                   |  5378 |   231K|     7   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST              |  5378 |   231K|     7   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_FB_TEST_FECHA |   968 |       |     4   (0)| 00:00:01 |
————————————————————————————————-

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
—————————————————————————–

   2 – access(«SYS».»TO_TIMESTAMP_DETERMINISTA»(INTERNAL_FUNCTION(«DT»))<«TO_TIMESTAMP_DETERMINISTA»(TIMESTAMP’ 2014-01-07 14:00:00′))

Note
—–
   – dynamic sampling used for this statement (level=2)

19 filas seleccionadas.

¿Te ha parecido interesante esta entrada? 
Si es así, échale un ojo a mi libro sobre Optimización SQL en Oracle.

El año 0 y Oracle GoldenGate

El año 0 no existe. Del año -1 (es decir, año 1 antes de Cristo) se pasa al año 1 y el primer año bisiesto de la historia es el año 4.
No obstante, en una base de datos he visto filas que en un campo de tipo DATE habían conseguido introducir una fecha ’29-FEB-0000′.
Oracle realiza dos controles con las fechas:
Uno específicamente sobre el año cero, que es el siguiente:
SQL> create table fechas (fecha date);

Tabla creada.

SQL> insert into fechas values (to_date(’01-01-0000′,’DD-MM-YYYY’));
insert into fechas values (to_date(’01-01-0000′,’DD-MM-YYYY’))
                                    *
ERROR en línea 1:
ORA-01841: el valor (completo) del año debe estar entre -4713 y +9999, y no debe ser igual a 0
Otro sobre la validez de la fecha, de modo que:

  • Un día 31 es válido sólo para los meses enero, marzo, mayo, julio, agosto, octubre y diciembre.
  • Un día 29 sólo es válido para el mes de febrero en los años bisiestos.
  • Un día 30 es válido para todos los meses excepto febrero.

SQL> insert into fechas values (to_date(’29-02-0001′,’DD-MM-YYYY’));
insert into fechas values (to_date(’29-02-0001′,’DD-MM-YYYY’))
                                    *
ERROR en línea 1:
ORA-01839: fecha incorrecta para el mes especificado
Como comento en este caso, en una base de datos consiguieron saltar los controles del motor y se introdujeron fechas en año 0, incluso el ’29-FEB-0000′.
NOTA: Ni idea cómo lo consiguieron. Intenté insertar esa fecha con SQL dinámico, o desde PL/SQL, etc y siempre recibía alguno de estos dos errores.
SQL>  select CODIGO, FECHA from  TABLA_BASE
  2  where FECHA =(select min(FECHA) from TABLA_BASE);

CODIGO       FECHA
———— ——————–
000600211048 01-ENE-0000 00:00:00
00060164681- 01-ENE-0000 00:00:00
El caso es que esa base de datos se iba a migrar de Oracle9i a Oracle11gR2, y los procedimientos de export/import propagaban las fechas incluyendo estos registros sobre el año 0. De modo que la importación fue bien, pero en cuanto volvieron a insertar un año 0 en la tabla, al propagarse por Orace GoldenGate al futuro entorno, el error ORA-01841 apareció de pronto.
Oracle GoldenGate Delivery for Oracle process started, group REPL discard file opened: 2013-09-26 17:07:02

Current time: 2013-09-26 17:16:35
Discarded record from action ABEND on error 1841

OCI Error ORA-01841: el valor (completo) del año debe estar entre -4713 y +9999, y no debe ser igual a 0 (status = 1841). UPDATE «APP_OWNER».»TABLA_BASE» SET «ACTIVO» = :a4,»TIPO» = :a5,»DURACION» = :a6,»FECHA» = :a7 WHERE «CODIGO» = :b0 AND «FECHA_ORIGEN» = :b1 AND «CATEGORIA» = :b2 AND «TABLA_BASE_CAT» = :b3

Aborting transaction on ./dirdat/ab beginning at seqno 58 rba 4270475
                         error at seqno 58 rba 4270475
Problem replicating APP_OWNER.TABLA_BASE to APP_OWNER.TABLA_BASE
Mapping problem with compressed update record (target format)…
*
CODIGO = 000600371922
FECHA_ORIGEN = 2004-05-27 00:00:00
CATEGORIA = asi1
TABLA_BASE_CAT = ~#
ACTIVO = NULL
TIPO = T
DURACION = 60
FECHA = 0000-02-29 00:00:00
*

Process Abending : 2013-09-26 17:16:35
Para solucionar esto, en el fichero de parámetros de REPLICAT, hay que mapear las fechas a una fecha válida. Para evitar, además, el problema del 29 de febrero envié el mapeo al año 0004.
Replicat repl
UserID oggadm1@bbdd, password *****
AssumeTargetDefs
DiscardFile ./dirrpt/repl.dsc
ALLOWNOOPUPDATES
TABLEEXCLUDE APP_OWNER.VM_TABLE1_DS
TABLEEXCLUDE APP_OWNER.VM_TABLE2_DS
Map app_owner.tabla_base, Target app_owner.tabla_base,
COLMAP (USEDEFAULTS,fecha=@STRSUB(fecha,»0000″,»0004″));
Map app_owner.*, Target app_owner.*;