Hablemos de Constraints.

Las constraints son reglas que ofrecen una garantía de integridad en la información vinculada. Existen 5 tipos de constraints.

–   Primary key
–   Foreign key
–   Unique key
–   Not Null
–   Check

Las constraints tienen, básicamente, tres propósitos:

         1.- Forzar integridad. Una constraint con estado ENABLED evita que, sobre las tablas, puedan modificarse valores de forma que no se satisfaga la regla de la constraint.

         2.- Validar la información. Una constraint con estado DISABLED VALIDATE realiza una validación sobre los datos, certificando que la información contenida en las tablas satisface la regla de la constraint.

         3.- Asumir la validez de la información. Una constraint con estado RELY no verifica la información, ni fuerza la integridad para las modificaciones y asume que los datos introducidos satisfacen la regla de la constraint.

Las constraints, por tanto, pueden estar ENABLED o DISABLED.
Las constraints habilitadas cumplen el propósito 1. Ningún dato introducido o modificado viola la integridad de la regla de la constraint. De este modo, sobre una primary key ENABLED no se permite un insert sobre una fila ya existente. No obstante, si la constraint está en el estado ENABLED NOVALIDATE, no se permitirán cambios que violen la regla, pero no se verificarán que los datos existentes la cumplan.

Las constraints, por tanto, pueden estar en estado VALIDATE o NOVALIDATE independientemente de si están ENABLED o DISABLED.
Las constraints con VALIDATE cumplen el propósito 2: La información existente está validada. No obstante, si la constraint está deshabilitada con DISABLED VALIDATE, no se permitirán cambios sobre las columnas afectadas, ya que aunque la constraint no está habilitada, certifica que los datos existentes son válidos y cumplen con la regla de la constraint.

Las constraints, por tanto, pueden estar en estado RELY o NORELY (también conocido como BELIEF) cuando las constraints están en estado DISABLED NOVALIDATE.
Las constraints en ese estado cumplen con el propósito 3: Asumen que la información es correcta y aportan al optimizador información válida para el uso de vistas materializadas, optimización de queries, etc. De este modo, por ejemplo, al optimizador le puede resultar mucho más fácil encontrar tablas FACT o DIMENSION por las constraints de foreign key entre las tablas.

Las constraints DISABLED NOVALIDATE RELY ni validan la información de la tabla, ni comprueban que las modificaciones cumplen las reglas de las restricciones.

Ejemplo 1: Una restricción de clave primaria habilitada con la opción de NOVALIDATE. No verifica la integridad referencial de las filas, pero no permite que las nuevas filas puedan violar la restricción.


SQL> create table valores(
  2   id number,
  3   valor varchar2(50),
  4   descrip varchar2(300),
  5   CONSTRAINT PK_VALORES PRIMARY KEY (id) deferrable initially immediate);

Tabla creada.

SQL> alter table valores disable primary key;

Tabla modificada.

SQL> insert into valores values (1,’ELEMENTO UNO’,’Primer elemento, tabla vacía’);

1 fila creada.

SQL> insert into valores values (1,’ELEMENTO UNO»’,’Primer elemento, repetido’);

1 fila creada.

SQL> insert into valores values (1,’ELEMENTO UNO»»’,’Primer elemento, otra vez repetido’);

1 fila creada.

SQL> alter table valores enable novalidate primary key;

Tabla modificada.

SQL> insert into valores values (1,’ELEMENTO UNO»»»’,’Primer elemento, violando PK, repetido’);
insert into valores values (1,’ELEMENTO UNO»»»’,’Primer elemento, violando PK, repetido’)
*
ERROR en lÝnea 1:
ORA-00001: restricci¾n ·nica (DW_VIAJES.PK_VALORES) violada

SQL> select * from valores;

        ID VALOR                                    DESCRIP
———- —————————————- —————————————-
         1 ELEMENTO UNO                             Primer elemento, tabla vacía
         1 ELEMENTO UNO’                            Primer elemento, repetido
         1 ELEMENTO UNO»                           Primer elemento, otra vez repetido
 

Ejemplo 2: Una restricción de clave primaria deshabilitada con la opción de VALIDATE. Verifica la integridad referencial de las filas, pero como está deshabilitada, no permite DML sobre la columna.


SQL> truncate table valores;

Tabla truncada.

SQL> alter table valores disable primary key;

Tabla modificada.

SQL> insert into valores values (1,’ELEMENTO UNO’,’Primer elemento, tabla vacía’);

1 fila creada.

SQL> insert into valores values (2,’ELEMENTO DOS’,’Segundo elemento, ID distinto.’);

1 fila creada.

SQL> insert into valores values (3,’ELEMENTO TRES’,’Tercer elemento, ID distinto.’);

1 fila creada.

SQL> alter table valores disable validate primary key;

Tabla modificada.

SQL> insert into valores values (3,’ELEMENTO TRES’,’Tercer elemento, ID distinto.’);
insert into valores values (3,’ELEMENTO TRES’,’Tercer elemento, ID distinto.’)
*
ERROR en lÝnea 1:
ORA-25128: No se puede insertar/actualizar/suprimir en la tabla con la restricci¾n (DW_VIAJES.PK_VALORES) desactivada y validada

SQL> insert into valores values (4,’ELEMENTO CUATRO’,’Cuarto elemento, ID distinto.’);
insert into valores values (4,’ELEMENTO CUATRO’,’Cuarto elemento, ID distinto.’)
*
ERROR en lÝnea 1:
ORA-25128: No se puede insertar/actualizar/suprimir en la tabla con la restricci¾n (DW_VIAJES.PK_VALORES) desactivada y validada

Ejemplo 3: Una restricción de clave ajena deshabilitada. Permite la entrada de valores no existentes en la PK. La opción de enable constraint con la cláusula EXCEPTIONS INTO registra en la tabla exceptions los identificadores de las filas que no cumplen la condición de la constraint.


SQL> create table master (id number primary key);

Tabla creada.

SQL> create table detail (id number primary key,  valor varchar2(50),
  2  master_id number constraint fk_master_id references master(id));

Tabla creada.

SQL> insert into master select rownum from user_tables;

40 filas creadas.

SQL> alter table detail disable constraint fk_master_id;

Tabla modificada.

SQL> insert into detail select rownum, object_name, mod(rownum,45)+1 from user_objects;

68 filas creadas.

SQL> alter table detail enable constraint fk_master_id exceptions into exceptions;
alter table detail enable constraint fk_master_id exceptions into exceptions
                                     *
ERROR en línea 1:
ORA-02298: no se puede validar (ORADBA.FK_MASTER_ID) – claves principales no encontradas

SQL> select count(*) from exceptions;

  COUNT(*)
———-
         5
SQL> select * from exceptions;

ROW_ID             OWNER                          TABLE_NAME                     CONSTRAINT
—————— —————————— —————————— ——————
AAAY/nAAJAAAd7KAAn ORADBA                         DETAIL                         FK_MASTER_ID
AAAY/nAAJAAAd7KAAo ORADBA                         DETAIL                         FK_MASTER_ID
AAAY/nAAJAAAd7KAAp ORADBA                         DETAIL                         FK_MASTER_ID
AAAY/nAAJAAAd7KAAq ORADBA                         DETAIL                         FK_MASTER_ID
AAAY/nAAJAAAd7KAAr ORADBA                         DETAIL                         FK_MASTER_ID

Uso de logminer.

En alguna ocasión he necesitado recuperar una transacción validada con COMMIT, pero no deseada o errónea. Pongo un ejemplo:

– Usuario borra una fila.
– Por accidente.
– No sabe de qué fila se trata, pero ‘algo’ ha dicho «registro borrado».
(…borrado y validado).
– Ni idea de qué datos tenía la fila.
– Ni él, ni nadie.
– Desde ese momento, la fila X ha desaparecido sin rastro.

Con variaciones, éste caso es bastante frecuente, y si el usuario que ha borrado el registro es lo suficientemente rápido, existe una posibilidad de recuperar los datos perdidos.

Generalmente, todas las transacciones convencionales procesan la información pasando por las áreas de redo log. En estos archivos está toda la información correspondiente al nuevo cambio. Oracle basa toda su actividad en el buen funcionamiento del registro de redo log y, por tanto, en esta área únicamente almacena los datos físicos elementales para rehacer la operación. Por tanto, no se almacena información sobre los valores anteriores.

Lo cual sí sucede en el área de rollback, y ¡la gestión de rollback también pasa por redolog!.

Oracle dispone de un paquete, el LOG MINER que permite rastrear los redo logs (online y archivados) junto con la información de rollback disponible también en éstos. Una vez ejecutado el paquete que crea el fichero de diccionario de objetos, Oracle es capaz de interpretar esos redologs y mostrar información tan válida como el SQL generado y el SQL inverso para «reparar» la acción.


SQL> connect / as sysdba
Conectado.
SQL> show parameters utl

NAME                                 TYPE        VALUE
———————————— ———– ——————————
utl_file_dir                         string      c:oraclefiles

SQL> exec DBMS_LOGMNR_D.BUILD( DICTIONARY_FILENAME =>’dictionary.ora’, DICTIONARY_LOCATION => ‘c:oraclefiles’);

Procedimiento PL/SQL terminado correctamente.

SQL> select member from v$logfile;

MEMBER
——————————————————————————–
C:ORACLEORADATAORCLREDO04A.LOG
C:ORACLEORADATAORCLREDO04B.LOG
C:ORACLEORADATAORCLREDO05A.LOG
C:ORACLEORADATAORCLREDO05B.LOG
C:ORACLEORADATAORCLREDO06A.LOG
C:ORACLEORADATAORCLREDO06B.LOG

6 filas seleccionadas.

SQL> exec DBMS_LOGMNR.add_logfile(‘C:ORACLEORADATAORCLREDO04A.LOG’);

Procedimiento PL/SQL terminado correctamente.

SQL> exec DBMS_LOGMNR.add_logfile(‘C:ORACLEORADATAORCLREDO05A.LOG’);

Procedimiento PL/SQL terminado correctamente.

SQL> exec DBMS_LOGMNR.add_logfile(‘C:ORACLEORADATAORCLREDO06A.LOG’);

Procedimiento PL/SQL terminado correctamente.

SQL> EXECUTE DBMS_LOGMNR.START_LOGMNR(DICTFILENAME =>’c:oraclefilesdictionary.ora’);

Procedimiento PL/SQL terminado correctamente.

SQL> set pages 100
SQL> set lines 120
SQL> column sql_redo format a50
SQL> column sql_undo format a50

SQL> select sql_redo, sql_undo from v$logmnr_contents where rownum<10;

SQL_REDO                                           SQL_UNDO
————————————————– ————————————————–
insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘170,01’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘170,01’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘170,01′,’1′,’170,01′,’1 170,01’ and «Importe AVG» = ‘170,01’ and «Ciudad»
70,01′,’170,01′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52149′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52149’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAA’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘112,95’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘112,95’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘112,95′,’1′,’112,95′,’1 112,95’ and «Importe AVG» = ‘112,95’ and «Ciudad»
12,95′,’112,95′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52206′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52206’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAB’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘123,29’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘123,29’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘123,29′,’1′,’123,29′,’1 123,29’ and «Importe AVG» = ‘123,29’ and «Ciudad»
23,29′,’123,29′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52331′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52331’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAC’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘178,55’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘178,55’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘178,55′,’1′,’178,55′,’1 178,55’ and «Importe AVG» = ‘178,55’ and «Ciudad»
78,55′,’178,55′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52419′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52419’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAD’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘107,36’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘107,36’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘107,36′,’1′,’107,36′,’1 107,36’ and «Importe AVG» = ‘107,36’ and «Ciudad»
07,36′,’107,36′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52488′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52488’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAE’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘120,5’ and «Importe COUNT» = ‘1’ a
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» nd «Importe MAX» = ‘120,5’ and «Importe MIN» = ’12
,»Detalle Vuelo») values (‘120,5′,’1′,’120,5′,’120 0,5’ and «Importe AVG» = ‘120,5’ and «Ciudad» = ‘M
,5′,’120,5′,’Madeira’,’Portugal’,’Mayores de 50 a± adeira’ and «Pais» = ‘Portugal’ and «Rango Edad» =
os’,’Mujer’,’VUELO52561′);                          ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and «De
                                                   talle Vuelo» = ‘VUELO52561’ and ROWID = ‘AAALVmAAP
                                                   AAAAn8AAF’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘109,01’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘109,01’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘109,01′,’1′,’109,01′,’1 109,01’ and «Importe AVG» = ‘109,01’ and «Ciudad»
09,01′,’109,01′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52690′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52690’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAG’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘174,91’ and «Importe COUNT» = ‘1’
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» and «Importe MAX» = ‘174,91’ and «Importe MIN» = ‘
,»Detalle Vuelo») values (‘174,91′,’1′,’174,91′,’1 174,91’ and «Importe AVG» = ‘174,91’ and «Ciudad»
74,91′,’174,91′,’Madeira’,’Portugal’,’Mayores de 5 = ‘Madeira’ and «Pais» = ‘Portugal’ and «Rango Eda
0 a±os’,’Mujer’,’VUELO52693′);                     d» = ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and
                                                    «Detalle Vuelo» = ‘VUELO52693’ and ROWID = ‘AAALV
                                                   mAAPAAAAn8AAH’;

insert into «DISCOVERER9I».»EUL5_MV101651″(«Import delete from «DISCOVERER9I».»EUL5_MV101651″ where «
e SUM»,»Importe COUNT»,»Importe MAX»,»Importe MIN» Importe SUM» = ‘131,5’ and «Importe COUNT» = ‘1’ a
,»Importe AVG»,»Ciudad»,»Pais»,»Rango Edad»,»Sexo» nd «Importe MAX» = ‘131,5’ and «Importe MIN» = ’13
,»Detalle Vuelo») values (‘131,5′,’1′,’131,5′,’131 1,5’ and «Importe AVG» = ‘131,5’ and «Ciudad» = ‘M
,5′,’131,5′,’Madeira’,’Portugal’,’Mayores de 50 a± adeira’ and «Pais» = ‘Portugal’ and «Rango Edad» =
os’,’Mujer’,’VUELO52951′);                          ‘Mayores de 50 a±os’ and «Sexo» = ‘Mujer’ and «De
                                                   talle Vuelo» = ‘VUELO52951’ and ROWID = ‘AAALVmAAP
                                                   AAAAn8AAI’;

9 filas seleccionadas.

In vs. Exists

He escrito anteriormente en este artículo sobre el uso de NOT IN y NOT EXISTS, mencionando que se trataba de cláusulas diferentes y que, por lo tanto, podían generar diferentes resultados. También mencioné que en su forma natural, IN y EXISTS suponen semánticamente operaciones distintas con diferentes vías de ejecución la mayoría de los casos, pero que pueden implementarse de forma que los resultados sean idénticos y permitir así el sustituir IN por EXISTS y viceversa.

Antes de desglosar la ejecución de IN y EXISTS para una su optimización, menciono la forma de implementarse.                  

IN

SELECT campo1, campo2, …, campoN
FROM   tabla1
WHERE  campoX IN (–subconsulta
                  SELECT campo1
                  FROM   tabla2
                  WHERE  condicion);

Cierto.

Esta consulta podría implementarse como una natural join. Se trata de un ejemplo. Oracle en ambos casos, tratándose de subconsultas simples, las interpreta como joins y resuelven el mismo plan de ejecución.

SELECT t1.campo1, t1.campo2, …, t1.campoN
FROM   tabla1 t1, tabla2 t2
WHERE  t1.campoX = t2.campo1
  AND  condicion;
  

Es decir, ambas consultas combinan los resultados de Tabla1 y Tabla2, unidos por las columnas t1.campoX y t2.campo1 de modo que visualicen únicamente los resultados de la tabla1 que cumplen la condición de combinación.

La interpretación de la consulta, realizada con IN, es la siguiente:

Selecciona el campo1, campo2, … hasta campoN
de la tabla tabla1
unicamente para las filas cuyo campoX se encuentre en la siguiente lista de valores campo1 de la tabla2, que cumplan con la condición condicion.

La forma de resolver esta petición, para el servidor, es consultar la tabla2 en la subconsulta y delimitar después qué campos de la tabla1 cumplen la condición de join.

EXISTS

SELECT campo1, campo2, …, campoN
FROM   tabla1
WHERE  EXISTS  (–subconsulta
                  SELECT NULL
                  FROM   tabla2
                  WHERE  t1.campoX=t2.campo1
                    AND  condicion);

No existe en esta forma una comparación de campos, salvo en la subconsulta. Ésta devuelve NULL (ni siquiera tendría que devolver un valor concreto para ser válida) y su interpretación semántica es la siguiente:

Selecciona el campo1, campo2, … hasta campoN
de la tabla tabla1
unicamente cuando la subconsulta devuelva un registro (aunque éste sea NULL).
La subconsulta, por tanto, devolverá un registro para aquellos elementos de la tabla2 cuyo campo1 sea igual que el campoX de la tabla principal.

Por lo que Oracle entiende que la consulta más idónea a resolverse antes es la principal, resolver los valores de campoX, y posteriormente compararlos con tabla2.

Es decir, en la ejecución con EXISTS, Oracle entiende que la consulta principal debe resolverse primero, y la subconsulta después. Justo al contrario que IN, lo que puede producir diferentes planes de ejecución.

Generalmente, IN resulta más ventajoso cuando la ejecución de la subconsulta tiene poco coste respecto a la principal, y EXISTS cuando sucede al revés.

(…continuará…)

Búsquedas fuzzy, índices context y gestión documental con Oracle. Parte II.

(Continua a partir de parte I).

Recientemente he estado hablando con gente interesada en la gestión de contenidos de documentos y, en general, la mayoría utiliza formatos PDF. El anterior artículo se basaba únicamente en documentos word, y he creído interesante hacer un ejemplo de como Intermedia realiza la indexación de documentos word y PDF de forma totalmente transparente.

Algo importante a mencionar sobre los índices context es que éstos no se mantienen automáticamente con las inserciones. Hay una causa mayor referente al impacto del rendimiento que tendría dicha reconstrucción. En este caso, se incluyen seis nuevas filas BFILE vinculadas a ficheros PDF existentes en el mismo directorio. Posteriormente a la inserción, es preciso reconstruir el índice para recrearlo con la nueva relación de términos.

Para la implementación completa del ejemplo (creación de la tabla, el índice, el objeto directory y temas relativos a privilegios) es preciso seguir los pasos de la Parte I.

Pasos:

Parte I.

       1.- Creación de una tabla con columna BFILE
       2.- Creación de un DIRECTORY oracle para vincular los documentos Word.
       3.- Concesión de privilegios sobre el DIRECTORY al usuario CTXSYS, propietario de Oracle Context.
       4.- Inserción de las filas en la tabla DOCUMENTOS.
       5.- Indexación de la columna BFILE.
       6.- Ejemplos de consultas.

Parte II.

       7.- Inserción de las filas de documentos PDF.
       8.- Reconstrucción del índice context.
       9.- Ejemplos de consultas.


SQL> host dir c:documentos
El volumen de la unidad C no tiene etiqueta.
El número de serie del volumen es: 2416-B169

Directorio de c:documentos

09/05/2005  09:31       <DIR>          .
09/05/2005  09:31       <DIR>          ..
25/04/2005  13:21              271.360 EntornosNoProd_Permisos extendidos.doc
25/04/2005  13:21               31.232 normas_dba.doc
25/04/2005  13:21              379.904 creacion_entorno_bbdd.doc
25/04/2005  13:21              252.928 CREACION_INSTANCIA_DE_ORACLE_EN_UNIX_v2_4.doc              
04/06/2002  21:58            1.479.383 Oracle9i – New Features for Administrators – Vol I.pdf
04/06/2002  21:59            1.542.651 Oracle9i – New Features for Administrators – Vol II.pdf
04/06/2002  21:55            1.244.186 Oracle9i – New Features for Developers – Vol I.pdf
04/06/2002  21:57            2.342.972 Oracle9i – New Features for Developers – Vol II.pdf
04/06/2002  21:13            1.245.254 Oracle9i – New Features Overview – Vol I.pdf
04/06/2002  21:14              801.119 Oracle9i – New Features Overview – Vol II.pdf

10 archivos        9.935.424 bytes
               2 dirs   4.970.479.616 bytes libres

SQL> — Inserción de los seis documentos PDF sobre la tabla
SQL> — y recreación del índice CONTEXT
SQL> ———————————————————————————–
SQL> insert into documentos values
  2  (5,’PDF’,’Oracle9i – New Features for Administrators – Vol I.pdf’,
  3  BFILENAME(‘DOCUMENTOS_WORD’,’Oracle9i – New Features for Administrators – Vol I.pdf’));

1 row created.

SQL> insert into documentos values
  2  (6,’PDF’,’Oracle9i – New Features for Administrators – Vol II.pdf’,
  3  BFILENAME(‘DOCUMENTOS_WORD’,’Oracle9i – New Features for Administrators – Vol II.pdf’));

1 row created.

SQL> insert into documentos values
  2  (7,’PDF’,’Oracle9i – New Features for Developers – Vol I.pdf’,
  3  BFILENAME(‘DOCUMENTOS_WORD’,’Oracle9i – New Features for Developers – Vol I.pdf’));

1 row created.

SQL> insert into documentos values
  2  (8,’PDF’,’Oracle9i – New Features for Developers – Vol II.pdf’,
  3  BFILENAME(‘DOCUMENTOS_WORD’,’Oracle9i – New Features for Developers – Vol II.pdf’));

1 row created.

SQL> insert into documentos values
  2  (9,’PDF’,’Oracle9i – New Features Overview – Vol I.pdf’,
  3  BFILENAME(‘DOCUMENTOS_WORD’,’Oracle9i – New Features Overview – Vol I.pdf’));

1 row created.

SQL> insert into documentos values
  2  (10,’PDF’,’Oracle9i – New Features Overview – Vol II.pdf’,
  3  BFILENAME(‘DOCUMENTOS_WORD’,’Oracle9i – New Features Overview – Vol II.pdf’));

1 row created.

SQL> commit;

Commit complete.

SQL> ALTER INDEX idx_documentos_doc REBUILD;

Index altered.

— Las búsquedas CONTEXT sobre índices domain se realizan con la cláusula CONTAINS
— más documentación al respecto en   http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/cqoper.htm )
————————————————————

— documentos con la palabra ‘tablespace’ cerca de ‘storage’
SQL> select tipo, titulo
  2  from documentos
  3  where CONTAINS(documento, ‘tablespace near storage’) &gt 0;

TIPO TITULO
—- ————————————————————
PDF  Oracle9i – New Features Overview – Vol II.pdf
PDF  Oracle9i – New Features Overview – Vol I.pdf
WORD Creación de instancia Oracle en UNIX.
WORD Normativa de DBA.

— documentos con una palabra parecida a ‘locally’ en un 70%
SQL> select tipo, titulo
  2  from documentos
  3  where CONTAINS(documento, ‘fuzzy(locally, 70, 6, weight)’, 1) &gt 0;

TIPO TITULO
—- ————————————————————
PDF  Oracle9i – New Features Overview – Vol II.pdf
PDF  Oracle9i – New Features Overview – Vol I.pdf
WORD Creación de un entorno de BBDD.

— documentos con una palabra parecida a ‘locally’ en un 60%
— nota: la palabra LOCAL aparece en estos dos nuevos documentos
SQL> select tipo, titulo
  2  from documentos
  3  where CONTAINS(documento, ‘fuzzy(locally, 60, 6, weight)’, 1) &gt 0;

TIPO TITULO
—- ————————————————————
PDF  Oracle9i – New Features Overview – Vol II.pdf
PDF  Oracle9i – New Features Overview – Vol I.pdf
WORD Creación de instancia Oracle en UNIX.
WORD Creación de un entorno de BBDD.
WORD Entornos no producción. Permisos extendidos.

— documentos que hablen sobre ‘listener’
SQL> select tipo, titulo
  2  from documentos
  3  where CONTAINS(documento, ‘about(listener)’) &gt 0;

TIPO TITULO
—- ————————————————————
PDF  Oracle9i – New Features Overview – Vol II.pdf
WORD Creación de instancia Oracle en UNIX.
WORD Normativa de DBA.

— relevancia de documentos que hablan de ‘listener’
— nota: en ‘Normativa de DBA’, efectivamente, se habla bastante menos sobre el listener.
SQL> select tipo, CONTAINS(documento, ‘about(listener)’)||’%’ relevancia, titulo
  2  from documentos
  3  where CONTAINS(documento, ‘about(listener)’) &gt 0;

TIPO RELEVANCIA TITULO
—- ———- ————————————————————
PDF  41%        Oracle9i – New Features Overview – Vol II.pdf
WORD 91%        Creación de instancia Oracle en UNIX.
WORD 23%        Normativa de DBA.


Más información en http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/csql.htm#21732

Defragmentación de un tablespace mediante scripts.

En muchas ocasiones recurro a generar scripts a partir de consultas.
Ejecuto en SQL*Plus algo parecido a:


SQL> select ‘alter package ‘||owner||’.’||object_name||’ compile;’ sentencia
  2> from dba_objects where status=’INVALID’ and object_type =’PACKAGE BODY’;

SENTENCIA
——————————————————————
alter package USUARIO.MAP02_CUACON02 compile;
alter package USUARIO.MAP01_CUACON03 compile;
alter package USUARIO.MAP01_FLUXHISTO03_CI compile;
alter package USUARIO.MAP01_BALANCEBE03 compile;
alter package USUARIO.MAP01_PAHISTOACT03 compile;
alter package USUARIO.MAP01_RAPPELS03 compile;

6 filas seleccionadas.

y simplemente copio y pego las filas como comandos en la consola SQL.

El procedimiento podrá parecer tosco, pero resulta muy versátil ejecutar una consulta que construya dinámicamente todas las sentencias SQL que se precisan para una determinada tarea y, lo más importante, que se adapten a cualquier entorno.

Un ejemplo de ésto podría ser aplicado a la reorganización de los segmentos de un tablespace.

Para la defragmentación de un tablespace, los objetos deben reconstruirse de la siguiente forma:

   – Tablas:   ALTER TABLE xxxx MOVE STORAGE INITIAL tamaño K/M);
   – Índices:   ALTER INDEX xxxx REBUILD [online];
   – Tablas particionadas:  ALTER TABLE xxxx MOVE PARTITION …….;
   – Índices particionados: ALTER INDEX xxxx REBUILD PARTITION …… [online];

Teniendo en cuenta que mover una tabla en Oracle9i deja inválidos los índices, una vez ejecutados estos comandos por todas las tablas e índices del tablespace, sería preciso comprobar qué índices (o partitiones de índice) pueden haber quedado afectados y reconstruirlos.

Habría otro detalle a tener presente: los segmentos reconstruidos sobre el mismo tablespace, dificilmente consiguen liberar el espacio contiguo al final de los ficheros, por lo que sería útil hacer un doble movimiento: mover primero a un tablespace vacío, y posteriormente mover los objetos al tablespace original de vuelta.

Si bien esto no es complicado, sí resulta minucioso. Este script, en este caso, puede resultar tremendamente útil.

Una vez defragmentado el tablespace, es preciso ejecutar el segundo script para reconstruir índices, lo que evitará que éstos se queden inválidos después del transporte.

* Nota: Es preciso tener en cuenta el tamaño total de los objetos. Si éstos son grandes, conviene aumentar el tamaño definido como inicial. Lo mejor es utilizar tablespaces gestionados localmente con gestión automática de las extensiones para que Oracle decida, en tablas grandes, hacer extensiones mayores dinámicamente.


— DEFRAGMENTA_TABLESPACE.SQL —
set verify off
set lines 120
set pages 1000
define tablespace_origen =&TABLESPACE_DATOS
define tablespace_destino=&TABLESPACE_VACIO  
define t_extension_inicial=&TAMAÑO_EXTENSION_INICIAL
select ‘alter table ‘||owner||’.’||table_name||
       ‘ move tablespace &tablespace_destino storage (initial &t_extension_inicial M);’
from dba_tables
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter table ‘||table_owner||’.’||table_name||
       ‘ move partition ‘||partition_name||
       ‘ tablespace &tablespace_destino storage (initial &t_extension_inicial M);’
from dba_tab_partitions
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter index ‘||owner||’.’||index_name||
       ‘ rebuild tablespace &tablespace_destino storage (initial &t_extension_inicial M);’
from dba_indexes
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter index ‘||index_owner||’.’||index_name||
       ‘ rebuild partition ‘||partition_name||
       ‘ tablespace &tablespace_destino storage (initial &t_extension_inicial M);’
from dba_ind_partitions
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter table ‘||owner||’.’||table_name||
       ‘ move tablespace &tablespace_origen storage (initial &t_extension_inicial M);’
from dba_tables
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter table ‘||table_owner||’.’||table_name||
       ‘ move partition ‘||partition_name||
       ‘ tablespace &tablespace_origen storage (initial &t_extension_inicial M);’
from dba_tab_partitions
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter index ‘||owner||’.’||index_name||
       ‘ rebuild tablespace &tablespace_origen storage (initial &t_extension_inicial M);’
from dba_indexes
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter index ‘||index_owner||’.’||index_name||
       ‘ rebuild partition ‘||partition_name||
       ‘ tablespace &tablespace_origen storage (initial &t_extension_inicial M);’
from dba_ind_partitions
where upper(tablespace_name)=’&tablespace_origen’;

— RECONSTRUYE_INDICES.SQL —
select ‘alter index ‘||index_owner||’.’||index_name||
       ‘ rebuild partition ‘||partition_name||
       ‘ tablespace &tablespace_origen storage (initial &t_extension_inicial M);’ SENTENCIA
from dba_ind_partitions
where upper(tablespace_name)=’&tablespace_origen’
union all
select ‘alter index ‘||owner||’.’||index_name||’ rebuild;’
from dba_indexes
where status=’UNUSABLE’
union all
select ‘alter index ‘||index_owner||’.’||index_name||
       ‘ rebuild partition ‘||partition_name||’;’
from dba_ind_partitions
where status=’UNUSABLE’;

TEST
====
SQL> @c:reorganiza_tablespace
Introduzca un valor para tablespace_datos: DM_ACTIVIDAD01
Introduzca un valor para tablespace_vacio: TS_VACIO
Introduzca un valor para tama±o_extension_inicial: 1

‘ALTERTABLE’||OWNER||’.’||TABLE_NAME||’MOVETABLESPACETS_VACIOSTORAGE(INITIAL1M);’
————————————————————————————————————–
alter table USUARIO.TABLA_XXX02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXX02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXASMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXXX02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXXXX02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXPMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXP02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXAS02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXON02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXPMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXASMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXCIBOSMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXCIBOSMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXCIBOS02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXTASMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXTAS02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXTASMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXXXXY02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXACION02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXACIONMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXACIONMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXTVS02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXTVSMA02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXTVSMES02 move tablespace TS_VACIO storage (initial 1 M);
alter table USUARIO.TABLA_XXXYY02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXY02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXASMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXX02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXX02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXPMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXP02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXAS02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXON02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXPMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXASMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXCIBOSMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXCIBOSMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXCIBOS02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXTASMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXTAS02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXTASMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXX02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXACION02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXACIONMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXACIONMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXTVS02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXTVSMA02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);
alter table USUARIO.TABLA_XXXTVSMES02 move tablespace DM_ACTIVIDAD01 storage (initial 1 M);

52 filas seleccionadas.

Una vez ejecutados todos estos comandos, lanzamos el script que consulta los índices inválidos con el fin de recompilarlos.


SQL> @c:reconstruye_indices
SENTENCIA
——————————————————————-
alter index USUARIO.INDICEXXXOSP02_01 rebuild;
alter index USUARIO.INDICEXXXURAS02_01 rebuild;
alter index USUARIO.INDICEXXXSION02_01 rebuild;
alter index USUARIO.INDICEXXXOSPMES02_01 rebuild;
alter index USUARIO.INDICEXXXRECIBOS02_01 rebuild;
alter index USUARIO.INDICEXXXABTAS02_01 rebuild;
alter index USUARIO.INDICEXXXDA02_01 rebuild;
alter index USUARIO.INDICEXXXCIACION02_01 rebuild;
alter index USUARIO.INDICEXXXTVS02_01 rebuild;
alter index USUARIO.INDICEXXXTVSMA02_01 rebuild;
alter index USUARIO.INDICEXXXTVSMES02_01 rebuild;

11 filas seleccionadas.

Generación de XML mediante query.

En algunos proyectos en los que he participado, el equipo de front-end, que desarrollaba la interfaz web sobre plataforma .NET, tenía serios problemas para recuperar un conjunto de filas con un objeto tipo array incrustado.

La consulta debía devolver un vector de registros, algunos de ellos formados por una lista de valores. En aquel entonces, el driver de .NET no podía gestionar objetos UDT (tipos de datos definidos por el usuario) y hubo que comprar un componente aparte que tampoco dio buen resultado,pues no soportaba consultas a NESTED TABLES.

Una de las soluciones propuestas fue devolver el resultado (se trataba de fichas de clientes) en formato XML. La idea era buena, pero había alguno que pretendía «decodificar» columnas y filas para elaborar el resultado… ¡escribiendo un procedure con substrs!

No hace falta. La funcionalidad de crear UDT tiene una importancia relevante en las conversiones a XML. Definiendo los tipos como OBJECT y TABLE OF OBJECT podemos crear una estructura de datos facilmente exportable a XML tal y como muestra el ejemplo.

Las tablas tomadas como referencia corresponden a EMP y DEPT del esquema de ejemplo SCOTT.


SQL> create or replace type r_empleado is object(
  2     NUM_EMP          NUMBER(4),
  3     NOMBRE_EMP       VARCHAR2(10),
  4     OFICIO_EMP       VARCHAR2(9),
  5     JEFE_EMP         NUMBER(4),
  6     FCONTRATO_EMP    DATE,
  7     SALARIO_EMP      NUMBER(7,2),
  8     COMISION_EMP     NUMBER(7,2),
  9     DEPARTAMENTO_EMP NUMBER(2));
10  /

Tipo creado.

SQL>
SQL> create or replace type t_empleado is table of r_empleado;
  2  /

Tipo creado.

SQL>
SQL> create or replace type r_dept is object(
  2     NUM_DEPT     NUMBER(2),
  3     NOMBRE_DEPT  VARCHAR2(14),
  4     CIUDAD_DEPT  VARCHAR2(13),
  5     empleados    t_empleado);
  6  /

Tipo creado.

SQL>
SQL> create or replace type t_dept is table of r_dept;
  2  /

Tipo creado.

SQL>
SQL>
SQL> select sys_xmlgen(
  2               r_dept(dept.deptno, dept.dname, dept.loc,
  3                 cast(multiset(select emp.*
  4                                 from emp
  5                                where emp.deptno = dept.deptno) as t_empleado)
  6                    ),xmlformat.createFormat(‘DEPARTAMENTO’)).getClobVal() as XML
  7  from dept
  8  where deptno=10;

XML
——————————————————————————–
<?xml version=»1.0″?>
<DEPARTAMENTO>
<NUM_DEPT>10</NUM_DEPT>
<NOMBRE_DEPT>ACCOUNTING</NOMBRE_DEPT>
<CIUDAD_DEPT>NEW YORK</CIUDAD_DEPT>
<EMPLEADOS>
  <R_EMPLEADO>
   <NUM_EMP>7782</NUM_EMP>
   <NOMBRE_EMP>CLARK</NOMBRE_EMP>
   <OFICIO_EMP>MANAGER</OFICIO_EMP>
   <JEFE_EMP>7839</JEFE_EMP>
   <FCONTRATO_EMP>09/06/81</FCONTRATO_EMP>
   <SALARIO_EMP>2450</SALARIO_EMP>
   <DEPARTAMENTO_EMP>10</DEPARTAMENTO_EMP>
  </R_EMPLEADO>
  <R_EMPLEADO>
   <NUM_EMP>7839</NUM_EMP>
   <NOMBRE_EMP>KING</NOMBRE_EMP>
   <OFICIO_EMP>PRESIDENT</OFICIO_EMP>
   <FCONTRATO_EMP>17/11/81</FCONTRATO_EMP>
   <SALARIO_EMP>5000</SALARIO_EMP>
   <DEPARTAMENTO_EMP>10</DEPARTAMENTO_EMP>
  </R_EMPLEADO>
  <R_EMPLEADO>
   <NUM_EMP>7934</NUM_EMP>
   <NOMBRE_EMP>MILLER</NOMBRE_EMP>
   <OFICIO_EMP>CLERK</OFICIO_EMP>
   <JEFE_EMP>7782</JEFE_EMP>
   <FCONTRATO_EMP>23/01/82</FCONTRATO_EMP>
   <SALARIO_EMP>1300</SALARIO_EMP>
   <DEPARTAMENTO_EMP>10</DEPARTAMENTO_EMP>
  </R_EMPLEADO>
</EMPLEADOS>
</DEPARTAMENTO>

NOT IN y NOT EXISTS no son lo mismo.

Aunque no lo parezca, NOT IN y NOT EXISTS no son cláusulas equivalentes. IN y EXISTS devuelven el mismo conjunto de resultados, pero su semántica es completamente distinta. Aun así, los programadores la utilizan indistintamente ya que se han acostumbrado a trabajar con una de las dos y realizan de igual modo la negación.

Por un lado, IN solicita aquellos registros cuyo valor exista dentro de un conjunto de valores. Por otro lado, la cláusula EXISTS incluye el registro si la subconsulta siguente devuelve un valor, sea cual sea, incluído NULL.

De este modo, la sintaxis de IN y EXISTS podrían aprobar los mismos registros si la consulta está bien construída, aunque los planes de ejecución sean completamente distintos.

NOT IN y NOT EXISTS no son sinónimos. El valor NULL determina la diferencia.

Dada la siguiente consulta con NOT IN:


SQL> select count(*) from clientes
  2  where ciu_id_ciudad not in (select ciu_id_ciudad from agencias);

  COUNT(*)
———-
         0

Y su equivalente con NOT EXISTS

SQL> select count(*) from clientes c
  2  where not exists (select a.ciu_id_ciudad from agencias a where c.ciu_id_ciudad=a.ciu_id_ciudad);

  COUNT(*)
———-
         0

Los resultados son idénticos: Efectivamente, no existe ningún cliente que viva en una ciudad donde no haya una agencia. En este caso, hay clientes y agencias en todas las ciudades de la base de datos.

No obstante, si las tablas contienen elementos nulos, las dos operaciones devolverán conjuntos de resultados distintos.

NOT IN y NOT EXISTS no son la misma cláusula con sintaxis distinta.

SQL> update clientes
  2  set ciu_id_ciudad=null
  3  where round(rownum/1000)=0;

499 filas actualizadas.

SQL> select count(*) from clientes
  2  where ciu_id_ciudad not in (select ciu_id_ciudad
  3                              from agencias);

  COUNT(*)
———-
         0

SQL> select count(*) from clientes c
  2  where not exists (select a.ciu_id_ciudad
  3   from agencias a where c.ciu_id_ciudad=a.ciu_id_ciudad);

  COUNT(*)
———-
       499

Así, NOT IN se basa en la consulta de todos los valores respecto un valor concreto para determinar que no está incluido en la lista. Los valores nulos se computan como FALSO y no incluyen el elemento en la selección.

NOT EXISTS se basa en la consulta de un valor y su no existencia en un subconjunto de registros. Los valores nulos se computan como VERDADERO, e incluyen el elemento en la selección.

PL/SQL y ejecuciones en host.

Por un motivo de seguridad, desde PL/SQL no es posible ejecutar llamadas al host. En principio, ningún módulo programado en PL/SQL debería ejecutar nada en sistema operativo. No obstante, seguro que cualquiera es capaz de recordar alguna situación concreta en la que poder invocar un «copy» o quizás ejecutar algún fichero .exe hubiera sido de gran ayuda.

Existe una forma de dar un rodeo a este escenario.

Oracle permite la llamada a procedimientos externos mediante un servicio extproc que debe configurarse en el fichero de alias de servicios «tnsnames.ora» para el cliente, y en el de configuración del listener «listener.ora» en el lado del servidor.

Los ficheros tendrán este aspecto.

Nota: puede haber cambios por la versión del servidor. El ejemplo se implementó sobre Oracle 8.1.7.

Fichero LISTENER.ORA
LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
      )
    )
  )
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = extproc_agent)
      (ORACLE_HOME = E:oracleora92)
      (PROGRAM = extproc)
      (ENVS = «EXTPROC_DLLS=ANY»)

    )
  )


Fichero TNSNAMES.ORA
EXTPROC_CONNECTION_DATA =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = IPC)(Key = EXTPROC))
    
)
    (CONNECT_DATA =
      (SID = extproc_agent)
    )
  )

Con esta parametrización del listener, y la definición de un servicio para llamadas a ‘extproc’, Oracle es capaz de invocar desde PL/SQL librerías externas programadas en C o PASCAL.

Pasos:

      1.- Descarga de un compilador de C++. Este ejemplo utiliza C++Compiler de Borland.
      2.- Creación de un programa en C que ejecuta en S.O. un comando dado.
      3.- Compilación del programa e implementación de las librerías.
      4.- Desde Oracle, creación del objeto LIBRARY (biblioteca).
      5.- Creación de un procedimiento PL/SQL que invoque a una función de esa biblioteca.


Creación del programa shell.c
—————————–
   #include <windows.h>    
   #include <stdio.h>    
   #include <stdlib.h>      

   void __declspec(dllexport) sh(char *);    
   void sh(char *cmd)    
   {    
       system(cmd);    
   }

Compilación del código C y creación de las librerías  .lib y .dll
—————————————————————–
  bcc32 -WD shell.c    
  implib shell.lib shell.dll    

Creación del objeto LIBRARY, en la consola de SQL.
————————————————–
SQL> create library shell_lib is ‘C:BorlandBCC55shell.dll’;
  2  /

Biblioteca creada.

De modo que ya es posible crear un procedimiento PL/SQL que referencie externamente a esa librería. Ésta se encargará de ejecutar en host el parámetro que le incluyamos. Por ejemplo, un «copy».
————————————————————————————————–
SQL> create or replace procedure shell (
  2         cmd IN varchar2)
  3  as external
  4       library shell_lib
  5       name «_sh»
  6       language C
  7       parameters (cmd string);

  8  /

Procedimiento creado.

SQL>

El testeo podrá hacerse con un simple ‘exec’ desde SQL*Plus, ya que equivale a «BEGIN shell(‘parametro’); END;»
—————————————————————————————————————
SQL> exec shell(‘copy c:test*.* c:destino’);

Procedimiento PL/SQL terminado correctamente.


Funciona.

Búsquedas fuzzy, índices context y gestión documental con Oracle. Parte I.

(Continua en parte II).

En muchas ocasiones he podido ver como el uso de las funcionalidades de gestión documental de Oracle era un misterio para los directores de proyecto, que solían confiar para estas tareas en otras herramientas documentales, analistas y programadores.

Éstos últimos, tenían serios problemas en realizar búsquedas por contenido sobre campos almacenados como CLOB, ya que en la mayoría de casos utilizaban la cláusula LIKE con comodines a ambos lados, lo cual impedía el uso por parte del servidor de cualquier índice asociado a esa columna. Otras veces la solución implementada era aun peor (lowers, uppers, substr, instr… ¡sobre un campo que puede llegar a tener hasta 4Gb de capacidad!). Terrible.

Oracle dispone de una potencia de gestión documental principalmente basada en el uso de índices domain. Se trata de índices por palabras (también conocidos como índices CONTEXT) que permiten a Oracle, en una columna de CLOBs o BFILEs, tener una relación del contenido de estos objetos.

El siguiente ejemplo es un pequeño ejercicio basado en el uso de las funcionalidades documentales del servidor Oracle9i.

Pasos:

       1.- Creación de una tabla con columna BFILE
       2.- Creación de un DIRECTORY oracle para vincular los documentos Word.
       3.- Concesión de privilegios sobre el DIRECTORY al usuario CTXSYS, propietario de Oracle Context.
       4.- Inserción de las filas en la tabla DOCUMENTOS.
       5.- Indexación de la columna BFILE.
       6.- Ejemplos de consultas.


SQL> — Inicialmente creo una tabla DOCUMENTOS donde vincularé los documentos en formato word
SQL> — para su posterior indexación

SQL> create table documentos(
  2        id number constraint pk_documentos primary key,
  3        tipo char(4),
  4        titulo varchar2(60) not null,
  5        documento bfile);

Tabla creada.

SQL> — Es preciso crear un objeto DIRECTORY, vinculado al directorio de sistema operativo
SQL> — donde se encuentran los documentos word.

SQL> create or replace directory documentos_word as ‘c:documentos’;

Directorio creado.

SQL> grant read on directory documentos_word to CTXSYS;

Concesión terminada correctamente.

SQL> host dir c:documentos
El volumen de la unidad C no tiene etiqueta.
El número de serie del volumen es: 2416-B169

Directorio de c:documentos

09/05/2005  09:31       <DIR>          .
09/05/2005  09:31       <DIR>          ..
25/04/2005  13:21              271.360 EntornosNoProd_Permisos extendidos.doc
25/04/2005  13:21               31.232 normas_dba.doc
25/04/2005  13:21              379.904 creacion_entorno_bbdd.doc
25/04/2005  13:21              252.928 CREACION_INSTANCIA_DE_ORACLE_EN_UNIX_v2_4.doc
               4 archivos        935.424 bytes
               2 dirs   4.859.199.488 bytes libres

SQL> — Inserción de los cuatro documentos sobre la tabla
SQL> — y creación del índice CONTEXT (la cláusula es INDEXTYPE IS ctxsys.context)
SQL> ———————————————————————————–
SQL> insert into documentos
  2  values (1,’WORD’,’Entornos no producción. Permisos extendidos.’,
  3          BFILENAME(‘DOCUMENTOS_WORD’,’EntornosNoProd_Permisos extendidos.doc’));

1 fila creada.

SQL> insert into documentos
  2  values (2,’WORD’,’Normativa de DBA.’,
  3          BFILENAME(‘DOCUMENTOS_WORD’,’normas_dba.doc’));

1 fila creada.

SQL> insert into documentos
  2  values (3,’WORD’,’Creación de un entorno de BBDD.’,
  3          BFILENAME(‘DOCUMENTOS_WORD’,’creacion_entorno_bbdd.doc’));

1 fila creada.

SQL> insert into documentos
  2  values (4,’WORD’,’Creación de instancia Oracle en UNIX.’,
  3          BFILENAME(‘DOCUMENTOS_WORD’,’CREACION_INSTANCIA_DE_ORACLE_EN_UNIX_v2_4.doc’));

1 fila creada.

SQL> CREATE INDEX idx_documentos_doc ON documentos(DOCUMENTO) INDEXTYPE IS ctxsys.context;

Índice creado.

— Las búsquedas CONTEXT sobre índices domain se realizan con la cláusula CONTAINS
— más documentación al respecto en   http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/cqoper.htm )
————————————————————

— documentos con la palabra ‘tablespace’ cerca de ‘storage’
SQL> select titulo
  2  from documentos
  3  where CONTAINS(documento, ‘tablespace near storage’) &gt 0;

TITULO
————————————————————
Creación de instancia Oracle en UNIX.
Normativa de DBA.

— documentos con una palabra parecida a ‘locally’ en un 70%
SQL> select titulo
  2  from documentos
  3  where CONTAINS(documento, ‘fuzzy(locally, 70, 6, weight)’, 1) &gt 0;

TITULO
————————————————————
Creación de un entorno de BBDD.

— documentos con una palabra parecida a ‘locally’ en un 60%
— nota: la palabra LOCAL aparece en estos dos nuevos documentos
SQL> select titulo
  2  from documentos
  3  where CONTAINS(documento, ‘fuzzy(locally, 60, 6, weight)’, 1) &gt 0;

TITULO
————————————————————
Creación de instancia Oracle en UNIX.
Creación de un entorno de BBDD.
Entornos no producción. Permisos extendidos.

— documentos que hablen sobre ‘listener’
SQL> select titulo
  2  from documentos
  3  where CONTAINS(documento, ‘about(listener)’) &gt 0;

TITULO
————————————————————
Creación de instancia Oracle en UNIX.
Normativa de DBA.

— relevancia de documentos que hablan de ‘listener’
— nota: en ‘Normativa de DBA’, efectivamente, se habla bastante menos sobre el listener.
SQL> select CONTAINS(documento, ‘about(listener)’)||’%’ relevancia, titulo
  2  from documentos
  3  where CONTAINS(documento, ‘about(listener)’) &gt 0;

RELEVANCIA TITULO
———- ————————————————————
78%        Creación de instancia Oracle en UNIX.
20%        Normativa de DBA.


Más información en http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/csql.htm#21732

Uso de cursores y bulk insert.

Uno de los muchos mitos en oracle es que el uso de cursores perjudica seriamente el rendimiento. Incluso he visto algunos proyectos en los que se había prohibido el uso de esta técnica.

El impacto sobre el rendimiento es cierto en parte, ya que el servidor trata individualmente cada registro y, por tanto, esto debería implementarse únicamente cuando fuera estrictamente necesario (sql dinámico, iteraciones dentro del fetch, etc.) utilizando, siempre que sea posible, SQL estándar.

No obstante, hay situaciones en las que no es posible el uso de SQL convencional. Por ejemplo, cuando en una inserción es preciso controlar los errores e insertarlos en otra tabla. Ante este caso, los procedimientos ETL filtran previamente las filas, las mueven a otra tabla, y cuando las filas ya están totalmente «limpias» de errores, entonces realizan
INSERT INTO tabla SELECT * …

Oracle, a partir de la versión 9i, dispone de la funcionalidad de BULK sql, es decir, tratamiento de
conjuntos de filas «a montón». Realizar bulk inserts puede resultar muy práctico en el caso anterior, ya que las filas en el cursor no se ejecutan individualmente y no es preciso el filtrado previo de las filas.

Así pues, ante la necesidad de insertar en una tabla un volúmen de filas, omitir los errores e insertar las filas erróneas en una tabla, el siguiente código PL/SQL puede ser de gran utilidad, ya que su coste de ejecución resulta idéntico al del INSERT.

— Código PL/SQL para insertar en una tabla, manejando errores en filas, con BULK INSERT
————————————————————————————–
DECLARE
    type clientes_array is table of BK_CLIENTE_BDM_03%rowtype index by binary_integer;
    registros  clientes_array;
    errores NUMBER;
    dml_errores EXCEPTION;
    contador_errores number := 0;
    PRAGMA exception_init(dml_errores, -24381);
    cursor c is select * from BK_CLIENTE_BDM_03;
BEGIN
    open c;
    loop
      fetch c BULK COLLECT INTO registros LIMIT 1000;      begin
       FORALL i IN 1 .. registros.count SAVE EXCEPTIONS
         insert into TRASPASO_BD_CLIENTE values registros(i);
      EXCEPTION
        WHEN dml_errores THEN
          errores := SQL%BULK_EXCEPTIONS.COUNT;          contador_errores := contador_errores + errores;
          FOR i IN 1..errores LOOP
            dbms_output.put_line (‘Se encontro el error ‘||;SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
                    ‘:’||SQL%BULK_EXCEPTIONS(i).ERROR_CODE);          end loop;
     end;
     exit when c%notfound;
    END LOOP;
    close c;
    dbms_output.put_line( contador_errores );
end;

Su coste de ejecución es idéntico al de ejecutar:

insert into TABLA_DESTINO select * from TABLA_ORIGEN;

Comparativa de costes de ejecución.
******************************


insert into TRASPASO_BD_CLIENTE select * from BK_CLIENTE_BDM_03

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.00          0          1          0           0
Execute      1      4.91     102.61      15713      31424      88189      600000
Fetch        0      0.00       0.00          0          0          0           0
——- ——  ——– ———- ———- ———- ———-  ———-
total        2      4.91     102.62      15713      31425      88189      600000

********************************************************************************

DECLARE
        type clientes_array is table of BK_CLIENTE_BDM_03%rowtype index by binary_integer;
        registros   clientes_array;
        errores NUMBER;
        dml_errores EXCEPTION;
        contador_errores number := 0;
        PRAGMA exception_init(dml_errores, -24381);
        cursor c is select * from BK_CLIENTE_BDM_03;
BEGIN
       open c;
       loop
           fetch c BULK COLLECT INTO registros LIMIT 1000;
           begin
              FORALL i IN 1 .. registros.count SAVE EXCEPTIONS
                 insert into TRASPASO_BD_CLIENTE values registros(i);
           EXCEPTION
               WHEN dml_errores THEN
                    errores := SQL%BULK_EXCEPTIONS.COUNT;
                    contador_errores := contador_errores + errores;
                    FOR i IN 1..errores LOOP
                        dbms_output.put_line
                        (‘Se encontrs el error ‘
                          SQL%BULK_EXCEPTIONS(i).ERROR_INDEX
                       ‘: ‘
                          SQL%BULK_EXCEPTIONS(i).ERROR_CODE );
                    end loop;
          end;
          exit when c%notfound;
        END LOOP;
        close c;
        dbms_output.put_line( contador_errores
end;

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.01          0          0          0           0
Execute      1      2.40       2.46          0          0          0           1
Fetch        0      0.00       0.00          0          0          0           0
——- ——  ——– ———- ———- ———- ———-  ———-
total        2      2.40       2.47          0          0          0           1

SELECT * FROM BK_CLIENTE_BDM_03

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      601     20.90      65.99      15712      16373          0      600000
——- ——  ——– ———- ———- ———- ———-  ———-
total      603     20.90      65.99      15712      16373          0      600000

INSERT INTO TRASPASO_BD_CLIENTE
VALUES
(:B1 ,:B2 ,:B3 ,:B4 ,:B5 ,:B6 ,:B7 ,:B8 ,:B9 ,:B10 ,:B11 ,:B12 ,:B13 ,:B14 ,
  :B15 ,:B16 ,:B17 ,:B18 ,:B19 ,:B20 ,:B21 ,:B22 ,:B23 ,:B24 ,:B25 ,:B26 ,
  :B27 ,:B28 ,:B29 ,:B30 ,:B31 ,:B32 ,:B33 ,:B34 ,:B35 ,:B36 ,:B37 ,:B38 ,
  :B39 ,:B40 ,:B41 ,:B42 ,:B43 ,:B44 ,:B45 ,:B46 ,:B47 )

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.00          0          0          0           0
Execute    600     17.40      26.39          0      15835      87647      600000
Fetch        0      0.00       0.00          0          0          0           0
——- ——  ——– ———- ———- ———- ———-  ———-
total      601     17.40      26.39          0      15835      87647      600000

********************************************************************************

RESUMEN:

Insert Select  –>   Execute      1      4.91    102.61      15713      31424      88189      600000

BULK INSERT   –>   Execute      1      2.40       2.46          0          0          0           1
+                   Fetch      601     20.90      65.99      15712      16373          0      600000
+                   Execute    600     17.40      26.39          0      15835      87647      600000

Tiempo insert select: 1’42»  (mismos bloques físicos y en caché)
Tiempo bulk insert:  1’33»  (mismos bloques físicos y en caché)
No hay diferencia…