java y oracle 11g

331
PEDRO SALGUEIRO GÓMEZ 2011

Upload: vianney-martinez-espinosa

Post on 27-Oct-2015

811 views

Category:

Documents


13 download

TRANSCRIPT

Page 1: Java y Oracle 11g

PEDRO SALGUEIRO GÓMEZ 2011

Page 2: Java y Oracle 11g
Page 3: Java y Oracle 11g

Tabla de contenidos

I. NOCIONES SOBRE BASES DE DATOS Y ORACLE 6

1. Arquitectura de Oracle Database 11g....................................... 6 1.1. Fundamentos de bases de datos. ........................................ 6 1.2. Fundamentos de bases de datos relacionales....................... 8 1.3. Bases de datos e instancias en Oracle. .............................. 11 1.4. Organización de las bases de datos en Oracle. .................. 11 1.5. Selección de arquitecturas y opciones. ............................. 14

2. Instalar Oracle Database 11g y crear una base de datos .......... 15 2.1. Descripción de la licencia y opciones de instalación. ........ 15

3. Servidor de Oracle ................................................................ 24 3.1. Elementos del servidor Oracle ......................................... 24 3.2. Conexiones. .................................................................... 24 3.3. Estructura de las bases de datos Oracle. ........................... 25 3.4. Instancia de la base de datos. ........................................... 25 3.5. Procesamiento de instrucciones SQL. .............................. 26 3.6. Archivos de inicialización ............................................... 27 3.7. Arranque y parada de la base de datos.............................. 28

4. Introducción a los conceptos del sistema Oracle .................... 29 4.1. Almacenamiento. ............................................................ 29 4.2. Transacciones. ................................................................ 29 4.3. Usuarios. ........................................................................ 29 4.4. Pérdidas de información. ................................................. 30 4.5. Copias de seguridad. ....................................................... 30 4.6. Bases de datos distribuidas. ............................................. 30 4.7. Herramientas de Oracle. .................................................. 30

II. LENGUAJE DE CONSULTAS .................................. 32 1. SQL para Oracle ................................................................... 32

1.1. Introducción. .................................................................. 32 1.2. Código SQL y normas de escritura. ................................. 32 1.3. SQL*Plus. ...................................................................... 32 1.4. Versión gráfica de SQL*Plus. ......................................... 33 1.5. iSQL*Plus. ..................................................................... 33

2. Estructura del lenguaje SQL.................................................. 34 2.1. Tipos de datos................................................................. 34 2.2. Operadores ..................................................................... 35 2.3. Funciones predefinidas.................................................... 36

3. Trabajando con objetos ......................................................... 52 3.1. Introducción. .................................................................. 52 3.2. Diccionario de datos de Oracle. ....................................... 52 3.3. Crear y usar bases de datos. ............................................. 53 3.4. Crear y usar tablespaces. ................................................. 53 3.5. Crear esquemas. .............................................................. 55 3.6. Crear y usar tablas. ......................................................... 56 3.7. Restricciones. ................................................................. 59 3.8. Crear y eliminar índices. ................................................. 61 3.9. Uso de tablas particionadas. ............................................ 64 3.10. Clústeres (o cubos). ....................................................... 67 3.11. Secuencias. ................................................................... 68 3.12. Sinónimos..................................................................... 69 3.13. Inserción de registros. ................................................... 69 3.14. Actualización de registros. ............................................ 72 3.15. Eliminación de registros. ............................................... 72 3.16. Combinar registros con MERGE. .................................. 73

4. Consultas de selección .......................................................... 73 4.1. Consultas básicas. ........................................................... 74 4.2. Alias............................................................................... 74 4.3. Ordenar los registros. ...................................................... 74 4.4. Consultas con predicado.................................................. 75 4.5. Recuperación de valores calculados. ................................ 75 4.6. La cláusula «WHERE». .................................................. 76 4.7. Consultas que incluyen nulos .......................................... 76

5. Criterios de selección ............................................................ 76 5.1. Operadores lógicos. ........................................................ 76 5.2. Intervalos de valores. ...................................................... 77 5.3. El operador «Like». ........................................................ 77 5.4. El operador «In». ............................................................ 77

6. Consultas sobre más de una tabla .......................................... 78

6.1. Reunión de una tabla consigo misma. .............................. 78 6.2. Consultas de unión internas. ............................................ 78 6.3. Consultas con operaciones de conjuntos........................... 80

7. Agrupaciones ....................................................................... 80 7.1. Funciones de agregado. ................................................... 80 7.2. La cláusula «Group by». ................................................. 83

8. Subconsultas ........................................................................ 84 8.1. Introducción. .................................................................. 84 8.2. Recuperación de datos con subconsulta............................ 84 8.3. Subconsultas correlacionadas. ......................................... 85 8.4. Subconsultas con operador de comparación distinto de

«IN». .................................................................................... 85 8.5. Funciones de agregado en subconsultas. .......................... 86 8.6. Subconsultas con «EXISTS». .......................................... 86 8.7. Expresiones de columna con subconsultas ....................... 86 8.8. Subconsultas como origen de registros para «FROM». ..... 87

9. Vistas ................................................................................... 87 9.1. Introducción. .................................................................. 87 9.2. Crear y consultar vistas. .................................................. 88 9.3. Ejecución de comandos DML sobre vistas. ...................... 88 9.4. Estabilidad de una vista. .................................................. 88 9.5. Mostrar la lista de vistas. ................................................. 89 9.6. Borrar vistas. .................................................................. 89

10. Comandos internos en SQL*PLUS e iSQL*Plus .................. 89 10.1. Variables de sustitución. ................................................ 89 10.2. Comando «SET». .......................................................... 90 10.3. Encabezado y pie de informe. ........................................ 90 10.4. Comando «COLUMN». ................................................ 91 10.5. Comando «BREAK». .................................................... 91 10.6. Comando «COMPUTE». .............................................. 92 10.7. Guardar consultas en ficheros. ....................................... 93 10.8. Redirigir la salida de SQL*Plus con «SPOOL». ............. 93

11. Consultas avanzadas ........................................................... 94 11.1. Consultas con «ROWNUM». ........................................ 94 11.2. Consultas con «ROWID». ............................................. 94 11.3. Consultas con «RANK». ............................................... 95 11.4. Consultas sobre estructuras jerárquicas. ......................... 96 11.5. Consultas de agrupación avanzada. ................................ 98

III. PL/SQL .................................................................... 101 1. Estructura del lenguaje PL/SQL .......................................... 101

1.1. Fundamentos de PL/SQL. ............................................. 101 1.2. Estructuras de control en PL/SQL. ................................. 102

2. Bloques PL/SQL................................................................. 103 2.1. Introducción. ................................................................ 103 2.2. Estructura de un Bloque. ............................................... 103 2.3. Sección de declaración de variables. .............................. 104 2.4. El paquete «DBMS_OUTPUT». .................................... 105 2.5. Asignación de variables................................................. 106

3. Excepciones en PL/SQL. .................................................... 107 3.1. Manejo de excepciones. ................................................ 107 3.2. Excepciones predefinidas. ............................................. 107 3.3. Excepciones definidas por el usuario. ............................ 108 3.4. Uso de «SQLCODE» y «SQLERRM». .......................... 109 3.5. Excepciones personalizadas en PL/SQL. ........................ 109 3.6. Propagación de excepciones en PL/SQL. ....................... 109

4. Cursores ............................................................................. 109 4.1. Cursores implícitos. ...................................................... 110 4.2. Cursores explícitos. ....................................................... 110 4.3. Cursores con parámetros. .............................................. 111 4.4. Cursores de actualización. ............................................. 111

5. Subprogramas en PL/SQL ................................................... 112 5.1. Permisos requeridos. ..................................................... 112 5.2. Procedimientos, funciones y paquetes. ........................... 113 5.3. Procedimientos almacenados. ........................................ 113 5.4. Funciones en PL/SQL. .................................................. 114 5.5. Subprogramas en bloques procedimentales. ................... 115 5.6. Depurando procedimientos. ........................................... 115

Page 4: Java y Oracle 11g

5.7. Paquetes en PL/SQL. .................................................... 115 5.8. Viendo el código fuente de objetos procedimentales....... 118 5.9. Compilando procedimientos, funciones y paquetes......... 118

6. Transacciones ..................................................................... 119 6.1. Estado de los datos durante la transacción. ..................... 119 6.2. Control de transacciones en PL/SQL. ............................ 119 6.3. Puntos de ruptura. ......................................................... 120 6.4. Transacciones autónomas .............................................. 120

7. Triggers ............................................................................. 121 7.1. Permisos requeridos. ..................................................... 121 7.2. Tipos de triggers. .......................................................... 121 7.3. Triggers asociados a tablas. ........................................... 122 7.4. Triggers para eventos DDL. .......................................... 125 7.5. Triggers para eventos del sistema. ................................. 128 7.6. Triggers de sustitución. ................................................. 129 7.7. Activar y desactivar triggers. ......................................... 130

8. Tipos de datos complejos y operaciones masivas. ................ 131 8.1. Registros (RECORD). ................................................... 131 8.2. Arrays asociativos (TABLE). ........................................ 132 8.3. Arrays variables (VARRAY)......................................... 133 8.4. Acceso masivo a los datos (BULK COLLECT). ............ 135 8.5. Funciones en línea. ....................................................... 136 8.6. Instrucción «FORALL». ............................................... 137 8.7. Objetos grandes (LOB). ................................................ 138

9. SQL Dinámico ................................................................... 142 9.1. Sentencias DML con SQL dinámico. ............................. 143 9.2. Cursores con SQL dinámico. ......................................... 143 9.3. Un ejemplo de cómo usar y cómo no usar SQL dinámico.146

10. PL/SQL y Java ................................................................. 146 10.1. Creación de Objetos Java en la base de datos ORACLE.146 10.2. Ejecución de programas Java con PL/SQL ................... 147 10.3. Correspondencia de tipos entre Java y Oracle. .............. 148 10.4. Paso de cursores Oracle a métodos de Java. ................. 148 10.5. Paso de objetos Oracle a métodos de Java. ................... 149 10.6. Paso de arrays desde un programa Java a un

procedimiento almacenado de Oracle. .................................. 150 10.7. Cómo pasar y retornar un array de objetos a través de un procedimiento almacenado................................................... 151 10.8. Paquete «DBMS_JAVA». ........................................... 152

IV. CARACTERÍSTICAS DE ORACLE GRID........... 155 1. Arquitectura de Rejilla ........................................................ 155

1.1. Hardware y elementos de configuración del sistema operativo. ............................................................................ 155 1.2. Añadiendo servidores a la rejilla. ................................... 157 1.3. Compartir datos entre la rejilla. ..................................... 157 1.4. Administración de la rejilla. .......................................... 158 1.5. Lanzar OEM. ................................................................ 159

2. Oracle Real Application Clusters ........................................ 160 2.1. Pasos de preinstalación. ................................................ 160 2.2. Instalación de RAC. ...................................................... 161 2.3. Inicia y parar instancias RAC. ....................................... 163 2.4. Transparencia de sobrefallos de aplicación..................... 164 2.5. Añadir nodos e instancias a un clúster. ........................... 165 2.6. Administración de registro y servicios del clúster. .......... 165

3. Seguridad en Oracle ........................................................... 166 3.1. Creación de usuarios. .................................................... 166 3.2. Eliminación de usuarios. ............................................... 167 3.3. Gestión de contraseñas. ................................................. 167 3.4. Perfiles de usuario......................................................... 169 3.5. Cuentas de base de datos sobre cuentas del sistema operativo. ............................................................................ 170 3.6. Usuarios globales. ......................................................... 170 3.7. Usuarios con permisos especiales: SYSOPER y SYSDBA.171 3.8. Roles estándar. ............................................................. 172 3.9. Permisos del sistema. .................................................... 173 3.10. Metadatos sobre permisos y usuarios. .......................... 181

4. Bases de datos virtuales privadas. ........................................ 181 4.1. Cómo implementar VPD a nivel de tabla. ...................... 182 4.2. Cómo implementar VPD a nivel de columna. ................. 186 4.3. Cómo desactivar VPD. .................................................. 186 4.4. Contenido del paquete «SYS.DBMS_RLS». .................. 186

4.5. Cómo usar grupos de políticas. ...................................... 187 5. Trabajando con espacios de tabla......................................... 187

5.1. Tablespaces y la estructura de las bases de datos. ........... 188 5.2. Planificando el uso de nuestro tablespace. ...................... 196

6. Usar SQL*Loader para cargar datos .................................... 197 6.1. El fichero de control. ..................................................... 197 6.2. Comienzo de la carga. ................................................... 199 6.3. Sobre la sintaxis del fichero de control. .......................... 201 6.4. Administración de la carga de datos. .............................. 202 6.5. Ajustar la carga de datos. .............................................. 203 6.6. Funcionalidades adicionales. ......................................... 205

7. Importar y exportar con «Data Pump» ................................. 205 7.1. Creando un directorio. ................................................... 205 7.2. Opciones de «Data Pump Export». ................................ 206 7.3. Iniciando una tarea de «Data Pump Export». .................. 207 7.4. Opciones para «Data Pump Import». ............................. 210 7.5. Iniciando una tarea de «Data Pump Import». .................. 211

8. Acceso a datos remotos ....................................................... 214 8.1. Enlaces de base de datos. .............................................. 214 8.2. Usando sinónimos para transparencia de localización. .... 218 8.3. Usando la pseudo-columna «USER» en vistas................ 219 8.4. Enlaces dinámicos: usando el comando de copia de SQL*Plus. ........................................................................... 220 8.5. Conectándose a una base de datos remota. ..................... 221

9. Vistas materializadas. ......................................................... 222 9.1. Funcionalidad. .............................................................. 222 9.2. Permisos requeridos. ..................................................... 222 9.3. Solo-lectura contra actualizable. .................................... 223 9.4. Sintaxis de creación de vistas materializadas. ................. 223 9.5. Usando vistas materializadas para modificar rutas de ejecución de consultas. ........................................................ 227 9.6. Usando «DBMS_ADVISOR». ...................................... 228 9.7. Refrescando vista materializadas. .................................. 229 9.8. Sintaxis para crear registros de vista materializada. ........ 233 9.9. Modificando vistas materializadas y registros. ............... 234 9.10. Eliminando vistas materializadas y registros. ............... 234

10. Oracle Text....................................................................... 235 10.1. Añadiendo texto a la base de datos. .............................. 235 10.2. Consultas de texto e índices de texto. ........................... 235 10.3. Conjuntos de índices. .................................................. 243

11. Uso de tablas externas ....................................................... 244 11.1. Accediendo a datos externos. ....................................... 244 11.2. Creando una tabla externa. .......................................... 245 11.3. Modificación de tablas externas. .................................. 250 11.4. Limitaciones, beneficios y usos potenciales de las tablas externas. .............................................................................. 251

12. Consultas flashback .......................................................... 252 12.1. Ejemplo de consulta flashback basada en el tiempo. ..... 253 12.2. Guardando los datos. ................................................... 254 12.3. Ejemplo de consulta flashback basada en SCN. ............ 254 12.4. ¿Qué ocurre si falla una consulta flashback? ................ 255 12.5. ¿Qué SCN está asociado con cada registro? ................. 255 12.6. Consultas de versión flashback. ................................... 256 12.7. Planificación de las consultas flashback. ...................... 257

13. Tablas y bases de datos flashback ...................................... 258 13.1. El comando «FLASHBACK TABLE». ........................ 258 13.2. El comando «FLASHBACK DATABASE». ................ 260

V. SOPORTE DE OBJETOS Y XML ........................... 263 1. Modelo objeto-relacional de Oracle ..................................... 263

1.1. Tipos abstractos de datos (clases y objetos). ................... 263 1.2. Seguridad para tipos de datos abstractos. ....................... 264 1.3. Herencia de clases. ........................................................ 266 1.4. Métodos. ...................................................................... 266 1.5. Tablas relacionales de objetos. ...................................... 269 1.6. Tipos referencia (REF). ................................................. 271 1.7. Tablas anidadas y arrays variables. ................................ 272 1.8. Vistas de objeto. ........................................................... 274 1.9. Trabajando con tipos SQL desde aplicaciones JDBC. ..... 275 1.10. Crear y usar clases de objetos Java personalizadas para objetos Oracle. .................................................................... 275

2. Documentos XML en Oracle............................................... 281

Page 5: Java y Oracle 11g

2.1. «XMLType». ................................................................ 281 2.2. Mapeado de «XMLType» dado un esquema XML. ........ 281 2.3. Crear tablas/columnas «XMLType». ............................. 282 2.4. Operaciones con columnas XMLType. .......................... 283 2.5. Validar los documentos XML sobre un esquema. ........... 286 2.6. Indexar elementos «XMLType». ................................... 287 2.7. SQLX, generar XML de los datos relacionales. .............. 287 2.8. Vistas «XMLType». ..................................................... 291

VI. PROCEDIMIENTOS DE GESTIÓN DE LA BASE

DE DATOS. ................................................................... 294 1. Diccionario de datos de Oracle ............................................ 294

1.1. Las vistas «DICTIONARY» (DICT) y «DICT_COLUMNS». ......................................................... 294 1.2. Cosas que podemos seleccionar de: tablas (y columnas),

vistas, sinónimos y secuencias. ............................................ 295 1.3. Papelera: USER_RECYCLEBIN y DBA_RECYCLEBIN300 1.4. Restricciones y comentarios. ......................................... 300 1.5. Índices y clústeres. ........................................................ 303 1.6. Tipos de datos abstractos, estructuras ORDBMS y LOB's.306

1.7. Enlaces de base de datos y vistas materializadas. ............ 308 1.8. Triggers, procedimientos, funciones y paquetes.............. 310 1.9. Dimensiones. ................................................................ 311 1.10. Asignación y uso de espacio, incluyendo particiones y subparticiones. .................................................................... 312 1.11. Usuarios y permisos. ................................................... 316 1.12. Roles. ......................................................................... 318 1.13. Auditoría. ................................................................... 318 1.14. Supervisión: las tablas de rendimiento dinámico V$. .... 320

2. Administración de la base de datos. ..................................... 325 2.1. Creación de una base de datos. ...................................... 325 2.2. Iniciación y parado de la base de datos........................... 326 2.3. Tamaño y gestión de las áreas de memoria. .................... 326 2.4. Asignar y gestionar espacio para objetos. ....................... 327

3. Auditoría de Seguridad ....................................................... 329 3.1. Auditando conexiones. .................................................. 330 3.2. Auditando Acciones ...................................................... 330 3.3. Auditando objetos. ........................................................ 331 3.4. Protegiendo los registros de auditoría............................. 331

Page 6: Java y Oracle 11g

Oracle /6

I. NOCIONES SOBRE BASES DE DATOS Y ORACLE

1. Arquitectura de Oracle Database 11g

Oracle Database 11g es una actualización significativa de Oracle. Se han añadido nuevas funcionalidades para los programadores, administradores de base de datos, y los usuarios finales tienen un mayor control sobre el almacenamiento, procesamiento y recuperación de los datos.

1.1. Fundamentos de bases de datos.

Todas las bases de datos relacionales manejan una serie de conceptos, los cuales pueden ser implementados de forma diferente en cada base de datos.

▪ Dato. Es un conjunto de caracteres con algún significado; que pueden ser numéricos, alfabéticos, o alfanuméricos. ▪ Información. Es un conjunto ordenado de datos, los cuales son manejados según la necesidad del usuario. Para que un conjunto de datos pueda ser procesado eficientemente y pueda dar lugar a información, primero se deben guardar lógicamente en archivos. ▪ Base de datos (BD).Es un conjunto de información relacionada que se organiza y estructura de alguna manera en archivos. En ese sentido, cualquier conjunto de fichas organizadas y guardadas en un archivador constituye una base de datos. En nuestro entorno cotidiano existen muchos ejemplos de bases de datos: registros de bibliotecas (con información sobre libros, lectores, préstamos, etc.), registros de empresas (con información sobre empleados, ocupaciones, productos, etc.), el censo de una población (con la información personal de sus habitantes), etc. La utilidad y eficacia de una base de datos depende de la forma en que se estructura la información que contiene. Por ejemplo, en un archivador, la información se distribuye en un número determinado de fichas que poseen la misma estructura.

▪ Registro. Es cada una de las fichas o filas de que consta una base de datos. ▪ Campos o atributos. Son cada una de las características diferenciadas que definen un registro. Cada registro o ficha está constituido por una serie de apartados en los que se introduce una determinada información (Nombre, Apellidos, Fecha, Dirección, Ocupación, etc.). ▪ Archivo o fichero. Es la unión de todos los registros con la misma estructura. ▪ Sistema Gestor de Base de Datos (SGBD). Es una colección de rutinas o programas interrelacionados, que permiten crear y manipular una base de datos. El objetivo primordial de un sistema gestor es proporcionar un entorno que sea a la vez conveniente y eficiente para ser utilizado al extraer, almacenar y manipular información de la base de datos. Todas las peticiones de acceso a la base de datos se manejan centralizadamente por medio del SGBD, por lo que este paquete funciona como una interfaz entre los usuarios y la base de datos. ▪ Esquema de base de datos. Es la estructura por la que está formada la base de datos. Se especifica por medio de un conjunto de definiciones que se expresa mediante un lenguaje especial llamado lenguaje de definición de datos (DDL). ▪ Administrador de base de datos (DBA). Es la persona o equipo de personas profesionales responsables del control y manejo del sistema de base de datos, generalmente tiene(n) experiencia en SGBD, diseño de bases de datos, Sistemas operativos, comunicación de datos, hardware y programación.

1.1.1. Objetivos de los sistemas de bases de datos. Los sistemas de base de datos se diseñan para manejar grandes cantidades de información. La manipulación de los datos involucra dos aspectos:

Page 7: Java y Oracle 11g

Oracle /7

- la definición de estructuras para el almacenamiento de la información, y - aportar mecanismos para la manipulación de la información.

Además, un sistema de base de datos debe de tener implementados mecanismos de seguridad que garanticen la integridad de la información, bien ante caídas del sistema o bien ante intentos de accesos no autorizados. Por tanto, el objetivo principal de un sistema de base de datos es minimizar los siguientes aspectos:

▪ Redundancia e inconsistencia de datos. Puesto que los archivos que mantienen almacenada la información son creados por diferentes tipos de aplicaciones, existe la posibilidad de que si no se controla detalladamente el almacenamiento, se pueda originar un duplicado de información (que la misma información esté en más de un soporte). Esto aumenta los costes de almacenamiento y acceso a los datos, además de que puede originar la inconsistencia de los datos. ▪ Dificultad para tener acceso a los datos. Un sistema de base de datos debe contemplar un entorno de datos que le facilite al usuario el manejo de los mismos. Supóngase un banco, y que uno de los gerentes necesita averiguar los nombres de todos los clientes que viven en una zona con el código postal 78733. El gerente pide al departamento de procesamiento de datos que genere la lista correspondiente. Si esta situación no fue prevista en el diseño del sistema, obtener tal lista se convertirá en una tarea difícil. ▪ Aislamiento de los datos. Puesto que los datos están repartidos en varios archivos, y éstos pueden tener diferentes formatos, es difícil crear nuevos programas para obtener los datos apropiados. ▪ Anomalías del acceso concurrente. Para mejorar el funcionamiento global del sistema y obtener un tiempo de respuesta más rápido, muchos sistemas permiten que múltiples usuarios actualicen los datos simultáneamente. En un entorno así, la interacción de actualizaciones concurrentes puede dar por resultado datos inconsistentes. Para prevenir esta posibilidad debe mantenerse alguna forma de supervisión en el sistema. ▪ Problemas de seguridad. La información de toda empresa es importante, aunque unos datos lo son más que otros; por tal motivo se debe considerar el control de acceso a los mismos. No todos los usuarios podrán visualizar determinada información; y por tal motivo, para que un sistema de base de datos sea confiable, debe mantener un grado de seguridad que garantice la autentificación y protección de los datos. ▪ Problemas de integridad. Los valores de datos almacenados en la base de datos deben satisfacer cierto tipo de restricciones de consistencia. Estas restricciones se hacen cumplir en el sistema añadiendo códigos apropiados en los diversos programas.

1.1.2. Abstracción de la información. Una base de datos es en esencia una colección de archivos relacionados entre sí, de la cual los usuarios pueden extraer información sin que tengan que conocer la estructura interna de los archivos. Un objetivo importante de un sistema de base de datos es proporcionar a los usuarios una visión abstracta de los datos; es decir, el sistema debe esconder ciertos detalles de cómo se almacenan y mantienen los datos. Sin embargo, para que el sistema sea manejable, los datos se deben extraer eficientemente. Existen diferentes niveles de abstracción para simplificar la interacción de los usuarios con el sistema:

▪ Nivel físico. Es la representación del nivel más bajo de abstracción; en éste se describe en detalle la forma en cómo se almacenan los datos en los dispositivos de almacenamiento (por ejemplo, mediante índices para el acceso aleatorio a los datos). ▪ Nivel conceptual. El siguiente nivel más alto de abstracción describe qué datos son almacenados realmente en la BD y las relaciones que existen entre los mismos. Describe completamente la base de datos en términos de su estructura de diseño. El nivel conceptual de abstracción lo usan los administradores de BD, quienes deben decidir qué información se va a guardar en la base de datos. Consta de las siguientes definiciones:

1) Definición de los datos: se describen el tipo de dato y sus características. 2) Relaciones entre datos: se definen las relaciones entre datos, para enlazar tipos de registros relacionados, para su procesamiento posterior.

▪ Nivel de visión. Es el nivel más alto de abstracción; es lo que el usuario final puede visualizar del sistema terminado. Sólo describe una parte de la BD según el usuario acreditado para verla. El sistema puede proporcionar muchas visiones para la misma BD.

La interrelación entre estos tres niveles de abstracción se ilustra en la siguiente figura.

Page 8: Java y Oracle 11g

Oracle /8

Nivel de visión

Vista 1 . . . Vista n

Nivel conceptual

Nivel físico

1.2. Fundamentos de bases de datos relacionales.

El modelo relacional de base de datos nace en 1970, cuando Edgar Codd escribe el artículo "A relational model of data for large strared data tanks". Es a partir de 1980 cuando aparecen los primeros gestores de base de datos cuyo modelo de datos subyacente es el relacional. El modelo relacional conecta registros mediante los valores que éstos contienen. De hecho, Codd propone una estructura tabular (correspondiente a tablas) para representar los datos. Es decir, si queremos representar toda la información contenida en los registros de un archivo podemos utilizar una estructura de tabla. Por ejemplo, la información del censo de una población podemos representarla de la siguiente manera:

DNI Nombre Fecha Ocupación

23444325 Pedro Salgueiro 20/12/64 Profesor

65335544 José Martínez 14/07/72 Carpintero

11143442 Esther López 03/07/72 Carpintero

66442444 José Martínez 12/10/65 Ingeniero

Como vemos, los registros se disponen por filas mientras que los campos se disponen por columnas. Esta forma particular de estructurar y representar la información se conoce como base de datos relacionales. Cada tabla es la representación física de una entidad o una relación, y se corresponde con un archivo o fichero de la BD. Cada fila de la tabla se corresponde con un registro, llamado también intensión o tupla. Cada columna se corresponde con los valores de un campo o atributo, siendo éstos una característica distinguible de una entidad o relación. Cada atributo tiene asignado un dominio, del cual tomará valores. 1.2.1. Campos clave. Las BD relacionales se basan en un concepto fundamental: cada registro de la tabla debe ser único, no pudiendo haber registros repetidos. Para asegurar esta circunstancia surge el concepto de clave, como aquel campo de la tabla cuyo valor para cada registro es único. En el ejemplo anterior podemos comprobar que existen nombres, apellidos y ocupaciones repetidas en varios registros; por lo tanto, ninguno de estos tres campos puede ser clave. Vemos que ninguna fecha se repite, pero nada nos asegura que no podamos insertar dos personas que hayan nacido en la misma fecha. Sin embargo, el DNI de una persona, por su propia definición, suele ser un valor único. Por tanto el campo DNI sí puede ser la clave para esta tabla. Puede darse el caso de que en una tabla ninguno de los campos sea clave. Entonces podemos probar a unir varios campos, de forma que el valor conjunto de estos campos sea único. En el ejemplo anterior podemos considerar que el valor conjunto de los campos Nombre y Fecha no suele repetirse (es decir, no suele haber dos personas con el mismo nombre y que han nacido en la misma fecha). En ese caso, los campos (Nombre, Fecha) constituyen una clave compuesta. Si en una tabla no existen claves simples ni claves compuestas, debemos inventarnos un nuevo campo que actúe como clave. Este nuevo campo normalmente es un código o identificador numérico que se va asignando a cada nuevo registro que se introduce en la tabla, de forma que nunca se repita. 1.2.2. Operaciones básicas sobre registros. Con los registros de una tabla podemos realizar las siguientes operaciones básicas:

▪ Añadir un registro. Cuando añadimos un nuevo registro en la tabla debemos comprobar que el valor (o conjunto de valores) de la clave no esté repetido. Si otro registro posee esa clave no podrá añadirse el nuevo registro.

Page 9: Java y Oracle 11g

Oracle /9

▪ Borrar un registro. No existen limitaciones para borrar un registro. (Normalmente los registros no se borran inmediatamente de la tabla, sino que internamente se les pone una marca para indicar que están pendientes de borrado.) ▪ Actualizar un registro. Se puede modificar la información de un registro en cualquiera de sus campos, excepto en los campos que pertenezcan a la clave.

1.2.3. Operaciones relacionales sobre tablas. Podemos realizar las siguientes operaciones sobre una tabla:

▪ La Intersección ( ∩ ). Sólo es aplicable sobre tablas con esquemas similares. Produce una nueva tabla con el mismo esquema, y que contendrá los registros comunes en ambas tablas.

▪ La Unión ( U ). También es sólo aplicable a tablas con el mismo esquema. Produce una nueva tabla con el mismo esquema y con todos los registros de ambas tablas (excluyendo los registros repetidos).

▪ Diferencia ( – ). Produce una nueva tabla con aquellos registros de la primera tabla que no pertenecen a la segunda tabla.

1.2.4. Operaciones propias del modelo relacional. ▪ Selección ( ∑). Se aplica sobre una tabla, y produce una nueva tabla con aquellos registros que en algunos atributos cumplen una condición determinada.

▪ Proyección ( ∏ ). Aplicada sobre una tabla, produce una nueva tabla con todos los registros de la original, pero cuyo esquema contiene sólo alguno de los atributos de la tabla original.

▪ Join ( ). Se aplica sobre dos tablas de distinto esquema pero con atributos comunes (o compatibles). Genera una nueva tabla que es el producto cartesiano de ambas tablas, seleccionando los registros con valores idénticos en los atributos comunes y eliminando las columnas repetidas.

1.2.5. Relaciones entre tablas. La gran potencia y eficacia de las bases de datos relacionales se comprueba cuando debemos asociar la información contenida en dos o más tablas relacionadas. Consideremos el ejemplo de una concesionaria de automóviles, la cual posee una base de datos con información sobre sus clientes y los coches que vende. En este caso, la base de datos se compone de dos tablas: una con los datos de los clientes, y otra con los datos de coches disponibles.

Page 10: Java y Oracle 11g

Oracle /10

TABLA DE CLIENTES TABLA DE COCHES

DNI Nombre Apellidos TipoPago Matrícula Marca Precio

11252111 PEDRO PÉREZ CONTADO M-2345-AF OPEL 1200000

12323253 JUAN GARCÍA PLAZOS M-3443-HW CITROEN 2100000

56344323 LUÍSA GÓMEZ PLAZOS M-1278-HZ CITROEN 1500000

La clave de la tabla CLIENTES es el campo DNI, y la clave de la tabla COCHES es Matrícula. Aunque estas dos tablas recogen toda la información disponible sobre clientes y coches, no tenemos manera de saber qué clientes han comprado qué coches. Es necesario relacionar estas dos tablas asociando cada registro de un cliente al registro del coche que ha comprado. Para relacionar tablas en una BD relacional surge el concepto de clave foránea. En una de las tablas se añade un nuevo campo que se corresponda con la clave de la otra tabla, actuando el nuevo campo como clave foránea. Existen cuatro tipos de relaciones posibles:

▪ Relación uno a uno. Sólo podemos asociar un registro de la primera tabla con un registro de la segunda tabla y viceversa. En nuestro ejemplo significa que un cliente sólo puede comprar un coche y un coche sólo puede ser comprado por un cliente. Esta relación se resuelve introduciendo en una de las tablas, como clave foránea, la clave de la otra tabla. Tenemos dos soluciones para nuestro ejemplo.

Matrícula

Marca

Precio

CLIENTES COCHES

1

1 DNI

Nombre

Apellidos

TipoPago

Matrícula

Matrícula

Marca

Precio

DNI

CLIENTES COCHES

1

1

DNI

Nombre

Apellidos

TipoPago

▪ Relación uno a varios. Podemos asociar un registro de la primera tabla con varios registros de la segunda tabla, y un registro de la segunda tabla con un registro de la primera. En nuestro ejemplo significa que un cliente puede comprar varios coches, pero un coche sólo puede ser comprado por un cliente. Se resuelve introduciendo en la segunda tabla la clave de la primera.

Matrícula

Marca

Precio

DNI

CLIENTES COCHES 1

DNI

Nombre

Apellidos TipoPago

▪ Relación varios a uno. Esta relación es análoga a la anterior. Indica que un cliente sólo puede comprar un coche, pero un coche puede ser comprado por varios clientes. Se resuelve introduciendo en la primera tabla la clave de la segunda.

Matrícula

Marca

Precio

CLIENTES COCHES

1 DNI

Nombre

Apellidos TipoPago

Matrícula

▪ Relación varios a varios. Podemos asociar un registro de la primera tabla con varios registros de la segunda tabla, y podemos asociar un registro de la segunda tabla con varios de la primera. En nuestro ejemplo significa que un cliente puede comprar varios coches y un coche puede ser comprado por varios clientes. Esta relación se resuelve creando una nueva tabla cuya estructura hereda los campos claves de las tablas relacionadas. Para nuestro ejemplo crearemos la tabla COMPRA, con dos campos:

- DNI, clave foránea heredada de la tabla CLIENTES. - Matrícula, clave foránea heredada de la tabla COCHES.

La clave de esta nueva tabla puede estar formada por ambos campos, y por lo tanto será compuesta; o bien podemos añadir una nuevo campo que actúe de clave.

Page 11: Java y Oracle 11g

Oracle /11

1

1

Matrícula

Marca

Precio

CLIENTES COCHES

DNI

Nombre

Apellidos TipoPago

Matrícula

COMPRA

DNI

Matrícula

1.2.6. Reglas de integridad. En las bases de datos relacionales deben cumplirse dos reglas fundamentales:

1ª REGLA (Integridad de entidad): dice que ningún campo que forme parte de una clave puede carecer de valor (o dicho en términos informáticos; no puede tomar el valor nulo). Esto es así porque si un registro careciese de valor en su clave ya no podría ser identificado de forma única. 2ª REGLA (Integridad referencial): dice que no se puede introducir en una clave foránea valores que no pertenezcan a la clave de la tabla referenciada. No tiene sentido indicar en una tabla referenciada valores que no existen en la tabla principal. En el ejemplo de la concesionaria, no tiene sentido indicar que un coche ha sido comprado por un cliente del cual no se tiene ninguna referencia.

La segunda regla puede crear problemas en el caso de que se quieran borrar registros con una clave foránea referenciada por otra tabla. Para evitar este problema existen tres soluciones:

- Prohibir el borrado. Por ejemplo, no se podrán eliminar clientes que hayan comprado algún coche. - Borrar en cascada. Por ejemplo, si se elimina un cliente, se eliminarán también todos los coches comprados por ese cliente. - Nulificar. Por ejemplo, si se elimina un cliente, en los registros de coches vendidos a ese cliente se pondrá un valor nulo en el DNI.

1.2.7. Diseño de una base de datos relacional. Una vez establecido un problema, para diseñar una BD relacional debemos seguir los siguientes pasos:

1) Decidir cuántas tablas necesitamos. Normalmente se creará una tabla por cada entidad que podamos distinguir en nuestro problema. 2) Definir la estructura de cada tabla. De qué campos se componen y cuál es la clave. 3) Determinar para cada campo su tipo de dato, su tamaño (si es necesario) y los valores posibles que podemos asignarle (su dominio). 4) Establecer las relaciones entre las tablas. Para ello debemos incluir en cada tabla las claves foráneas que sean necesarias o crear nuevas tablas.

Una vez diseñada la base de datos podemos crearla utilizando un gestor de base de datos.

1.3. Bases de datos e instancias en Oracle.

Una base de datos de Oracle es una colección de datos en uno o más archivos. La base de datos de Oracle contiene estructuras físicas y lógicas. Durante el desarrollo de una aplicación podemos crear estructuras como tablas e índices para almacenar filas y acelerar su recuperación. Podemos crear sinónimos para los nombres de objetos, vistas de objetos en varias bases de datos y podemos restringir el acceso a los objetos. También podemos usar tablas externas para acceder a ficheros fuera de la base de datos como si las filas en los ficheros fuesen filas de tablas. Una instancia de Oracle comprende un área de memoria llamada Área Global del Sistema (SGA) y los procesos de fondo que interactúan entre el SGA y los ficheros de la base de datos en disco. En una "Real Application Cluster de Oracle" (RAC) más de una instancia será usada sobre la misma base de datos; las instancias generalmente estarán sobre servidores independientes conectados mediante una interconexión de alta velocidad.

1.4. Organización de las bases de datos en Oracle.

Dentro de una base de datos de Oracle, la estructura básica en la tabla. Oracle Database 11g soporta muchos tipos de tablas, incluyendo las siguientes:

■ Tablas relacionales. Usando los tipos de datos soportados por Oracle, podemos crear tablas para almacenar las filas insertadas y manipularlas en nuestras aplicaciones. Las tablas tienen definiciones de columna, y podemos añadir o quitar columnas según los requerimientos de nuestras aplicaciones. ■ Tablas objeto-relacionales. Para tomar ventajas de funcionalidades como la herencia de tipo, podemos

Page 12: Java y Oracle 11g

Oracle /12

usar las capacidades objeto-relacional de Oracle. Podemos definir nuestros propios tipos de datos y usarlos como base para la definición de columnas, tablas de objetos, tablas anidadas, arrays variables y más. ■ Tablas organizadas por índice. Podemos crear una tabla que almacene sus datos como una estructura de índice, permitiendo que los datos sean ordenados dentro de la tabla. ■ Tablas externas. Los datos almacenados en ficheros planos pueden ser tratados como tablas que los usuarios pueden consultar directamente y relacionar con otras tablas en consultas. Podemos usar tablas externas para acceder a grandes volúmenes de datos sin tener que cargarlos en nuestra base de datos. Oracle también soporta tipos de datos BFILE, un puntero a un fichero binario externo. ■ Tablas particionadas. Podemos dividir una tabla en varias particiones, lo cual permite controlar independientemente cada parte de la tabla. Podemos añadir una nueva partición a una tabla, separar particiones existentes, y administrar una partición a parte de otra partición de la tabla. El particionado puede simplificar o mejorar el rendimiento de actividades de mantenimiento y de las consultas de usuario. Podemos particionar tablas según rangos de valores, según una lista de valores, según códigos de valores de columna, o según una combinación de estas opciones. ■ Vistas materializadas. Una vista materializada en una réplica de los datos recuperados por una consulta. Las consultas de usuario pueden ser redireccionadas a las vistas materializadas para evitar tablas largas durante la ejecución (el optimizador rescribirá las consultas automáticamente). Podemos establecer y controlar tareas de refresco para obtener los datos actualizados en las vistas materializadas según las necesidades del negocio. ■ Tablas temporales. Podemos usar tablas temporales globales para crear una tabla en la cual varios usuarios puedan insertar registros. Cada usuario sólo verá sus filas en la tabla. ■ Tablas de clúster (o cubo).Si dos tablas son normalmente consultadas conjuntamente, podemos almacenarlas físicamente juntas a través de una estructura llamada clúster (o cubo). ■ Tablas eliminadas. Desde Oracle Database 10g, podemos recuperar rápidamente tablas eliminadas mediante el comando DROP. Podemos recuperar varias tablas de una vez o recuperar toda la base de datos en un momento concreto. Oracle soporta consultas de deshacer, las cuales retornan versiones anteriores de filas en una tabla existente.

Como soporte en el acceso a las tablas, podemos usar vistas que realicen combinaciones y agregaciones, limitar las filas retornadas, o modificar las columnas mostradas. Las vistas pueden ser de sólo lectura o modificables, y pueden referenciar tablas locales y remotas. Las tablas remotas pueden ser accedidas a través de enlaces a bases de datos. Podemos usar sinónimos para enmascarar la localización física de las tablas. Para controlar los accesos a las tablas, Oracle soporta muchos tipos de índices, incluyendo los siguientes:

■ Índices B*-tree. Un índice B*-tree es el tipo estándar de índices disponibles en Oracle, y es muy usado para seleccionar filas por un criterio de equivalencia o una criterio de rango. ■ Índices Bitmap. Para columnas que tienen pocos valores únicos, un índice bitmap puede mejorar el rendimiento de las consultas. Los índices bitmap deberían usarse sólo cuando los datos se cargan por lotes (como en muchos depósitos de datos o aplicaciones de informes). ■ Índices de clave inversa. Si hay problemas de contención de E/S durante la inserción de valores secuenciales, Oracle puede invertir dinámicamente los valores de índice antes de almacenarlos. ■ Índices basados en funciones. En vez de indexar una columna, como Nombre, podemos indexar una columna basada en una función, como UPPER(Nombre). Este tipo de índice tiene opciones adicionales del optimizador de Oracle cuando seleccionamos una ruta de ejecución. ■ Índices particionados. Podemos particionar índices para soportar tablas particionadas o para simplificar la gestión de índices. Los índices particionados pueden ser locales para cada partición de la tabla o pueden aplicarse globalmente a todas las filas de la tabla. ■ Índices de texto. Podemos indexar valores de texto para soportar capacidades de búsqueda avanzada, como palabras derivadas o búsqueda de frases. Los índices de texto son conjuntos de tablas e índices mantenidos por Oracle para soportar requerimientos de búsqueda de texto complejos. Oracle Database 11g ofrece facilidades para indexar texto que simplifique su administración y mantenimiento.

1.4.1. Almacenando los datos. Toda la estructura lógica de una base de datos debe ser almacenada en algún sitio dentro de la base de datos. Oracle mantiene una diccionario de datos que registra los metadatos acerca de cada objeto (el propietario del objeto, una definición, privilegios relacionados, y cosas así). Para los objetos que requieren un espacio de almacenamiento físico de sí mismos, Oracle reserva espacio dentro de un tablespace.

Page 13: Java y Oracle 11g

Oracle /13

Tablespaces. Un tablespace consiste de uno o más ficheros de datos; un fichero de datos puede ser parte de un y solo un único tablespace. Oracle Database 11g crea al menos dos tablespaces para cada base de datos (SYSTEM y SYSAUX) para soportar las necesidades de administración interna. Podemos usar el «Administrador de Ficheros de Oracle» (OMF) para simplificar la creación y mantenimiento de ficheros de datos. A partir de Oracle Database 10g, podemos crear un tipo especial de tablespace, llamado "bigfile tablespace", que puede tener muchos miles de terabytes de tamaño. Con OMF, la administración de bigfiles hace la gestión de tablespace completamente transparente para el administrador de base de datos (DBA); el DBA puede administrar los tablespace como una unidad sin preocuparse sobre el tamaño y estructura de los ficheros de datos subyacentes. Si un tablespace esta designado como un tablespace temporal, el tablespace mismo es permanente; sólo los segmentos guardados en el tablespace son temporales. Oracle usa tablespaces temporales para soportar operaciones de ordenación en la creación de índices y procesos de combinación. Los segmentos temporales no deberían ser almacenados en el mismo tablespace como objetos permanentes. Los tablespaces pueden ser gestionados en diccionario o gestionados localmente. En una gestión de diccionario, el espacio gestionado es registrado en el diccionario de datos. En una gestión local (por defecto en Oracle Database 11g), Oracle mantiene una mapa de cada fichero de datos del tablespace para rastrear la disponibilidad de espacio. En el diccionario de datos sólo se gestionan cuotas, lo cual reduce dramáticamente la contención de tablas del diccionario de datos. Gestión automática de almacenamiento. La gestión automática de almacenamiento (ASM), disponible desde Oracle Database 10g, automatiza el diseño de ficheros de datos y otros ficheros del nivel del sistema operativo usados por la base de datos, distribuyéndolos entre los disco disponibles. Cuando un nuevo disco es añadido a la instancia ASM, los ficheros de datos son automáticamente redistribuidos a través de todos los discos en los grupos de discos definidos para optimizar el rendimiento. Las características de multiplexión de una instancia ASM minimizan la posibilidad de pérdida de datos y es generalmente más efectiva que un esquema manual que pone ficheros críticos y ficheros de respaldo en diferentes unidades físicas. Gestión automática de deshacer. Para soportar nuestras transacciones, Oracle puede crear y administrar dinámicamente segmento de deshacer, los cuales ayudan a mantener imágenes prioritarias de los bloques y filas cambiadas. Los usuarios que consultaron previamente las filas que hemos cambiado todavía verán las filas tal como existían cuando las consultaron. La gestión automática de deshacer (AUM) permite a Oracle administrar los segmentos de deshacer directamente sin la intervención del administrador de base de datos. El uso de AUM también simplifica el uso de consultas de flashback. Desde Oracle Database 10g, podemos ejecutar consultas de versiones pasadas para ver diferentes versiones de un fichero tal como ha cambiado durante un intervalo de tiempo específico. Datos eliminados. El concepto de cajón de reciclado introducido con Oracle Database 10g afecta a las exigencias de espacio requerido para nuestros tablespaces y ficheros de datos. En Oracle Database 11g el comportamiento por defecto para el borrado de una tabla es que la tabla retenga su espacio asignado; podemos ver este espacio usando la vista RECYCLEBIN del diccionario de datos. Si creamos y borramos una tabla dos veces, habrá dos copias de la tabla en el cajón de reciclado. Aunque esta arquitectura simplifica enormemente las recuperaciones de borrados de tablas accidentales, puede incrementar considerablemente el espacio usado por la base de datos. Se usa el comando PURGE para quitar entradas antiguas del cajón de reciclado. 1.4.2. Guardando los datos. Podemos controlar completamente el acceso a nuestros datos. Podemos conceder privilegios a otros usuarios para realizar funciones específicas (como seleccionar, insertar y más) sobre nuestros objetos. Podemos adquirir privilegios desde roles, los cuales son entonces concedidos a los usuarios, agrupando así privilegios dentro de conjuntos manejables. Oracle soporta un nivel muy detallado de permisos; podemos controlar qué filas son accesibles y, durante auditoría, qué filas desencadenan eventos de auditoria que sean registrados. Cuando se usa la opción Base de datos Privada Virtual (VPD), las consultas de usuario sobre tablas son siempre limitadas independientemente del método a través del cual se accede a las tablas. Desde Oracle Database 10g, VPD ha ido más lejos incluyendo enmascaramiento de columna para columnas que contienen datos sensibles.

Page 14: Java y Oracle 11g

Oracle /14

Además del acceso seguro a los datos, podemos auditar actividades en la base de datos. Los eventos auditables incluyen acciones privilegiadas (como crear usuarios), cambios en las estructuras de datos y accesos a filas y tablas específicas. 1.4.3. Soporte para programación. Oracle soporta una gran cantidad de métodos de acceso a programación. El lenguaje SQL es la clave para cualquier esfuerzo de programación de aplicaciones. Otros métodos de acceso incluyen los siguientes:

■ PL/SQL.PL/SQL es un componente crítico en la implementación de muchas aplicaciones. Podemos usar PL/SQL para crear procedimientos y funciones almacenados, y entonces podemos llamar a las funciones dentro de consultas. Los procedimientos y funciones pueden ser recolectados dentro de paquetes. También podemos crear desencadenadores (triggers), los cuales dicen a la base de datos qué pasos deben seguir con varios eventos que ocurren dentro de la base de datos. Los triggers pueden ocurrir durante eventos de la base de datos (como el acceso inicial a la base de datos), cambios en la estructura (como un intento de borrar tablas), o cambios en las filas. En cada caso, se usa PL/SQL para controlar el comportamiento de la base de datos o aplicaciones en los cuales ocurren los eventos. ■ SQL Dinámico. Podemos generar SQL en tiempo de ejecución y pasarlo a procedimientos que lo ejecuten a través de SQL Dinámico. ■ SQL*Plus. SQL*Plus proporciona una interfaz sencilla para las bases de datos de Oracle. SQL*Plus puede soportar requerimientos de informes rudimentarios, pero es mejor conocerlo para soportar scripting. Proporciona una interfaz consistente para recuperar datos del diccionario de datos y crear objetos en la base de datos. ■ Java y JDBC. Oracle da soporte a Java y JDBC, permitiéndonos usar Java en lugar de PL/SQL en muchas operaciones. Podemos todavía escribir procedimientos almacenados basados en Java. Las capacidades del Java de Oracle han sido ampliadas y realzadas con cada nueva versión. ■ XML. Podemos usar interfaces y tipos XML de Oracle para soportar la inserción y recuperación de datos en formato XML. ■ SQL y PL/SQL orientados a objetos. Podemos usar Oracle para crear y acceder a estructuras orientadas a objetos, incluyendo tipos de datos definidos por el usuario, métodos, objetos grandes (LOB's), tablas de objetos, y tablas anidadas. ■ Data Pump. «Data Pump Import» y «Data Pump Export», ambos introducidos en Oracle Database 10g, realzan enormemente la manejabilidad y rendimiento de las recientes utilidades de importación y exportación. Podemos usar Data Pump para extraer rápidamente datos y moverlos a diferentes bases de datos mientras alteramos el esquema y cambiamos las filas. ■ SQL*Loader. Podemos usar SQL*Loader para cargar rápidamente ficheros planos dentro de tablas de Oracle. Un único fichero plano puede ser cargado dentro de varias tablas durante la misma carga, y la carga puede ser paralela. ■ Programas y procedimientos externos. Podemos embeber SQL dentro de programas externos, o podemos crear librerías de procedimientos que más tarde sean enlazadas a Oracle. ■ UTL_MAIL. Un paquete introducido en Oracle Database 10g, UTL_MAIL, permite a programadores de aplicaciones PL/SQL enviar correos electrónicos sin tener que conocer la pila de protocolos SMTP subyacente.

1.5. Selección de arquitecturas y opciones.

Oracle proporciona un completo conjunto de herramientas para programar aplicaciones basadas en Oracle Database 11g. Podemos usar el servidor de aplicaciones de Oracle como la capa intermedia para aplicaciones de tres capas que acceden a Oracle Database 11g. Muchas de las funcionalidades introducidas con Oracle Database 11g estarán disponibles independientemente de la arquitectura de aplicación seleccionada. Estas funcionalidades incluyen administración de base de datos como gestión automática de almacenamiento, tuneado automático, y redimensionado automático de las áreas de memoria en el SGA. Oracle proporciona un conjunto de procedimientos que ayudan a gestionar la planificación de refresco de las vistas materializadas. Por ejemplo, podemos ejecutar un procedimiento que genere una descripción de los problemas con los refrescos y las configuraciones que nos impiden usar las opciones más rápidas posibles. Podemos usar otro procedimiento de Oracle para generar recomendaciones para tunear estructuras de vistas materializadas según un conjunto de consultas previstas. Algunas nuevas funcionalidades pueden contener pequeños cambios que pueden tener un gran impacto en

Page 15: Java y Oracle 11g

Oracle /15

nuestra aplicación o nuestro código. Por ejemplo, desde Oracle Database 10g están disponibles búsquedas usando expresiones regulares.

2. Instalar Oracle Database 11g y crear una base de datos

El software de instalación del Oracle se hace más fácil de usar con cada nueva versión; basta con abrir la caja de cedés y comenzar la instalación enseguida. Si queremos experimentar con alguna nueva funcionalidad de base de datos, se requiere una mayor planificación para realizar una instalación sucesiva sin tener que hacer todo el trabajo de reinstalación. En este capítulo veremos lo básico de una instalación de Oracle usando el Instalador Universal de Oracle (OUI), así como una plantilla básica para hacer una instalación manual de la base de datos usando el comando CREATE. Las siguientes cosas deben ser resueltas antes de empezar la instalación:

• Decidir un nombre de base de datos local, y en qué dominio será contenido la base de datos. Estos nombres serán asignados en los parámetros de inicialización DB_NAME y DB_DOMAIN. • Para el primer proyecto que usará la base de datos, estimar el número de tablas e índices, así como su tamaño, para planificar el espacio en disco además del requerido por los tablespace SYSTEM y el software y herramientas asociadas de Oracle. • Planificar la ubicación de los ficheros de datos físicos en el disco del servidor para maximizar el rendimiento y recuperación. En general, cuantos más disco físicos mejor. Si un RAID o Almacén de red son usados por los ficheros de datos, podemos usar el «Administrador de Ficheros de Oracle» para gestionar la ubicación de los ficheros de datos. Desde Oracle Database 10g podemos usar almacenamiento automático (ASM) para simplificar la gestión de almacenamiento. • Revisar y comprender los parámetros básicos de inicialización. • Seleccionar el juego de caracteres de la base de datos, con un juego de caracteres alternativos. Aunque podemos dejar el juego de caracteres por defecto aplicado durante la instalación, podemos necesitar considerar dónde estarán localizados los usuarios de la base de datos y sus requerimientos de idioma. El juego de caracteres puede ser cambiado después de la instalación sólo si el nuevo juego de caracteres es un superjuego del existente. • Decidir el mejor tamaño por defecto de los bloques de base de datos. El tamaño por defecto definido por DB_BLOCK_SIZE no puede ser cambiado después sin reinstalar la base de datos. Oracle puede soportar varios tamaños de bloques dentro de una única base de datos. • Planificar el almacenamiento de objetos de usuarios distintos de SYSTEM en tablespaces distintos de SYSTEM. Hay que asegurarse de que todos los usuarios no administrativos son asignados a un tablespace distinto de SYSTEM por defecto. • Planificar la implementación del «Administrador Automático de Deshacer» para que sea fácil la administración de transacciones que rehagan al información. • Planificar una estrategia de copias de seguridad y recuperación. Decidir cómo la base de datos necesita ser recuperada, y cuánto a menudo. Planificar usar más de un método para recuperar la base de datos.

2.1. Descripción de la licencia y opciones de instalación.

Una instalación inicial acertada del software es el primer paso. Independientemente de la plataforma de software y hardware sobre la cual decidamos instalar Oracle, los tipos de instalaciones que podemos realizar son los mismos. Aunque puede haber cambios en cada versión del producto, normalmente se incluyen los siguientes tipos de instalación:

• Edición Empresarial. Es la versión más extensa, con más funcionalidades. Incluye funciones como Flashback Database y permite añadir piezas adicionales de funcionalidades con licencia, como Oracle Spatial, Oracle OLAP, Oracle Label Security, y Oracle Data Mining. • Edición Estándar. Esta versión proporciona un buen subconjunto de funcionalidades, incluyendo las que necesitan la mayoría de negocios. • Edición Personal. Esta versión permite el desarrollo de aplicaciones que se ejecutarán sobre la Edición Estándar o Empresarial. Esta edición no puede ser usada en un entorno de producción.

Desde Oracle Database 10g, la licencia de Oracle es sólo para un nombre de usuario o una CPU, y no hay la opción de licenciar a usuarios concurrentes. Por lo tanto, el DBA debería usar el parámetro de inicialización LICENSE_MAX_USERS para especificar el número máximo de usuarios que pueden ser creados en la base de datos. Como resultado, los parámetros LICENSE_MAX_SESSIONS y LICENSE_SESSIONS_WARNING están obsoletos en Oracle Database 11g.

Page 16: Java y Oracle 11g

Oracle /16

Además, el servidor de Oracle puede ser instalado durante una instalación del lado servidor o cliente. Sin embargo, es recomendable que esta instalación sea realizada después de que una instalación básica sea completada. 2.1.1. Usando OUI para instalar el software de Oracle. Usaremos el «Instalador Universal de Oracle» (OUI) para instalar y administrar todos los componentes de Oracle tanto en el lado servidor como el lado cliente. Podemos también desinstalar cualquier producto de Oracle desde la pantalla inicial del OUI. Durante la instalación del servidor, podemos elegir la versión de Oracle Database 11g: Empresarial, Estándar, o una de las otras opciones disponibles para nuestra plataforma. Es recomendable crear una base de datos de arranque cuando nos lo soliciten durante la instalación. Crear la base de datos de arranque es un buen modo de asegurarnos de que el entorno servidor se instale correctamente, así como repasar cualquier nueva funcionalidad de Oracle Database 11g. La base de datos de arranque también puede ser un buen candidato como repositorio tanto del «Administrador Empresarial de Oracle» (OEM) como del «Administrador de Recuperación». El flujo exacto del proceso de instalación puede cambiar dependiendo de nuestro entorno operativo y la versión de Oracle.

Nota. En entornos UNIX, necesitamos asignar un valor apropiado a la variable de entorno DISPLAY

y habilitar xhost previamente para empezar el OUI a través del script runInstaller.

En general, los pasos serán como sigue: 1) En la pantalla de apertura, elegir entre instalar un producto o desinstalar un producto previamente instalado. 2) Especificar la ubicación del fichero fuente para el producto que queremos instalar y el directorio inicial dentro del cual el software de Oracle será instalado. El instalador debería presentarnos valores por defecto. En general, los valores por defecto para los archivos fuente de software deberían ser válidos, mientras que otros pueden necesitar ser cambiados. 3) Seleccionar un producto a instalar. Nuestras opciones incluirán la base de datos y el cliente. Si seleccionamos la opción "database", el OUI instalará un base de datos de arranque preconfigurada, opciones de producto, herramientas administrativas, servicios de red, utilidades, herramientas de desarrollo, precompiladores, y software cliente básico. Para la primera instalación, deberíamos usar la opción "database" para crear la base de datos de arranque. 4) Elegir el tipo de instalación: Edición Empresarial, Edición Estándar, o personalizada. 5) Si se ha elegido la opción "database" en el paso 3, ahora se nos pedirá que confirmemos la creación de la base de datos de arranque. 6) Se nos pedirá que elijamos entre configuraciones de base de datos estándar: propósito general, procesamiento de transacciones, o depósitos de datos (warehouse). 7) Para la base de datos de arranque, elegir las opciones de configuración. Estas opciones incluyen el nombre global de la base de datos, el nombre de la instancia, el juego de caracteres, y si incluimos o no esquemas de ejemplo. 8) Especificar una única contraseña que será usada por todos los esquemas precargados en la base de datos de arranque, o contraseñas independientes para cada cuenta. 9) Especificar la opción de almacenamiento que se usará. Si estamos usando ficheros del sistema, especificar los directorios a usar. Otras opciones incluyen administración automática de almacenamiento y dispositivos raw. 10) Nos solicitarán finalizar la selección de opciones de administración y servicios previos para aceptar la configuración e inicio de la instalación.

Durante la instalación del software, el «Asistente de Configuración de la Base de datos» (DBCA) nos solicita los parámetros necesarios de tamaño y configura nuestra base de datos (comenzando en el paso 6). Los pasos de instalación en la siguiente sesión asumen que hemos completado la instalación del software y creado una base de datos de arranque. Ahora crearemos y configuraremos una segunda base de datos sobre el mismo servidor con DBCA.

Nota. Desde Oracle 10g, DBCA puede configurar nodos en un entorno de «Real Application Clusters».

Page 17: Java y Oracle 11g

Oracle /17

2.1.2. Usando el DBCA para crear una base de datos. En UNIX, podemos comenzar el «Asistente de Configuración de la Base de datos» (DBCA) ejecutando el fichero dbca ubicado en el directorio $ORACLE_HOME/bin. Debemos configurar la variable de entorno DISPLAY y asignar xhost antes de empezar el DBCA. En Windows, el DBCA está ubicado en el submenú "Herramienta de configuración y migración" del menú "Oracle". En las subsecciones que siguen, se indican pautas y guías para la mayor parte de las pantallas durante la creación de la base de datos. Opciones del DBCA. Después de una pantalla de bienvenida inicial, se nos presenta una selección de cuatro opciones:

■ Crear una base de datos. Esta opción crea una nueva base de datos desde el principio, usando una plantilla como punto de partida. ■ Configurar opciones de base de datos en una base de datos. Esta opción permite cambiar alguno de los parámetros de sistema para una instalación de base de datos existente, como cambiar desde un servidor dedicado a un servidor compartido. ■ Eliminar una base de datos. Esta opción elimina todos los ficheros de datos y ficheros de control asociados con la base de datos. Necesitamos la contraseña del usuario SYS o SYSTEM para ejecutar esta opción. ■ Administrar plantillas. Esta opción permite añadir, modificar o eliminar plantillas. Durante una sesión del DBCA, una vez que todos los parámetros de base de datos han sido reunidos, tenemos la opción de guardar las asignaciones como una plantilla. En muchos casos, las plantillas predefinidas que Oracle proporciona no son exactamente perfectas para nuestro entorno, y ahorra tiempo poder guardar nuestras opciones como una plantilla para una futura sesión del DBCA.

Seleccionando una plantilla de base de datos. La figura siguiente muestra la lista de plantillas disponibles. Si creamos una plantilla en una sesión previa del DBCA aparecerá en esta pantalla también.

Las plantillas a seleccionar son las siguientes: ■ Base de datos personalizada. Se usa esta opción si hemos realizado muchas instalaciones y sabemos de antemano los valores para todas las opciones que necesita la base de datos. Esta opción es mejor si creamos una nueva plantilla desde el principio o tenemos exigencias muy específicas para la configuración de nuestra base de datos. ■ Depósito de datos. Esta plantilla es para entornos de base de datos donde los usuarios realizan numerosas y complejas consultas que reúnen muchas tablas para informes, pronósticos y analíticas.

Page 18: Java y Oracle 11g

Oracle /18

■ Propósito general. Si todavía no estamos seguros de para qué se empleará nuestra base de datos, o si tenemos que soportar a usuarios con requerimientos de procesamiento analítico y transaccional, se recomienda esta plantilla. ■ Procesamiento de transacciones. En entornos donde el número de usuarios es alto, las transacciones son pesadas pero cortas, y la mayor parte de la actividad es crear y actualizar, se recomienda esta plantilla.

Para continuar con la instalación, seleccionaremos la plantilla de propósito general. Esta plantilla combina las funcionalidades de los depósitos de datos y un entorno OLTP en una única base de datos. Identificación de la base de datos. En el siguiente paso del DBCA, debemos identificar el nombre de la instancia junto con el nombre de la base de datos global.

Nota. Si el nombre de la base de datos global necesita ser cambiado en el futuro, debemos usar el comando ALTER DATABASE para cambiarlo, además de cambiarlo en el fichero de parámetros de inicialización. El nombre de la base de datos global es almacenado en el diccionario de datos cuando se crea la base de datos.

A menos que tengamos un dominio existente, debemos usar el nombre de dominio por defecto .world. Deberíamos comprobar con el administrador de sistemas si deberíamos usar un nombre específico para la base de datos global. Credenciales de la base de datos. La siguiente figura muestra la pantalla de asignación de contraseñas para las cuentas de usuario SYS y SYSTEM. Después de la instalación, debemos asegurarnos de crear al menos una cuenta con privilegios DBA para no tener que usar los usuarios SYS o SYSTEM en las tareas administrativas del día a día.

En esta pantalla, podemos también indicar que esta instancia sea incluida como un nodo administrativo en un entorno de Administración Empresarial de Oracle (OEM) existente, o podemos especificar esta instancia como un repositorio OEM. Si especificamos esta instancia como un repositorio OEM, es extremadamente recomendable que este nodo sea usado sólo para este propósito. Opciones de almacenamiento. Las bases de datos pueden usar una cantidad de métodos diferentes para almacenar ficheros de datos, ficheros de control y ficheros de deshacer. La siguiente figura muestra la pantalla donde podemos seleccionar el mecanismo de almacenamiento.

Page 19: Java y Oracle 11g

Oracle /19

Si tenemos la posibilidad de dedicar otra instancia de base de datos para administrar espacio de disco, debemos elegir la opción ASM. Si estamos en un entorno «Real Application Clusters» y no tenemos un sistema de ficheros de clúster disponible (como un OCFS) debemos elegir la opción "Raw Devices". Localización de ficheros. En la pantalla mostrada a continuación es donde podemos seleccionar la localización de los archivos de datos, ficheros de control, ficheros de deshacer, así como las localizaciones de archivado, copias de seguridad y recuperación.

Un concepto nuevo desde Oracle Database 10g es el Área de Recuperación Flash. Ésta es una localización del disco dedicada, separada de la localización de los ficheros operacionales de la base de datos, que contiene

Page 20: Java y Oracle 11g

Oracle /20

ficheros de respaldo del Administrador de Respaldo (RMAN). Es altamente recomendado usar el Área de Recuperación Flash para que el RMAN pueda administrar más fácilmente copias de respaldo y operaciones de recuperación. Deberemos asegurarnos de que el Área de Recuperación Flash tenga el espacio necesario para al menos dos copias de todos los ficheros de datos, copias de respaldo incrementales, ficheros de control, SPFILE's, y ficheros de deshacer que están todavía en el disco. Podemos también habilitar el modo ARCHIVELOG, para especificar la localización o localizaciones de los ficheros de deshacer. Se recomienda dejar el archivado deshabilitado hasta que la base de datos se instale, porque habilitarlo incrementa el tiempo de creación de la base de datos. Los parámetros para el modo ARCHIVELOG pueden ser modificados fácilmente en el fichero init.ora o el SPFILE inmediatamente después de que la base de datos esté activa y ejecutándose. Componentes de la base de datos. En el siguiente paso de la sesión del DBCA, se nos pregunta acerca de la instalación de los esquemas de ejemplo. En bases de datos de no-producción, es altamente recomendable instalar esquemas de ejemplo; muchos tutoriales y guías de estudio se apoyan en los esquemas de ejemplo de la base de datos. Son también útiles en esto los ejemplos demostrativos de casi todos los tipos de datos y constructores disponibles en la base de datos, rangos de mapas de índices para tablas en clústeres y tipos de objetos. Parámetros de inicialización. La siguiente pantalla mostrada a continuación permite al DBA ajustar los parámetros clave de inicialización para la base de datos.

La figura muestra la ficha de Memoria. Si seleccionamos típica (Typical) o si seleccionamos personalizada (Custom) con administración automática de la memoria compartida, Oracle hará asunciones acerca de la memoria que podemos usar para el SGA y los procesos de segundo plano. Incluso usando por defecto muchos de los parámetros en una configuración típica, todavía podemos especificar qué cantidad de la memoria física del servidor debería usar Oracle, dependiendo de cuánta memoria es usada por el sistema operativo y si cualquier otra aplicación está siendo ejecutada en este servidor junto con Oracle. El valor para Java Pool debe ser al menos el tamaño de una fracción de la base de datos, entre 4 MB o 16 MB, pero al menos se recomiendan 20MB. Las últimas pantallas de esta sección del DBCA permiten especificar el tamaño de bloque por defecto de la base de datos, el número total de procesos que serán simultáneamente conectados, el modo de conexión usado, y el juego de caracteres para la base de datos. Almacenamiento de la base de datos. En la pantalla de almacenamiento de base de datos del DBCA, podemos ver y revisar la localización de los

Page 21: Java y Oracle 11g

Oracle /21

ficheros de control, ficheros de datos y ficheros de deshacer, así como multiplexar los ficheros de control y crear grupos de ficheros de deshacer. Los nombres y localizaciones de los ficheros de control en esta pantalla determinan el valor de CONTROL_FILES en el fichero de parámetros de inicialización. Opciones de creación. En la siguiente figura se muestra la pantalla para crear la base de datos. Además, podemos usar la información proporcionada en las pantallas previas y guardarla en una plantilla. Ante la duda guardar como una plantilla; el almacenamiento requerido para guardar sólo una plantilla es mínimo y puede fácilmente ser suprimido más tarde por nueva ejecución del DBCA.

Antes de que la base de datos sea creada, se presenta un resumen de nuestra plantilla, y tenemos la opción de guardar este informe como un archivo HTML para propósitos de documentación. Completando la instalación. Después de pulsar el botón "OK" de la pantalla de resumen, el DBCA realiza las tareas necesarias para crear la base de datos e iniciar la instancia. Se ejecuta un conjunto de scripts estándar cuando la base de datos inicia por primera vez; esto incluye el script que crea los esquemas de ejemplo, más cualquier script personalizado especificado previamente. El conjunto estándar de scripts varía dependiendo de las opciones seleccionadas a través del DBCA. Una vez completados los scripts de inicialización y creación, se presenta una pantalla de resumen, mostrando la localización de los ficheros de registro de esta instalación. Es recomendable revisar estos ficheros de registro para asegurarnos que no ocurrieron errores durante la instalación. Deberíamos también guardar este fichero de registro con otra documentación de esta base de datos; esto puede ser útil para futuras instalaciones. La base de datos de Oracle recién creada es activada y ejecutada. Tenemos la opción de desbloquear otras cuentas creadas durante esta instalación y asignarles contraseñas. 2.1.3. Creación manual de una base de datos. El DBCA puede soportar requerimientos de instalación complejos. Por ejemplo, si necesitamos crear la misma base de datos en varios servidores, podemos usar el DBCA para crear y ejecutar plantillas. Podemos crear manualmente una base de datos en vez de usar el DBCA. Oracle proporciona un script de ejemplo de creación de base de datos que puede ser personalizado para una instalación manual. A continuación se describen los pasos necesarios para crear una base de datos manualmente. Algunos de estos pasos son dependientes del sistema operativo o de la plataforma. Por ejemplo, bajo Windows necesitamos ejecutar la utilidad oradim para crear el proceso de fondo de Oracle y para asignar los valores de

Page 22: Java y Oracle 11g

Oracle /22

registro relevantes. 1) Decidir una estructura de directorios para la base de datos; se recomienda cumplir con los estándares de la arquitectura flexible óptima de Oracle cuando pongamos nuestros ficheros en el disco. 2) Seleccionar una SID (identificador de instancia) de Oracle para distinguir esta instancia de otras que se estén ejecutando en el servidor. Frecuentemente será el mismo que el nombre de la base de datos especificado en el parámetro de inicialización DB_NAME. En la línea de comandos de Windows escribir lo siguiente:

SET ORACLE_SID=rjbdb

Bajo UNIX, debemos usar: EXPORT ORACLE_SID=rjbdb

o bien: SETENV ORACLE_SID=rjbdb

dependiendo de la consola de comandos por defecto. 3) Establecer un método de autentificación para conectar los permisos de usuario a la base de datos. Se usa la utilidad de línea de comando orapwd para crear un fichero de contraseñas si queremos que Oracle autentifique los permisos de usuario; se debe asignar el parámetro de inicialización REMOTE_LOGIN_PASSWORDFILE a EXCLUSIVE. Si estamos usando la autentificación del sistema operativo, no es necesario un fichero de contraseña; y por lo tanto debemos asignar REMOTE_LOGIN_PASSWORDFILE

a NONE. 4) Crear un fichero de parámetros de inicialización y ponerlo en la localización por defecto para nuestra plataforma, al menos inicialmente para la instalación. Bajo UNIX, el lugar por defecto es $ORACLE_HOME/dbs; bajo Windows, es $ORACLE_HOME\database. A continuación se muestra un fichero de inicialización de ejemplo:

# Cache and I/O

DB_BLOCK_SIZE=4096

DB_CACHE_SIZE=20971520

# Cursors and Library Cache

CURSOR_SHARING=SIMILAR

OPEN_CURSORS=300

# Diagnostics and Statistics

BACKGROUND_DUMP_DEST=/u01/oracle11g/admin/rjbdb/bdump

CORE_DUMP_DEST=/u01/oracle11g/admin/rjbdb/cdump

TIMED_STATISTICS=TRUE

USER_DUMP_DEST=/u01/oracle11g/admin/rjbdb/udump

# Control File Configuration

CONTROL_FILES=("/u01/oracle11g/prod/rjbdb/control01.ctl",

"/u02/oracle11g/prod/rjbdb/control02.ctl",

"/u03/oracle11g/prod/rjbdb/control03.ctl")

# Archive

LOG_ARCHIVE_DEST_1='LOCATION=/u06/oracle11g/oradata/rjbdb/archive'

# New log archive format. If compatibility 10.0 and up,

# this is enforced.

LOG_ARCHIVE_FORMAT=%t_%s_%r.dbf

# The following parameter is deprecated in 10iR1

# LOG_ARCHIVE_START=TRUE

# Shared Server

# Starts shared server if set > 0.

SHARED_SERVERS=2

# Uncomment and use first DISPATCHERS parameter

# below when your listener is

# configured for SSL

# (listener.ora and sqlnet.ora)

# DISPATCHERS = "(PROTOCOL=TCPS)(SER=MODOSE)",

# "(PROTOCOL=TCPS)(PRE=oracle.aurora.server.SGiopServer)"

DISPATCHERS="(PROTOCOL=TCP)(SER=MODOSE)",

Page 23: Java y Oracle 11g

Oracle /23

"(PROTOCOL=TCP)(PRE=oracle.aurora.server.SGiopServer)",

(PROTOCOL=TCP)

# Miscellaneous

COMPATIBLE=10.0.0

DB_NAME=rjbdb

# Distributed, Replication and Snapshot

DB_DOMAIN=rjbdba.com

REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE

# Network Registration

INSTANCE_NAME=rjbdb

# Pools

JAVA_POOL_SIZE=31457280

LARGE_POOL_SIZE=1048576

SHARED_POOL_SIZE=52428800

# Processes and Sessions

PROCESSES=150

# Redo Log and Recovery

FAST_START_MTTR_TARGET=300

# Resource Manager

RESOURCE_MANAGER_PLAN=SYSTEM_PLAN

# Sort, Hash Joins, Bitmap Indexes

SORT_AREA_SIZE=524288

# Automatic Undo Management

UNDO_MANAGEMENT=AUTO

UNDO_TABLESPACE=undotbs

5) Conectar la instancia usando SQL*Plus: SQLPLUS /NOLOG

CONNECT SYS/contraseña AS SYSDBA

Nótese que aunque la instancia misma ya exista, no hay mucho que podamos hacer puesto que aún no hemos creado la base de datos. 6) Crear un fichero de parámetros de servidor (SPFILE). Si el fichero de inicialización está en la localización por defecto, el siguiente comando creará el SPFILE:

CREATE spfile FROM pfile;

7) Empezar la instancia usando el siguiente comando: STARTUP NOMOUNT

Nótese que porque no tenemos una base de datos creada aún, ésta es la única opción que podemos emplear. 8) Ejecutar el comando CREATE DATABASE. A continuación hay un ejemplo:

CREATE DATABASE rjbdb

USER SYS IDENTIFIED BY paris703

USER SYSTEM IDENTIFIED BY tyler12

LOGFILE GROUP 1 ('/u02/oracle11g/oradata/rjbdb/redo01.log') SIZE 100M,

GROUP 2 ('/u04/oracle11g/oradata/rjbdb/redo02.log') SIZE 100M,

GROUP 3 ('/u06/oracle11g/oradata/rjbdb/redo03.log') SIZE 100M

MAXLOGFILES 6

MAXLOGMEMBERS 5

MAXLOGHISTORY 1

MAXDATAFILES 100

MAXINSTANCES 1

CHARACTER SET US7ASCII

NATIONAL CHARACTER SET AL16UTF16

DATAFILE '/u01/oracle11g/oradata/rjbdb/system01.dbf' SIZE 325M REUSE

EXTENT MANAGEMENT LOCAL

SYSAUX DATAFILE '/u01/oracle11g/oradata/rjbdb/sysaux01.dbf'SIZE 325M REUSE

Page 24: Java y Oracle 11g

Oracle /24

DEFAULT TABLESPACE tbs_1

DEFAULT TEMPORARY TABLESPACE tempts1

TEMPFILE '/u01/oracle11g/oradata/rjbdb/temp01.dbf'SIZE 20M REUSE

UNDO TABLESPACE undotbs

DATAFILE '/u02/oracle11g/oradata/rjbdb/undotbs01.dbf'

SIZE 200M REUSE AUTOEXTEND ON MAXSIZE UNLIMITED;

Hay varias cosas a destacar en este ejemplo. Se explicitan las contraseñas de SYS y SYSTEM; si no se especifican aquí, tendrán como valores por defecto "change_on_install" y "manager" respectivamente. Los grupos de archivos de deshacer tienen sólo un miembro cada uno; una vez que nuestra base de datos esté en producción deberíamos multiplexarlos. Ya que especificamos un tablespace de deshacer con el parámetro UNDO_TABLESPACE en el archivo de parámetros de inicialización, necesitamos crear este tablespace aquí; sino, la instancia no se iniciará. Después de que se cree la base de datos, es montada y abierta para su uso. 9) Crear tablespaces adicionales para los usuarios, índices y aplicaciones. 10) Construir las vistas del diccionario de datos con los scripts proporcionados catalog.sql y catproc.sql. El script catalog.sql crea vistas a partir de las tablas del diccionario de datos, vistas de rendimiento dinámico, y sinónimos públicos para muchas de las vistas. A las vistas del grupo PUBLIC se les concede sólo acceso de lectura. El script catproc.sql establece PL/SQL. 11) Crear respaldo de la base de datos usando copias en frío o el Administrador de Respaldo. En el caso de que la base de datos falle en las etapas iniciales del despliegue, tendremos una base de datos completa y ejecutándose para restaurar, y más probablemente no tendremos que recrear la base de datos desde el principio.

3. Servidor de Oracle

Un servidor Oracle es el software que permite una administración y desarrollo de bases de datos de Oracle. Tiene tres posibilidades de ejecución:

▪ Local o basada en host. El servidor se ejecuta en la misma máquina en la que se conectan los clientes. La versión personal de Oracle Database produce servidores de este tipo. ▪ Cliente-Servidor. Enfoque más típico. El servidor reside en un ordenador distinto respecto al que los usuarios van a usar para conectarse a la base de datos. ▪ Cliente-Servidor de Aplicaciones-Servidor. Los usuarios acceden a un servidor de aplicaciones (Oracle Application Server) que, a su vez, accede al servidor Oracle. Los tres elementos (cliente, servidor de aplicaciones, servidor Oracle) pueden estar en tres máquinas distintas.

3.1. Elementos del servidor Oracle

El servidor Oracle está formado por dos elementos: ▪ La instancia de la base de datos. Consta de datos (llamados estructuras de memoria) y de procesos en memoria (procesos background) necesarios para dar servicio a los usuarios de la base de datos. Puede haber más de una instancia si se distribuye la base de datos en más de una máquina. Cada instancia abre una y sólo una base de datos. ▪ Ficheros en disco. Representan la base de datos en sí. Consta de:

- Estructuras lógicas: Tablespaces, objetos del esquema de usuario. - Estructuras físicas: Los ficheros de datos almacenados en disco. Los ficheros de datos (asociados a los tablespaces), los ficheros deshacer y los ficheros de control.

3.2. Conexiones.

Para establecer una sesión con la base de datos, el usuario necesita conectarse con la instancia de la base de datos. Normalmente esto significa arrancar una herramienta cliente como SQL*Plus o la Consola de Administración de Oracle, o ejecutar una aplicación de desarrollo de bases de datos (como Oracle Forms); entonces se ejecuta un proceso de usuario. Cuando esto ocurre, en el servidor se establece un proceso de servidor. Este proceso es el encargado de comunicar al usuario con la instancia Oracle en nombre del proceso de usuario. Cada vez que el usuario ejecuta instrucciones SQL, éstas son transmitidas a la instancia Oracle por el proceso servidor. De este modo una conexión es un camino entre un proceso de usuario y un servidor Oracle. Cada sesión es una conexión de un usuario con el servidor Oracle. Un usuario puede establecer múltiples sesiones (si se conecta desde diferentes herramientas y máquinas).

Page 25: Java y Oracle 11g

Oracle /25

3.3. Estructura de las bases de datos Oracle.

Desde el punto de vista de Oracle, una base de datos es una colección de datos tratados como una única unidad. Una base de datos Oracle contiene tres tipos de ficheros:

▪ Archivos de datos (.DBF). Contiene los datos actuales de la base de datos así como el diccionario de datos. ▪ Archivos rehacer o redo logs (.LOG). Almacenan datos de transacciones recuperables en caso de error grave. ▪ Archivos de control (.CTL). Necesarios para mantener la integridad de la base de datos.

Además se utilizan otros archivos de forma auxiliar ▪ Archivos de parámetros (.ora). Que definen algunas características de una instancia Oracle. ▪ Archivos de contraseñas. Que sirven para autentificar a los usuarios. ▪ Copias de archivos rehacer. Utilizadas para la recuperación de datos.

3.4. Instancia de la base de datos.

La instancia de la base de datos es uno de los dos elementos de cualquier base de datos Oracle. Sirve para gestionar los datos de la base de datos y proporcionar servicio a los usuarios que acceden a la misma.

Está compuesta de: ▪ Estructuras en memoria. ▪ Procesos en segundo plano (background).

En las estructuras en memoria nos encontramos con: ▪ SGA, la abreviatura de System Global Area o Área Global de Sistema. Está situada al inicio de los datos de la instancia y contiene los datos e información de control de la instancia. Está formada por las siguientes estructuras:

- Shared pool, o fondo común compartido. Almacena las últimas instrucciones SQL y PL/SQL ejecutadas. Posee dos estructuras internas: la caché de instrucciones (Library cache), que almacena las últimas instrucciones SQL y PL/SQL ejecutadas; y la caché del diccionario de datos, que almacena las últimas definiciones de la base de datos utilizadas (tablas, índices, privilegios, usuarios,...). Cada vez que una instrucción utiliza un nombre de la base de datos (tabla, índice,...) se comprueba en el diccionario de datos y se almacena en este caché. De este modo la siguiente vez no hace falta acceder al diccionario de datos real. - Caché buffer de la base de datos. Almacena los últimos bloques de datos accedidos por los usuarios. - Buffer de archivo deshacer. Almacena los últimos cambios realizados a los bloques de datos de la base de datos. - Large pool. Opcional. Se utiliza como memoria de sesión y para realizar operaciones de backup. - Java pool. Opcional. Se utiliza como caché de los comandos Java.

Page 26: Java y Oracle 11g

Oracle /26

- Otras estructuras ▪ PGA, la abreviatura de Program Global Area o Zona global de los programas. En ella se almacenan los datos correspondientes a un proceso (sólo un proceso puede utilizar esta área). Incluye:

- Áreas de ordenación. Para acelerar las tareas de ordenación de datos. - Información de sesión. Usuario, privilegios,... - Estado del cursor. Tareas SQL actualmente en ejecución. - Espacio de pila. Variables y otros datos.

En Oracle los procesos pueden ser de estos tipos: ▪ Proceso de usuario. Lanzado por el usuario para pedir interacción con la base de datos. ▪ Proceso de servidor. Hacen de enlace entre los procesos de usuarios y el servidor Oracle. Se utilizan como manejadores de los procesos de usuario. Los comandos de usuario se envían a estos procesos que se encargan de solicitar peticiones a la base de datos mediante el interfaz de programas de Oracle (OPI, Oracle Program Interface). ▪ Procesos en segundo plano (background). Cada instancia de Oracle arranca una serie de procesos de fondo. Los procesos obligatorios son:

- DBWR (DataBase WRiter). Proceso encargado de escribir en los ficheros de datos los buffers más antiguos de la memoria, para que la base de datos vaya almacenando los cambios. - LGWR (LoG WRiter). Escribe los datos a los ficheros deshacer (redo) desdela caché de archivos deshacer. - CKPT. Actualiza todas las cabeceras de los ficheros de datos para que aparezca la nueva disposición de datos. Esto ocurre cuando se genera un punto de comprobación. - SMON (System MONitor). Permite recuperar la instancia de la base de datos en caso de caída fatal (cuando el sistema falla por ejemplo). - PMON (Process MONitor). Es el encargado de gestionar adecuadamente los procesos que fallan. Ante caídas de procesos, PMON se encarga de restaurarlos datos adecuadamente. - SQL *Net Listener. Es el encargado de encaminar por una red solicitudes de un cliente a un servidor de base de datos Oracle. Este proceso escuchador (listener) está tanto en el cliente como en el servidor. Puede encaminar solicitudes que se dirigen a varias instancias.

3.5. Procesamiento de instrucciones SQL.

Para poder ejecutar SQL sobre la base de datos, hay que conectarse con la instancia Oracle de la base de datos, lo cual requiere la comunicación entre un proceso cliente y el servidor (el proceso cliente puede ser una instancia de SQL*Plus, por ejemplo). Los componentes utilizados por Oracle para procesar el SQL dependen del código enviado:

- Las consultas devuelven filas. - Las instrucciones DML (Lenguaje de Manipulación de Datos) graban cambios. - La instrucción COMMIT asegura el proceso de la transacción.

Pero de manera general los pasos en ese proceso son: 1) El usuario abre la herramienta que permite el envío de peticiones SQL (por ejemplo SQL*Plus). 2) El usuario introduce su nombre de usuario y contraseña. 3) Oracle consulta el diccionario de datos para verificar la existencia del usuario y para validar su permiso de conexión. Si lo tiene, se produce la conexión. 4) El usuario escribe la instrucción SQL (por ejemplo, una instrucción de modificación). 5) Oracle traduce la instrucción con el analizador de instrucciones (devolvería un error si la instrucción no es válida). 6) Oracle traduce los nombres usados en la instrucción con la ayuda del diccionario de datos. 7) Si es una instrucción de mostrar datos (SELECT), comprueba si otros usuarios han enviado hace poco esa misma instrucción; eso lo comprueba en el caché de instrucciones de la SGA. Si la instrucción está ahí coge los resultados del búfer caché de la base de datos. 8) Si la instrucción conlleva cambios, el servidor bloquea las filas que se modificarán. 9) La base de datos graba los cambios (si los hubo) y actualiza los archivos de deshacer. 10) La base de datos graba los nuevos valores para los datos. 11) Oracle libera del bloqueo los registros. 12) El usuario recibe un mensaje de éxito.

Page 27: Java y Oracle 11g

Oracle /27

3.6. Archivos de inicialización

Además de estructuras de disco y de memoria, un servidor Oracle necesita ciertos archivos para poder ejecutarse. Estos archivos se establecen durante la creación de la base de datos, y se consultarán cada vez que se arranque la base de datos, por lo que deben estar disponibles. Esta sección explica los ficheros de parámetros que utiliza la base de datos Oracle para arrancar: init.ora y spfile.ora. 3.6.1. Parámetros de inicialización y configuración. Oracle es una base de datos configurable mediante una serie de parámetros, de forma que el administrador puede optimizar los valores de esta base de datos. Estos parámetros de optimización y configuración de base de datos se almacenan en un fichero. Este fichero es el primero al que se accede al arrancar la base de datos Oracle y se denomina «init.ora». En este fichero se escriben los parámetros de configuración de Oracle; pero si en este archivo alguno de los parámetros de Oracle configurables no se encuentra, entonces tomará el valor que Oracle tenga por defecto. Existen tres tipos de parámetros en Oracle:

• Parámetros "fijos": Son parámetros que una vez instalada la base de datos no se pueden volver a modificar/configurar. El juego de caracteres es un claro ejemplo. • Parámetros estáticos: Son parámetros que se pueden modificar, pero su modificación implica cerrar la base de datos y volverla a abrir para que los lea del fichero y pueda realizar el cambio. • Parámetros dinámicos: Son parámetros cuyo valor se puede cambiar sin necesidad de cerrar la base de datos, a diferencia de los estáticos.

3.6.2. Ubicación y nomenclatura del fichero «init.ora». El archivo «init.ora» se encuentra en Windows dentro del directorio ORACLE_HOME\database y en UNIX dentro del directorio ORACLE_HOME/dbs. El nombre del archivo siempre corresponderá a «initsid.ora» siendo sid el nombre de la base de datos. (Éste es el nombre que Oracle buscará al arrancar la base de datos.) 3.6.3. El fichero «spfile.ora». El fichero «init.ora» no es el único archivo de parámetros que podemos encontrar en las base de datos Oracle. A partir de la versión 9 se puede encontrar el archivo «spfile.ora». Éste es el primer archivo que va a "buscar" Oracle en su arranque de base de datos. Si no encuentra este archivo entonces irá a buscar el archivo «init.ora». Este archivo está codificado y las modificaciones en él se realizarán mediante una serie de comandos Oracle que posteriormente indicaremos. La ubicación de este archivo es la misma que la de «init.ora». 3.6.4. Cambio de los valores de los parámetros. Si queremos realizar algún cambio en algún parámetro de base de datos tenemos que diferenciar dos cosas:

• Si el cambio es en «init.ora» o «spfile.ora». • Tipo de parámetro sobre el que se quiere hacer el cambio.

Vamos a explicar como realizar un cambio en el fichero «init.ora», para ello tenemos que tener en cuenta el tipo de parámetro que vamos a cambiar. Como se ha indicado, existen tres tipos de parámetros; dejando a un lado los parámetros fijos (aquellos que no se pueden cambiar una vez instalada la base de datos) nos quedan los parámetros estáticos y los dinámicos. Para modificar un parámetro estático nos basta con editar el fichero «init.ora» y modificar o añadir ahí el parámetro nuevo reiniciando la base de datos para que coja estos cambios. En cuando a los parámetros dinámicos podemos cambiarlos en tiempo real sin parar la base de datos mediante la siguiente sentencia: ALTER SYSTEM SET parámetro = valor;

Este cambio pasa automáticamente a ser efectivo en la base de datos, aunque tenemos que tener en cuenta que la próxima vez que la base de datos sea iniciada se volverá a leer el fichero de parámetros «init.ora». Para realizar cualquier cambio en «spfile.ora» hay que usar el comando ALTER SYSTEM añadiendo la cláusula SCOPE con una serie de valores que detallaremos a continuación con un ejemplo. Para cambiar el parámetro shared_pool_size a 150 Megas: ALTER SYSTEM SET shared_pool_size= 150 SCOPE=spfile

En este caso hemos cambiado el parámetro y estos cambios se han recogido en el archivo spfile, por lo tanto permanecerá cuando sea reiniciada la base de datos. ALTER SYSTEM SET shared_pool_size= 150 SCOPE=memory

En este caso se ha cambiado el parámetro y estos cambios se han recogido solamente en memoria, esto quiere decir que se hacen efectivos al momento (si el tipo de parámetro lo permite) pero este cambio no se verá reflejado en el archivo de parámetros. ALTER SYSTEM SET shared_pool_size= 150 SCOPE=both

Page 28: Java y Oracle 11g

Oracle /28

En este caso el parámetro se cambia tanto en el spfile como en memoria.

3.7. Arranque y parada de la base de datos.

Durante el arranque y parada de la BD se suceden un conjunto de eventos que llevan a la base de datos por varios estados. Para que los usuarios puedan acceder a la base de datos el administrador debe abrirla con el comando STARTUP OPEN. El siguiente es un ejemplo de apertura de una base de datos llamada Test. STARTUP OPEN Test

ORACLE instancia iniciada.

Total System Global Area 4512688 bytes.

Fixed Size 39732 bytes.

Variable Size 4055164 bytes.

Database Buffers 409600 bytes.

Redo Bufers 8192 bytes.

Database mounted.

Database opened.

Cuando se ejecuta el comando STARTUP OPEN la base de datos pasa por tres estados (NOMOUNT, MOUNT y OPEN) antes de estar disponible. El administrador de base de datos puede arrancar la base de datos hasta uno de los estados con el comando STARTUP: STARTUP NOMOUNT o STARTUP MOUNT. A continuación se describe cada uno de los estados por los que pasa la base de datos en el proceso de arranque.

Estado «nomount». Se arranca la base de datos en el estado «nomount» con el siguiente comando:

STARTUP OPEN Test

Oracle lee el fichero init.ora, localiza los ficheros de control, crea e inicializa la SGA, y finalmente arranca todos los procesos Oracle. En este estado la instancia de base de datos está arrancada. Se deberá llevar la base de datos al estado «nomount» cuando se esté creando la base de datos o cuando se esté restaurando un fichero de control después de haberlo perdido.

Estado «mount». Se cambia la base de datos al estado «mount» con el siguiente comando:

ALTER DATABASE MOUNT;

Oracle abre los ficheros de control para localizar los ficheros de datos y los redo log, pero no se realiza ninguna comprobación en ellos en este momento. La instancia monta la base de datos y la bloquea, verificando que ninguna otra instancia ha montado la misma base de datos. Hay varias razones para querer tener la base de datos en el estado «mount». En general, todas las sentencias SQL del tipo ALTER DATABASE se deben ejecutar en esta etapa. Algunas de las operaciones a realizar cuando la base de datos está montada son:

• Efectuar recuperaciones. • Poner online/offline un fichero de datos. • Recolocar los ficheros de datos y redo log. • Crear un nuevo grupo o miembro redo log, o borrar un grupo o miembro redo log existente.

Estado «open». Se cambia la base de datos al estado «open» con el siguiente comando:

ALTER DATABASE OPEN;

Durante esta etapa, la instancia abre la base de datos, bloquea los ficheros de datos, y abre todos los ficheros redo log. Si la instancia abre la base de datos después de una terminación anormal, o después de una caída, se ejecutará automáticamente el proceso de recuperación utilizando los ficheros redo log. Al final de esta etapa la base de datos está dispuesta para su uso normal.

Para parar la base de datos se usa el comando SHUTDOWN como se puede ver en el siguiente ejemplo: SHUTDOWN

Database closed.

Database dismounted.

ORACLE instance shut down.

Este comando admite tres opciones: NORMAL, IMMEDIATE y ABORT.

SHUTDOWN NORMAL

Page 29: Java y Oracle 11g

Oracle /29

Impide el acceso a la base de datos, espera a que todos los usuarios completen todas sus peticiones y se desconecten del servidor. Purga todos los búferes de datos y cachés de redo log, actualizando los ficheros de datos y de redo log, se eliminan los bloqueos de ficheros, se completan las transacciones en marcha, se actualizan las cabeceras de ficheros, elimina los hilos, libera los bloqueos de la base de datos por parte de la instancia, y sincroniza los ficheros de control y de datos. En resumen, la opción «normal» cierra la base de datos, la desmonta y para la instancia con cuidado y es la opción recomendada para parar la base de datos.

SHUTDOWN IMMEDIATE En ciertas ocasiones puede ser necesario parar la base de datos de modo inmediato. Si es así, las sentencias en proceso son terminadas inmediatamente, cualquier transacción no confirmada es vuelta atrás y la base de datos es parada. La única desventaja de utilizar esta opción es que Oracle no espera a que los usuarios se desconecten. Sin embargo, la base de datos será consistente y no se necesitará recuperación en el siguiente arranque.

SHUTDOWN ABORT En situaciones de emergencia, y cuando todo lo demás falla, se debe realizar una parada de este tipo. Por ejemplo, cuando un proceso de la instancia muere y la base de datos no puede pararse de modo normal o inmediato. Cuando se utiliza la opción ABORT las sentencias SQL son terminadas bruscamente, y las transacciones no confirmadas no son vueltas atrás. Parar la base de datos con la opción ABORT requiere recuperación en la siguiente vez que arranque la base de datos y esta opción debe ser utilizada sólo cuando no quede más remedio.

4. Introducción a los conceptos del sistema Oracle

4.1. Almacenamiento.

Una base de datos tiene una estructura lógica (que se manipula mediante comandos) y una estructura física (la que realmente se almacena en disco). La estructura lógica de Oracle está formada por: tablespaces, segmentos, extensiones y bloques de datos. La estructura física está formada por: ficheros de datos y bloques de sistema.

4.2. Transacciones.

Los cambios en la base de datos no son guardados hasta que tras una serie de instrucciones se decide llevar a cabo esos cambios. Hasta ese momento todo lo realizado se toma como provisional. Un fallo en la máquina permitiría invertir los cambios. Una transacción son varias operaciones SQL que forman una unidad de trabajo. Comienza cuando una persona se conecta y de ahí hasta que ejecuta la instrucción COMMIT (ejecutar la transacción) o ROLLBACK (anular la transacción). La anulación deja la base de datos en el estado anterior al comienzo de la transacción. Tras un COMMIT o un ROLLBACK comienza la siguiente transacción. En Oracle se admite además el uso de puntos de ruptura (savepoints) para almacenar valores intermedios y volver a cualquier de ellos si interesa. Pero esto ralentiza excesivamente el sistema.

4.3. Usuarios.

Los usuarios son las personas que acceden de una forma o de otra a la base de datos. Cada usuario tiene una vista determinada de la base de datos. Hay varios conceptos sobre los usuarios a tener en cuenta.

• Privilegios. Controlan el permiso que posee un usuario de ejecutar una determinada instrucción SQL. Un usuario que quiera crear una tabla, deberá tener el privilegio (o permiso) adecuado para ello. Además se pueden colocar privilegios en los objetos; es decir, un usuario propietario de una tabla puede otorgar privilegios a esa tabla (se trataría de un privilegio a nivel de objeto) para que haya otros usuarios que la puedan usar. • Rol. Son agrupaciones de privilegios que facilitan la tarea de gestionar a los usuarios. Así, cuando una serie de usuarios van a tener los mismos privilegios, se crea un rol que contenga esos privilegios y a esos usuarios se les asigna el rol. Oracle proporciona varios roles ya predefinidos, por ejemplo el rol DBA da privilegio absoluto a un usuario. • Esquemas. Están asociados a los usuarios. Agrupan los objetos lógicos que pertenecen al usuario. Es decir, es el conjunto de tablas, vistas, sinónimos, instantáneas, enlaces de base de datos, procedimientos y funciones, paquetes, etc. creados por un usuario. Cada usuario tiene su propio esquema y, en principio, un usuario no tiene acceso a los elementos de otro

Page 30: Java y Oracle 11g

Oracle /30

usuario, salvo que sea un administrador o que otro usuario ceda el privilegio de utilización de uno o más de sus objetos al resto de usuarios. La primera vez que un usuario crea un objeto en la base de datos, se crea un esquema con su mismo nombre.

4.4. Pérdidas de información.

Es una de las tareas y herramientas fundamentales que nos proporcionan las bases de datos. Hay posibilidad de perder datos de nuestra base de datos por alguna de estas razones:

• Fallo en una instrucción. Hay instrucciones que pueden provocar la pérdida no deseada de cientos de registros en un momento (DELETE o UPDATE). En ese caso basta con ejecutar una instrucción ROLLBACK

antes de que las instrucciones previas se confirmen. • Fallo en la comunicación. Si una conexión de usuario se corta anulando el proceso de usuario relacionado. En ese caso, Oracle anula los cambios de la última transacción (el resto de transacciones sí se almacena). • Caída del servidor. Puede que la instancia Oracle se deje de ejecutar. En ese caso basta con lanzar de nuevo la instancia. El proceso SMON se encargará de grabar los archivos deshacer y aplica de nuevo las transacciones confirmadas. Se anulan los cambios no confirmados. • Pérdida de datos en los archivos. Es el único caso en el que tiene que intervenir el administrador. La única posibilidad es recuperarlos de una copia de seguridad.

4.5. Copias de seguridad.

Es una de las herramientas fundamentales de toda base de datos. Al hacer la copia de seguridad se nos permite recuperar la información de esa copia. Eso hace que perdamos definitivamente los datos perdidos desde la última copia de seguridad, de ahí la importancia de hacer copias a menudo. Hay una posibilidad de perder menos información y es hacer que la base de datos se ejecute en modo "Archivo Log", lo que significa que se almacena, en un archivo de datos especial, los datos que se van rechazando en los registros de deshacer por estar llenos. Lo malo de esta opción es que el servidor funciona más lento; lo bueno es que en caso de desastre se pueden recuperar los datos almacenados. Hay dos tipos de copia de seguridad:

• En frío. La copia se realiza tras parar la instancia de Oracle. La copia de seguridad almacena todos los datos de la base (incluidos los archivos de control y de deshacer). • En caliente. Ya que en muchos casos no se puede parar la instancia tan fácilmente (por estar 24 horas al día funcionando). En ese caso es una indicación la que se hace a la base de datos y la copia se realiza desde el sistema operativo (copiando sin más).

Otras posibilidades de copias son: • Catálogos de copia de seguridad. Que almacenan información sobre las copias realizadas, fechas, datos, estructura,... • Copias de seguridad incrementales. Un problema de las copias es que, en bases extensas, la copia tarda muchas horas. Cuando la copia termina, la información ya está desfasada. Las copias incrementales sólo almacenan los datos que han cambiado recientemente.

4.6. Bases de datos distribuidas.

Se trata de una base de datos a nivel lógico (los usuarios la manejan como una base de datos normal), pero que en realidad (físicamente) está implementada en varias ubicaciones físicas, incluso en máquinas diferentes y distantes. Cada máquina ejecuta su propia instancia y conjuntos de archivos y todas se conectan en red para hacer que el usuario no tenga que cambiar su código para reflejar esta distribución. La dificultad de esta estructura suele estar aliviada por medio de instantáneas que graban momentáneamente los datos de las tablas distantes. Permiten trabajar con los datos copiados y se programan para que cada cierto tiempo recojan nuevamente los datos a fin de reflejar sus cambios. Gracias a las instantáneas no hace falta una sobrecarga excesiva de la base de datos.

4.7. Herramientas de Oracle.

El software del sistema de bases de datos Oracle incorpora herramientas para realizar la mayoría de tareas comunes en una base de datos:

▪ Oracle Universal Installer. Gestor de instalaciones, controla cada nueva instalación de software Oracle a fin de que se integren de la mejor manera posible.

Page 31: Java y Oracle 11g

Oracle /31

▪ SQL*plus. Programa cliente que permite conexión con el servidor Oracle para enviarle secuencias SQL y PL/SQL. ▪ iSQL*plus. Permite conexiones al servidor Oracle con la misma finalidad que el anterior pero utilizando un navegador de Internet, lo que facilita el trabajo. ▪ SQL*plus WorkSheet. Permite conexiones al servidor de Oracle, utilizando un entorno más potente (procede del Oracle Enterprise Manager). ▪ Oracle Enterprise Manager. Entorno que permite la administración y configuración completa del servidor Oracle. ▪ SQL*Loader. Permite cargar en bases de datos de Oracle información que procede de un archivo de texto. Necesaria para utilizar en las bases de datos de Oracle, información que procede de otro software. ▪ Import/Export. Para importar y exportar datos entre instancias de Oracle. De un servidor a otro por ejemplo. También se utiliza como herramienta de copia de seguridad. ▪ Servidor http de Oracle. Basado en el servidor Apache, permite opciones de documentación y sobre todo la comunicación directa a través de iSQL*Plus con el servidor Oracle sin tener necesidad de instalar software adicional. ▪ Net Manager. Permite la administración de los servicios de red a fin de configurar las conexiones hacia instancias de Oracle. ▪ Oracle Forms. Permite crear aplicaciones visuales sobre bases de datos de Oracle. ▪Oracle Reports. Asistente para la producción de informes. ▪ Oracle Designer. Herramienta CASE de Oracle, para crear esquemas en el ordenador y que el software produzca los resultados del mismo. ▪ Oracle JDeveloper. Crea aplicaciones Java pensadas para desarrollar formularios sobre datos de Oracle ▪ Oracle Developer Suite. Integra todos los componentes anteriores. ▪ Oracle AS (Application Server). Servidor de aplicaciones de Oracle. Permite compilar aplicaciones J2EE. ▪ Pro C/C++. Precompilador de C/C++ para Oracle.

Page 32: Java y Oracle 11g

Oracle /32

II. LENGUAJE DE CONSULTAS

1. SQL para Oracle

1.1. Introducción.

El lenguaje de consulta estructurado SQL (Standar Query Language) es un lenguaje de base de datos normalizado, utilizado por el motor de los Gestores de Base de Datos relacionales que existen en la actualidad. SQL pretende ser un lenguaje que simula su escritura en lenguaje normal. De ahí que se le considere un lenguaje de cuarta generación. Consta de palabras especiales y de expresiones. Se trata de un lenguaje que intenta agrupar todas las funciones que se le pueden pedir a una base de datos. Las instrucciones SQL se introducen a través de una herramienta que las traduce inmediatamente a la base de datos, por lo que se ejecutan al instante. Las instrucciones SQL se colocan como parte del código de otro lenguaje anfitrión (C, Java, Pascal, Visual Basic, etc.). Estas instrucciones están separadas del resto del código de forma conveniente. Al compilar el código se utiliza un precompilador de la propia base de datos para traducir el SQL. Las aplicaciones clientes deben acceder al servidor de Oracle a través del puerto 1521.

1.2. Código SQL y normas de escritura.

El código SQL consta de los siguientes elementos: - Comandos. Las distintas instrucciones que se pueden realizar desde SQL: SELECT, DML, DDL, instrucciones de transferencia, e instrucciones de control del lenguaje. - Cláusulas. Son palabras especiales que permiten modificar el funcionamiento de un comando (WHERE, ORDER BY, etc.). - Operadores. Permiten crear expresiones complejas. - Funciones. Para conseguir valores complejos (SUM(), DATE(), etc.). - Constantes. Valores literales para las consultas, números, textos, caracteres, etc. - Datos. Obtenidos de la propia base de datos

Las normas de escritura son: - En SQL no se distingue entre mayúsculas y minúsculas. Da lo mismo cómo se escriba. - El final de una instrucción lo determina el signo del punto y coma. - Los comandos SQL (SELECT, INSERT, ...) pueden ser partidos por espacios o saltos de línea antes de finalizar la instrucción. - Se pueden tabular líneas para facilitar la lectura, si fuera necesario. - Los comentarios en el código SQL comienzan por /* y terminan por */.

1.3. SQL*Plus.

Para poder enviar sentencias SQL al servidor Oracle, éste incorpora la herramienta SQL*Plus. Toda instrucción SQL que el usuario escribe es verificada por este programa. Si la instrucción es válida se envía a Oracle, el cual retornará la respuesta a la instrucción; respuesta que puede ser transformada por el programa SQL*Plus para modificar su salida. Para que el programa SQL*Plus funcione en el cliente, el ordenador cliente debe haber sido configurado para poder acceder al servidor Oracle. En cualquier caso, al acceder a Oracle con este programa siempre preguntará por el nombre de usuario y contraseña. Éstos son datos que nos tiene que proporcionar el administrador de la base de datos Oracle (DBA). Para conectarnos mediante SQL*Plus podemos ir a la línea de comandos y escribir el texto sqlplus. A continuación aparecerá la pantalla siguiente:

Page 33: Java y Oracle 11g

Oracle /33

En esa pantalla se nos pregunta el nombre de usuario y contraseña para acceder a la base de datos (información que deberá indicarnos el administrador o DBA). Tras indicar esa información conectaremos con Oracle mediante SQL*Plus, y veremos aparecer el símbolo: SQL>

Tras el cual podremos comenzar a escribir nuestros comandos SQL. Ese símbolo puede cambiar por un símbolo con números 1, 2, 3, etc.; en ese caso se nos indica que la instrucción no ha terminado y la línea en la que estamos. Otra posibilidad de conexión consiste en llamar al programa SQL*Plus indicando la contraseña y base de datos a conectar. El formato es: slplus usuario/contraseña@nombreServicioBaseDeDatos

Ejemplo: slplus usr1/[email protected]

En este caso conectamos con SQL*Plus indicando que somos el usuario usr1 con contraseña miContra y que conectamos a la base de datos inicial de la red forempa.net. El nombre de la base de datos no tiene porqué tener ese formato, habrá que conocer cómo es el nombre que representa a la base de datos como servicio de red en la red en la que estamos.

1.4. Versión gráfica de SQL*Plus.

Oracle incorpora un programa gráfico para Windows para utilizar SQL*Plus. Se puede llamar a dicho programa desde las herramientas instaladas en el menú de programas de Windows, o desde la línea de programas escribiendo sqlplusw. Al llamarle aparece esta pantalla:

Como en el caso anterior, se nos solicita el nombre de usuario y contraseña. La «cadena de Host» es el nombre completo de red que recibe la instancia de la base de datos a la que queremos acceder en la red en la que nos encontramos. También podremos llamar a este entorno desde la línea de comandos utilizando la sintaxis comentada anteriormente. En este caso: slplusw usuario/contraseña@nombreServicioBaseDeDatos

Esta forma de llamar al programa permite entrar directamente sin que se nos pregunte por el nombre de usuario y contraseña.

1.5. iSQL*Plus.

Es un producto ideado desde la versión 9i de Oracle. Permite acceder a las bases de datos Oracle desde un

Page 34: Java y Oracle 11g

Oracle /34

navegador. Para ello necesitamos tener configurado un servidor web Oracle que permita la conexión con la base de datos. Utilizar iSQL*Plus es indicar una dirección web en un navegador, esa dirección es la de la página iSQL*Plus de acceso a la base de datos: http://host_oracle:7778/isqlplus. (El número de puerto, en este caso 7778, puede variar en cada instalación.) Desde la página de acceso se nos pedirá nombre de usuario, contraseña y nombre de la base de datos con la que conectarnos (el nombre de la base de datos es el nombre con el que se la conoce en la red). Si la conexión es válida aparece esta pantalla:

En esa pantalla, en el apartado «Introducir Sentencias», se escribe la sentencia que deseamos enviar. El botón «Ejecutar» hace que se valide y se envíe a Oracle. Se pueden almacenar sentencias SQL usando el botón «Examinar» y cargar sentencias previamente guardadas mediante «Cargar archivos de comandos».

2. Estructura del lenguaje SQL.

En SQL se distinguen los siguientes tipos de instrucciones: - SELECT. Se trata del comando que permite realizar consultas sobre los datos de la base de datos. Obtiene datos de la base de datos. - DML, Data Manipulation Language (Lenguaje de manipulación de datos). Modifica filas (registros) de la base de datos. Lo forman las instrucciones INSERT, UPDATE, MERGE y DELETE. - DDL, Data Definition Language (Lenguaje de definición de datos). Permiten modificar la estructura de las tablas de la base de datos. Lo forman las instrucciones CREATE, ALTER, DROP, RENAME y TRUNCATE. - Instrucciones de transferencia. Administran las modificaciones creadas por las instrucciones DML. Lo forman las instrucciones ROLLBACK, COMMIT y SAVEPOINT. - DCL, Data Control Language (Lenguaje de control de datos). Administran los derechos y restricciones de los usuarios. Lo forman las instrucciones GRANT y REVOKE.

2.1. Tipos de datos.

Como todo lenguaje de programación, SQL-Oracle posee una serie de tipos de datos. Éstos se corresponden con los tipos de datos que pueden utilizarse en Oracle al definir tablas.

Page 35: Java y Oracle 11g

Oracle /35

Datos numéricos:

Number(38) Equivale al tipo Integer, Int o SamllInt

Number Equivale al tipo Float, Double o Real

Number(m,d) Números decimales de coma fija, con m cifras en total y d decimales

ROWID Valor hexadecimal que representa la dirección única de una fila en su tabla.

Datos de carácter:

Char(n) Almacena n caracteres en formato ASCII (como máximo 2000). Siempre se utilizan los

n caracteres indicados, incluso si la entrada de datos es inferior. Cuando se recuperen los datos es como si fuesen rellenados con espacios en blanco hasta completar la longitud del campo.

Varchar2(n) Almacena n caracteres en formato ASCII (como máximo 4000). Al recuperar los datos, sólo se recupera el texto asignado con su longitud original.

Nchar(n) Almacena n caracteres en formato UNICODE. Siempre se utilizan los n caracteres indicados, incluso si la entrada de datos es inferior.

Nvarchar2(n) Almacena n caracteres en formato UNICODE.

Datos de fecha:

Date Almacena fechas en formato día, mes, año, hasta el nivel de segundos.

TimeStamp Almacena fechas hasta el nivel de fracciones de segundo.

Interval Almacena intervalos de tiempo. Por ejemplo, para indicar un período de 3 años y algunos meses:

CREATE TABLE tiempo (meses INTERVAL YEAR(3) TO MONTH);

INSERT INTO tiempo VALUES ('3-2'); Para indicar un intervalo hasta segundos (sin decimales en segundos):

CREATE TABLE tiempo (días INTERVAL DAY(3) TO SECOND(0));

INSERT INTO tiempo VALUES('2 7:12:23');

El valor entre paréntesis indicado para YEAR y DAY establece la precisión del año y del día respectivamente.

Datos binarios grandes:

Raw Sirve para almacenar valores binarios de hasta 2000 bytes (se puede especificar el tamaño máximo entre paréntesis).

Long Raw Almacena hasta 2GB.

Blob, Clob,

Nclob y Bfile

Almacenan datos binarios de varios gigas (imágenes, vídeos, etc.).

Datos de texto grandes:

Long Clob Almacenan secuencias de caracteres de varios gigas.

2.2. Operadores

SQL-Oracle incluye los operadores habituales:

Operadores de comparación Significado

= Igual que

<> ó != ó ^= Distinto de

< Menor que

<= Menor o igual que

> Mayor que

>= Mayor o igual que

Operadores lógicos Significado

AND Y lógico

OR O lógico

NOT Complementario

Operadores para nulos Significado

IS NULL Indica si una expresión es nula

Page 36: Java y Oracle 11g

Oracle /36

IS NOT NULL Indica si una expresión no es nula

Operadores para texto Significado

|| Concatenador de texto

LIKE Evalúa un texto contra una expresión regular

2.3. Funciones predefinidas.

Todos los SGBD implementan funciones para facilitar la creación de consultas complejas. Esas funciones dependen del SGBD que utilicemos; las que aquí se comentan son algunas de las que se utilizan con Oracle. Oracle proporciona una tabla llamada DUAL con la que se permiten hacer pruebas. Esa tabla tiene un solo campo (llamado DUMMY) y una sola fila de modo que es posible hacer pruebas sobre ella. Por ejemplo, la consulta: SELECT SQRT(5) FROM DUAL;

Muestra una tabla con el contenido del cálculo SQRT(5) (la raíz cuadrada de 5). 2.3.1. Funciones de cadena.

ASCII(texto) Retorna la representación decimal (en el juego de caracteres de la base de datos) del primer caracter del texto.

CHR(código) Retorna el caracter que se corresponde con el código dado en el juego de caracteres de la base de datos.

CONCAT(texto1, texto2) Concatena dos textos (igual que el operador ||).

INITCAP(texto) Coloca la primera letra de cada palabra en mayúsculas.

INSTR(texto, textoBuscado

[,posInicial [, nAparición]])

Obtiene la posición en la que se encuentra el texto buscado en el texto inicial. Se puede empezar a buscar a partir de una posición inicial concreta e incluso indicar

el número de aparición del texto buscado. Ejemplo, si buscamos la letra a y

ponemos 2 en nAparición, devuelve la posición de la segunda letra a del texto). Si no lo encuentra devuelve 0.

LENGTH(texto) Obtiene el tamaño del texto.

LOWER(texto) Convierte el texto a minúsculas.

LPAD(texto,longitud,pad) Alarga por la izquierda. Crea un nuevo texto a partir del dato con la longitud

especificada añadiendo por la izquierda el caracter pad especificado o espacios en blanco si no se especifica.

LTRIM(texto) Elimina los espacios a la izquierda que posea el texto.

REPLACE(texto, textoABuscar,

textoReemplazo)

Buscar el texto a buscar en un determinado texto y lo cambia por el indicado como texto de reemplazo.

RPAD(texto,longitud,pad) Alarga por la derecha. Crea un nuevo texto a partir del dato con la longitud

especificada añadiendo por la derecha el caracter pad especificado o espacios en blanco si no se especifica.

RTRIM(texto) Elimina los espacios a la derecha del texto.

SOUNDEX(texto) Retorna la representación fonética de un texto.

SUBSTR(texto,n[,m]) Obtiene los m siguientes caracteres del texto a partir de la posición n (si m no se

indica se cogen desde n hasta el final).

TRIM(caracteres

FROM texto)

Elimina del texto los caracteres indicados. Por ejemplo TRIM('h' FROM nombre)

elimina las haches de la columna nombre que estén a la izquierda y a la derecha.

TRIM(texto) Elimina los espacios en blanco a la izquierda y la derecha del texto y los espacios dobles del interior.

UPPER(texto) Convierte el texto a mayúsculas.

REGEXP_INSTR,

REGEXP_REPLACE y

REGEXP_SUBSTR

Versiones de INSTR, REPLACE y SUBSTR para expresiones regulares.

Cómo cortar y pegar cadenas. Veremos cómo aplicar las funciones LPAD, RPAD, LTRIM, RTRIM, TRIM, LENGTH, SUBSTR y INSTR para cortar y pegar cadenas de caracteres. Cada una de estas funciones realiza algo para cortar y pegar. La función más simple, LENGTH, nos dice la longitud de una cadena (cuántos caracteres tiene). Por ejemplo, LENGTH('012345') retorna el valor 6. RPAD y LPAD son muy parecidas. RPAD permite rellenar una cadena existente con un conjunto de caracteres por el lado derecho. LPAD hace lo mismo pero por el lado izquierdo.

Page 37: Java y Oracle 11g

Oracle /37

Como ejemplo, la expresión RPAD('12345', 8) retorna la cadena ' 12345', en la cual se ha rellenado con tres espacios en blanco por la derecha para que el resultado ocupe 8 caracteres. Podemos también especificar el caracter de relleno. Por ejemplo, LPAD('12345',10,'.') produce la salida '12345.....', donde se ha rellenado por la izquierda con cinco puntos. Estas dos funciones son interesantes porque aplicadas a un campo de una base de datos nos pueden garantizar que todos los valores de cada fila tengan la misma longitud. Las funciones LTRIM, RTRIM y TRIM nos permiten recortar los espacios en blanco previos y posteriores de cualquier cadena de caracteres. LTRIM elimina espacios en blanco por la izquierda, RTRIM por la derecha y TRIM por ambos lados. Por ejemplo, RTRIM(' 12 34 ') produce la salida '12 34', donde se han recortado los espacios en blanco por la derecha. También podemos especificar los caracteres de recorte. Por ejemplo, LTRIM('..0.012345..','0.') produce la salida '12345..', donde se han recortado los puntos y los ceros por la izquierda. Podemos combinar LTRIM y RTRIM para recortar por ambos lados. Por ejemplo, la expresión LTRIM( RTRIM('000012345......','.'), '0')

produce la salida '12345', donde se han recortado primero los puntos por la derecha y después los ceros por la izquierda. Si los caracteres a recortar por la izquierda y la derecha son los mismos podemos usar la función TRIM. Por ejemplo, TRIM('0' FROM '00123450') produce la salida '12345', donde se han recortados los ceros por la izquierda y por la derecha. Podemos usar la función SUBSTR para extraer un trozo de una cadena. Se indica la posición inicial del trozo dentro de la cadena y opcionalmente se indica el número de caracteres del trozo. Por ejemplo, SUBSTR('Soy

una cadena',6,4) produce la salida 'a ca'. Se pueden también usar número negativos en la función SUBSTR. Cuando se indica una posición inicial negativa, entonces es relativa al final de la cadena. Por ejemplo, SUBSTR('Soy una cadena",-4) produce la salida 'dena'. Ya que no se especifica la longitud en un tercer parámetro se retorna la subcadena hasta el final. El valor del tercer parámetro de la función SUBSTR debe ser siempre positivo o no especificado. Si se usa un número negativo la función retorna el valor NULL. La función INSTR permite búsquedas simples o sofisticadas dentro de una cadena por un conjunto de caracteres. Esta función no produce una nueva cadena como las funciones previas, sino que determina en qué posición dentro de la cadena está la subcadena buscada. INSTR tiene dos opciones, una dentro de la otra. Podemos especificar la posición de inicio para la búsqueda, de forma que se salta los caracteres anteriores a esa posición. Por ejemplo, INSTR('AABBAACCAADD','BB',1) busca la subcadena 'BB' dentro de la cadena 'AABBAACCAADD' a partir del primer caracter. El resultado de esta función será 3. También podemos especificar qué ocurrencia buscamos. Si la subcadena buscada se repite varias veces dentro de la cadena sobre la que se busca podemos indicar cuál subcadena buscamos. Por ejemplo, INSTR('AABBAACCAADD','AA',3,2) busca la segunda ocurrencia de la subcadena 'AA' a partir del tercer caracter. El resultado de esa función será 9. Si sólo especificamos la cadena y la subcadena, la función realiza la búsqueda de la primera ocurrencia desde el primer caracter. Por ejemplo, INSTR('AABBAACCAADD','CC') devuelve el resultado 7. Cómo trabajar a nivel de caracteres en una cadena. Las funciones LOWER, UPPER e INITCAP permiten modificar los caracteres de una cadena. La función LOWER toma como argumento una cadena y retorna otra con todos sus caracteres en minúsculas. Por ejemplo, LOWER('tEXto') produce la salida 'texto'. La función UPPER hace lo opuesto, convierte todos los caracteres a mayúsculas. Por ejemplo, LOWER('TEXto') produce la salida 'TEXTO'. Por su parte, la función INITCAP convierte a mayúsculas la letra inicial de cada palabra de la cadena. Por ejemplo, INITCAP('uno dos') produce la salida 'Uno Dos'. Aunque las funciones ASCII y CHR se usan raramente en consultas, son útiles para trabajar con caracteres no habituales o no imprimibles. CHR convierte un valor numérico a su representación de caracter ASCII. Por ejemplo CHR(70) equivale al caracter 'F'. La función ASCII realiza la operación inversa, convierte un caracter a su valor numérico equivalente. Por ejemplo, ASCII('FSOUG') produce el resultado 70, ya que sólo se tiene en cuenta el primer caracter. Cómo comparar cadenas por su parecido sonoro. La función SOUNDEX tiene la inusual habilidad de encontrar palabras que suenan como otras palabras, independientemente de cómo sean deletreadas. Podemos usar SOUNDEX sobre dos cadenas distintas y comparar el resultado para saber si suenan igual o no. Por ejemplo, la expresión de comparación SOUNDEX('a

Page 38: Java y Oracle 11g

Oracle /38

ver tú')=SOUNDEX('abierto') evalúa a verdadero. SOUNDEX hace determinadas asunciones sobre cómo las letras y combinaciones de letras se pronuncian normalmente en inglés, y las dos cadenas a comparar deben comenzar con la misma letra. Podemos usar SOUNDEX para encontrar erratas sobre una lista de valores y corregirlas, evitando de esta forma incoherencias entre los datos. Por ejemplo, en una tabla VENTA que contenga nombres de cliente, podemos buscar erratas en un mismo nombre de cliente con la siguiente consulta. SELECT V1.IdVenta, V1.Nombre, V2.IdVenta, V2.Nombre

FROM VENTA V1, VENTA V2

WHERE V1.Nombre > V2.Nombre AND SOUNDEX(V1.Nombre)=SOUNDEX(V2.Nombre);

Si en dos registros de VENTA aparecen los nombres 'José Pérez' y 'Jose Perez' obtendremos en el resultado los ID's de ambos registros, lo cual nos permitirá después corregir uno de los nombres. (La condición V1.Nombre >

V2.Nombre impide que obtengamos dos registros con el mismo emparejamiento.) Soporte para otros idiomas distintos del inglés. Oracle no usa sólo caracteres ingleses; puede representar datos en cualquier idioma porque implementa el soporte para lenguajes nacionales (NLS). Al usar una representación que usa más espacio que el que ocupan los caracteres ordinarios, Oracle puede representar caracteres japoneses y de otros idiomas. Las funciones NLS_SORT, NLS_INITCAP, NLS_LOWER y NLS_UPPER son versiones para NLS de las funciones ordinarias. Además de la función SUBSTR, Oracle incluye SUBSTRB (para buscar bytes en vez de caracteres), SUBSTRC (para buscar caracteres Unicode), SUBSTR2 (para buscar codepoints UCS2) y SUBSTR4 (para buscar codepoints UCS4). Soporte para expresiones regulares. Desde Oracle Database 10g, las funciones de cadena INSTR, REPLACE y SUBSTR han sido extendidas para soportar expresiones regulares con las funciones. En la siguiente sección se describen estas nuevas funciones. 2.3.2. Funciones que trabajan con expresiones regulares. Las nuevas funciones que soportan expresiones regulares son REGEXP_SUBSTR, REGEXP_INSTR, REGEXP_LIKE y REGEXP_REPLACE. Búsqueda de cadenas. Con la función REGEXP_SUBSTR podemos realizar búsquedas más sofisticadas que las realizadas con la función SUBSTR. Por ejemplo, si tenemos códigos de producto con el formato '123-ABC-456' donde cada secuencia de dígitos y letras separados por guiones tiene un significado determinado, puede ser necesario acceder sólo a la secuencia de letras de cada código de producto. Si sabemos que la secuencia de letras está siempre encapsulada entre guiones podemos usar una expresión regular para realizar la búsqueda de esta secuencia. Con la expresión REGEXP_SUBSTR('123-ABC-456', '-[^-]+-')

le decimos a Oracle que empiece buscando una subcadena que comience con un guión, a continuación la expresión [^-] le dice a Oracle que puede seguirle cualquier caracter que no sea un guión, y el signo + indica que se puede repetir una o más veces lo anterior (es decir, caracteres seguidos que no sean guiones), y por último debe incluir un guión. En este ejemplo, la función REGEXP_SUBSTR retorna '-ABC-'. Si intentásemos este mismo ejemplo usando funciones ordinarias tendríamos una expresión como la siguiente: SUBSTR( '123-ABC-456',

INSTR('123-ABC-456', '-',1,1),

INSTR('123-ABC-456', '-',1,2)-INSTR('123-ABC-456', '-',1,1))

Que evidentemente es menos concisa que la anterior. La tabla siguiente describe la simbología de las expresiones regulares:

Expresión Descripción

. Representa a cualquier caracter (excepto el salto de línea)

* Indica que lo anterior se puede repetir cero o más veces

+ Indica que lo anterior se puede repetir una o más veces

| Operador de alternancia. Se usa para indicar expresiones alternativas

() Los paréntesis se usan para agrupar expresiones, de forma que sean tratadas como una unidad

$ Representa el final del texto o de la línea. No es un caracter sino una posición

^ Representa el inicio del texto. No es un caracter sino una posición

A? Indica una A opcional. El ? es válido para hacer opcional cualquier expresión

Page 39: Java y Oracle 11g

Oracle /39

A{3} Tres A'es seguidas. Las llaves indican repetición exacta de expresiones

A{3,} Al menos tres A'es seguidas

A{3,8} Al menos tres A'es y menos de ocho A'es

[aBcD] Alguno de los caracteres a, B, c o D

[C-G] Un caracter en el rango entre C y G inclusive

[^aBcD] Cualquier carácter distinto de a, B, c o D

[.ch.] Especifica el caracter ch español

\t El caracter tabulador

\r El caracter retorno de carro

\n El caracter de nueva línea

\a El caracter beep

\e El caracter de escape

\f El caracter salto de página

\v El caracter tabulador vertical

\x00A2 El caracter de código ASCII 0042

\d Un dígito del 0 al 9

\w Cualquier carácter alfanumérico

\s Un espacio en blanco

\D Cualquier caracter que no sea un dígito

\W Cualquier caracter no alfanumérico

\S Cualquier carácter que no sea un espacio en blanco

\A Representa el inicio de la cadena. No un caracter sino una posición

\Z Representa el final de la cadena. No un carácter sino una posición

\b Marca el inicio y el final de una palabra

\B Marca la posición entre dos caracteres alfanuméricos o dos no-alfanuméricos

Además de esta simbología, Oracle soporta las siguientes clases de caracteres según las definiciones de clases de caracteres en NLS.

Clase Descripción

[:alnum:] Todos los caracteres alfanuméricos

[:alpha:] Todos los caracteres alfabéticos

[:blank:] Todos los caracteres de espacio en blanco

[:cntrl:] Todos los caracteres de control (no imprimibles)

[:digit:] Todos los dígitos numéricos

[:graph:] Todos los caracteres[:punct:], [:upper:], [:lower:], y [:digit:]

[:lower:] Todos los caracteres alfabéticos en minúsculas

[:print:] Todos los caracteres imprimibles

[:punct:] Todos los caracteres de puntuación

[:space:] Todos los caracteres de espacio (no imprimibles)

[:upper:] Todos los caracteres alfabéticos en mayúsculas

[:xdigit:] Todos los caracteres hexadecimales válidos

La función «REGEXP_SUBSTR». La función REGEXP_SUBSTR, como se ha visto, usa expresiones regulares para especificar el punto inicial y final de la subcadena retornada. La sintaxis completa de esta función es la siguiente: REGEXP_SUBSTR(source_string, pattern [, position [, occurrence [, match_parameter ]]])

Esta función retorna una cadena de tipo VARCHAR2 o CLOB con el mismo juego de caracteres que el parámetro source_string, que especifica la cadena en la que se busca. El parámetro pattern especifica la expresión regular, que puede contener un máximo de 512 bytes. El parámetro position indica dónde empieza la búsqueda dentro de source_string; el valor por defecto es 1. El parámetro ocurrence es un entero que indica que posición de la ocurrencia buscamos; su valor por defecto es 1. Se puede usar el parámetro match_parameter para cambiar el comportamiento de búsqueda de la función; los posibles valores que podemos incluir en este parámetro son:

i Se usa para indicar búsquedas no sensibles a mayúsculas y minúsculas.

c Se usa para indicar búsquedas sensibles a mayúsculas y minúsculas.

n Permite que el caracter punto (.) también case con el salto de línea.

Page 40: Java y Oracle 11g

Oracle /40

m Trata la cadena de origen como una cadena de varias líneas. Oracle tratará ^ y $ como el inicio y final, respectivamente, de cada línea.

Si se especifican valores contradictorios en el parámetro match_parameter, Oracle usa el último valor. El siguiente ejemplo realiza una búsqueda insensible a mayúsculas y minúsculas: REGEXP_SUBSTR('MI LIBRO DE CONTABILIDAD: Débitos, Créditos y Facturas 1940','mi',1,1, 'i')

Siendo el resultado 'MI'. Si ahora cambiamos el modificador de búsqueda: REGEXP_SUBSTR('MI LIBRO DE CONTABILIDAD: Débitos, Créditos y Facturas 1940', 'mi', 1, 1, 'c')

El resultado será el valor NULL. Por defecto, las búsquedas son sensibles a mayúsculas y minúsculas. En el siguiente ejemplo, se busca el segundo dígito: REGEXP_SUBSTR('MI LIBRO DE CONTABILIDAD: Débitos, Créditos y Facturas 1940', '[[:digit:]]', 1, 2)

Siendo en este caso el resultado 9. La función «REGEXP_INSTR». La función REGEXP_INSTR usa expresiones regulares para retornar el punto inicial o final de una búsqueda. La sintaxis de esta función es como sigue: REGEXP_INSTR (source_string, pattern[, position [, ocurrence[, return_option[, match_parameter ]]]])

Esta función retorna un entero que indica la posición inicial o final de la subcadena especificada por el patrón, o cero si no encuentra ninguna subcadena. Esta función es similar a la función REGEXP_SUBSTR, y añade una nueva funcionalidad. El parámetro return_option permite especificar el significado del valor devuelto por la función:

• Si return_option es 0, la función retorna la posición del primer caracter de la ocurrencia. Éste es el valor por defecto. • Si return_option es 1, la función retorna la posición del caracter que sigue a la ocurrencia.

Por ejemplo, la siguiente expresión retorna la posición del primer dígito dentro de una cadena: REGEXP_INSTR('MI LIBRO DE CONTABILIDAD: Débitos, Créditos y Facturas 1940','[[:digit:]]')

Siendo el resultado 56. Si queremos la posición siguiente a la de este dígito, la expresión será: REGEXP_INSTR('MI LIBRO DE CONTABILIDAD: Débitos, Créditos y Facturas 1940', '[[:digit:]]', 1, 1, 1)

La función «REGEXP_LIKE». La función REGEXP_LIKE permite evaluar una cadena para ver si coincide con un patrón especificado con una expresión regular. Esta función retorna verdadero o falso. Por ejemplo, la expresión establece si una cadena comienza por '111': REGEXP_LIKE('111-222-333', '111+')

En este caso la función se evalúa a verdadero. El formato de esta función es: REGEXP_LIKE(source_string, pattern, [match_parameter ])

Donde el significado de los parámetros es similar al de las funciones vistas previamente. Las funciones «REPLACE» y «REGEXP_REPLACE». La función REPLACE reemplaza una subcadena dentro de una cadena por otra. Por ejemplo, podemos reemplazar cada ocurrencia de una letra con un número. El formato para REPLACE es: REPLACE (source_string, search_string [, replace_string])

Si no especificamos un valor para el parámetro replace_string, la subcadena search_string es quitada de la cadena de origen. La cadena de origen puede ser de cualquiera de los tipos de caracteres (CHAR, VARCHAR2, NCHAR, NVARCHAR2, CLOB o NCLOB). Por ejemplo: REPLACE('GEORGE', 'GE', 'EG')

Produce el resultado 'EGOREG', y: REPLACE('GEORGE', 'GE', NULL)

Produce el resultado 'OR'. La función REGEXP_REPLACE extiende las capacidades de la función REPLACE de varios modos. Soporta el uso de expresiones regulares en el parámetro search_string, y además incluye los parámetros de las funciones descritas en este capítulo: position, ocurrence y match_parameter. La sintaxis para la función REGEXP_REPLACE es la siguiente: REGEXP_REPLACE(source_string, pattern[, replace_string[, position[, ocurrence[, match_parameter ]]]])

En el siguiente ejemplo se pone entre paréntesis cada terna de un número telefónico: REGEXP_REPLACE ('111222333', '([[:digit:]]{3})([[:digit:]]{3})([[:digit:]]{3})', '(\1) (\2) (\3)')

Page 41: Java y Oracle 11g

Oracle /41

El resultado de este ejemplo es '(111) (222) (333)'. 2.3.3. Funciones numéricas.

ABS(n) Devuelve el valor absoluto del argumento.

ACOS(n) Devuelve, en radianes, el arcocoseno del argumento.

ASIN(n) Devuelve, en radianes, el arcoseno del argumento

ATAN(n) Devuelve, en radianes, el arcotangente del argumento

ATAN2(n, m) Devuelve, en radianes, la arcotangente de dos argumentos.

BITAND (n, m) Realiza una operación AND a nivel de bits entre los argumentos y devuelve un entero con el resultado.

CEIL(n) Devuelve el valor entero más pequeño mayor o igual que el argumento.

COALESCE(n, m, …) Retorna el primer valor no NULL de la lista de argumentos.

COS(n) Devuelve el coseno del argumento (el cual tiene que estar en radianes).

COSH(n) Devuelve el coseno hiperbólico del argumento.

EXP(n) Devuelve el valor de e elevado al argumento n.

FLOOR(n) Devuelve el valor entero más grande menor o igual que el argumento

GREATEST(n, m, …) Retorna el valor más grande de la lista de argumentos, o NULL si uno de ellos es nulo.

LEAST(n, m, …) Retorna el valor más pequeño de la lista de argumentos, o NULL si uno de ellos es nulo.

LN(n) Devuelve el logaritmo neperiano del argumento.

LOG(n) Devuelve el logaritmo en base 10 del argumento n.

MOD(n1,n2) Devuelve el resto de la división entera de los argumentos.

NANVL(n,m) Para números BINARY_FLOAT y BINARY_DOUBLE, esta función retorna el segundo argumento si el primero no es un número, sino retorna el primer argumento

NVL(n, m) Retorna el segundo argumento si el primero es NULL, sino retorna el primer argumento.

NVL2(n,m,p) Retorna el tercer argumento si el primero es NULL, sino retorna el segundo argumento.

POWER(valor, exponente) Retorna el resultado de elevar un valor al exponente indicado.

REMAINDER(n, m) Retorna el resto de dividir dos números.

ROUND(n, decimales) Redondea el número al siguiente número con el número de decimales indicado más cercano.

SIGN(n) Devuelve 1 si el argumento es positivo, cero si vale cero y -1 si es negativo

SIN(n) Devuelve el seno del argumento (el cual tiene que estar en radianes).

SINH(n) Devuelve el seno hiperbólico del argumento.

SQRT(n) Devuelve la raíz cuadrada del argumento.

TAN(n) Devuelve la tangente del argumento (el cual tiene que estar en radianes).

TANH(n) Devuelve la tangente hiperbólica del argumento.

TRUNC(n, decimales) Los decimales del número se cortan para devolver sólo el número de decimales indicado.

VSIZE(n) Almacena el tamaño del argumento en Oracle.

Las funciones de Oracle tratan con tres clases de números: valores simples, grupos de valores y las listas de valores. Como con las funciones de cadenas, algunas funciones numéricas cambian los valores que les son aplicados, mientras que otras reportan información acerca de los valores. Un valor simple es un número procedente de:

• Una expresión literal, como 544.3702. • Una variable de SQL*Plus o PL/SQL. • Una columna de una fila de base de datos.

Las funciones de valores simples normalmente cambian estos valores tras algún cálculo. Un grupo de valores son todos los números de una columna de una serie de filas. Las funciones sobre grupos de valores realizan alguna operación sobre todos los números del grupo, como el promedio de los precios de venta, pero no sobre los valores individuales. (Las funciones numéricas sobre grupos de valores se denominan «funciones de agregado» y se estudiarán en el capítulo correspondiente.) Una lista de valores es una serie de número que pueden proceder de:

Page 42: Java y Oracle 11g

Oracle /42

• Una lista de expresiones literales separadas por comas, como 1, 7.3, 22. • Variables de SQL*Plus o PL/SQL. • Columnas de una base de datos.

Las funciones de listas de valores seleccionan uno de los valores de la lista según algún criterio. Funciones de valor simple. La mayor parte de funciones de valor simple son bastante intuitivas. La función ABS retorna el valor independientemente del signo. Por ejemplo ABS(-23) devuelve el valor 23. La función CEIL busca el valor entero más pequeño que sea más grande o igual que uno dado. Hay que prestar atención especial a su efecto sobre números negativos. La siguiente tabla muestra algunos ejemplos:

CEIL(2) = 2

CEIL(1.3) = 2

CEIL(-2) = -2

CEIL(-2.3) = -2

La función FLOOR es la opuesta a CEIL. La siguiente tabla muestra algunos ejemplos: FLOOR(2) = 2

FLOOR(1.3) = 1

FLOOR(-2) = -2

FLOOR(-2.3) = -3

La función MOD permite dividir un número entre otro y retorna el resto de la división entera. Por ejemplo MOD(23,6) divide 23 entre 6 y devuelve el resto 5. El valor de MOD es cero si el divisor es cero o negativo. La siguiente tabla muestra algunos ejemplos:

MOD(100,10) = 0

MOD(22,23) = 22

MOD(10,3) = 1

MOD(-30.23,7) = -2.23

MOD(4.1,.3) = .2

MOD(44, 1) = 0

Esta función puede ser interesante para saber si un número es par o impar. Simplemente hay que tener en cuenta que si dividimos por 2, los números pares tienen resto 0 y los impares resto 1. También es útil para normalizar cualquier número dentro de un rango. Por ejemplo, cualquier número dividido entre 10 dará como resto un valor entre 0 (inclusive) y 10 (exclusive). La función TRUNC trunca, o recorta, los dígitos de precisión de un número; mientras que la función ROUND redondea un número según el número de dígitos de precisión especificado. El formato de ambas funciones es el siguiente: ROUND(valor, precisión)

TRUNC(valor, precisión)

Si no se especifica la precisión se toma por defecto precisión cero. La siguiente tabla muestra ejemplos de ambas funciones:

ROUND(11, 2) = 11 TRUNC(11, 2) = 11

ROUND(-22, 2) = -22 TRUNC(-22, 2) = -22

ROUND(33.33, 2) = 33.33 TRUNC(33.33, 2) = 33.33

ROUND(55.5, 2) = 55.5 TRUNC(55.5, 2) = 55.5

ROUND(66.666, 2) = 66.67 TRUNC(66.666, 2) = 66.66

En todos estos ejemplos se redondea y trunca en base a dos decimales. Hay que tener en cuenta que el valor decimal de .5 es redondeado siempre hacia arriba, de forma que ROUND(55.5) da como resultado 56. También podemos trabajar con precisiones negativas, de forma que se mueve el punto decimal hacia la izquierda. Por ejemplo:

ROUND(11, -1) = 10 TRUNC(11, -1) = 10

ROUND(-22, -1) = -20 TRUNC(-22, -1) = -20

ROUND(33.33, -1) = 30 TRUNC(33.33, -1) = 30

ROUND(55.5, -1) = 60 TRUNC(55.5, -1) = 50

ROUND(66.666, -1) = 70 TRUNC(66.666, -1) = 60

En estos ejemplos al usar precisión -1 se redondea al nivel de decenas, si se usase precisión -2 se redondearía al nivel de centenas, y así sucesivamente. El redondeo con un número negativo puede ser útil para generar informes económicos, donde las sumas de poblaciones o moneda tienen que ser redondeadas hasta los millones, billones o trillones.

Page 43: Java y Oracle 11g

Oracle /43

La función SIGN es la otra cara del valor absoluto. Mientras que ABS nos da la magnitud de un valor, pero no su signo, SIGN nos da el signo de un valor, pero no su magnitud. Por ejemplo, SIGN(146) nos da 1, mientras que SIGN(-30) nos da -1. Además, SIGN(0) da 0. La función SIGN es normalmente usada en conjunción con la función DECODE. Las funciones trigonométricas seno, coseno y tangente son funciones científicas y técnicas que no se usan mucho en los negocios. SIN, COS y TAN obtienen los valores correspondientes para un ángulo expresado en radianes (grados multiplicados por pi dividido por 180). Funciones de lista. Las funciones de lista trabajan sobre un grupo de valores, realizando una selección simple sobre ellos. Así, las funciones GREATEST y LEAST pueden ser usadas sobre varios valores (numéricos o de caracteres) de la siguiente forma: GREATEST('Bob', 'Jorge', 'Andrés, 'Isaías') = Isaías

LEAST('Bob', 'Jorge', 'Andrés, 'Isaías') = Andrés

GREATEST obtiene el mayor valor de la lista de valores, mientras que LEAST obtiene el menor valor de la lista de valores. Para el ejemplo previo se utiliza la ordenación alfabética de caracteres para establecer qué nombre es mayor y menor. Podemos usar la función COALESCE para evaluar varios valores que pueden incluir nulos. COALESCE retorna el primer valor de una lista que no sea nulo, y si todos son nulos retorna el valor NULL. Por ejemplo: COALESCE( NULL, 4, NULL, 7) = 4

2.3.4. Funciones que trabajan con nulos.

NVL(valor, sustituto) Si el valor es NULL, devuelve el valor sustituto; de otro modo, devuelve valor.

NVL2(valor,sustituto1,sustituto2) Variante de la anterior, devuelve el valor sustituto1 si valor no es nulo. Si valor

es nulo devuelve el sustituto2.

NANVL(valor, sustituto) Análoga a NVL para los tipos BINARY_FLOAT y BINARY_DOUBLE.

Los valores nulos no pueden ser usados para realizar cálculos. Cuando se realiza una operación entre dos valores, uno de los cuales es NULL, el resultado siempre será NULL. Debemos tener en cuenta que el valor NULL no es igual que el valor cero; más bien el valor NULL quiere decir un valor no conocido o irrelevante. Sin embargo, habrá consultas donde debamos realizar cálculos sobre campos que admiten el valor nulo. En estos casos es importante saber si un valor determinado es nulo y, en ese caso, poder sustituirlo por un valor determinado. Por ejemplo, si tenemos una tabla de ventas, que tiene una columna "cantidad" que admite valores nulos, podemos querer interpretar los valores nulos como el valor cero. Esto se puede hacer con la función NVL aplicándola de la siguiente manera: SELECT idVenta, NVL(cantidad, 0) FROM Ventas;

Esta consulta permite mostrar los id's de venta y las cantidades en cada venta. Si el valor de cantidad está a nulo se mostrará el valor cero. El formato de esta función es el siguiente: NVL(valor, sustituto)

Si el argumento valor es NULL, esta función retorna el argumento sustituto, sino retorna el propio valor. NVL funciona con cualquier tipo de datos, pero tanto valor como sustituto deben ser del mismo tipo. NVL es realmente útil en casos donde el dato es desconocido pero no irrelevante. En los casos donde debamos evaluar si el dato es nulo pero su valor concreto es irrelevante podemos usar la función NVL2. Su formato es el siguiente: NVL2 ( expr1 , expr2 , expr3 )

En NVL2, expr1 nunca será retornada; sino que, o bien expr2oexpr3serán retornados. Si expr1no es NULL, NVL2 retorna expr2. Siexpr1es NULL, NVL2 retorna expr3. El primer argumento puede ser de cualquier tipo. Los otros dos argumentos deben ser del mismo tipo excepto LONG. Desde Oracle Database 10g, podemos usar la función análoga NANVL para los tipos de datos BINARY_FLOAT

y BINARY_DOUBLE. NANVL toma dos argumentos, y retorna el segundo si el primero no es un número. 2.3.5. Funciones de fecha y hora.

ADD_MONTHS(fecha,n) Añade a la fecha el número de meses indicado por n.

CURRENT_DATE Retorna la fecha y hora actual en la zona horaria de la sesión.

CURRENT_TIMESTAMP Retorna la fecha y hora actual con la información de zona horaria activa.

DBTIMEZONE Retorna la zona horaria actual en formato UTC.

EXTRACT(valor FROM fecha) Extrae un valor de una fecha concreta. El valor puede ser day (día), month

(mes), year (año), etc.

Page 44: Java y Oracle 11g

Oracle /44

FROM_TZ(timestamp) Convierte un valor timestamp a otro timestamp con un valor de zona horaria.

GREATEST(fecha1, ...) Devuelve la fecha más moderna de la lista.

LAST_DAY(fecha) Obtiene el último día del mes al que pertenece la fecha. Devuelve un valor

DATE.

LEAST(fecha1, fecha2,...) Devuelve la fecha más antigua de la lista.

LOCALTIMESTAMP Retorna el timestamp local en la zona horaria activa, pero sin mostrar la información de zona horaria.

MONTHS_BETWEEN(fecha1,fecha2) Obtiene la diferencia en meses entre las dos fechas (puede ser decimal).

NEW_TIME(fecha,esta,otra) Obtiene la fecha (y hora) en esta zona horaria. El valor del argumento esta puede ser reemplazado por una abreviatura de tres letras para la zona

horaria actual. El valor del argumento otra puede ser reemplazado por una abreviatura de otra zona horaria para la cual queremos saber la fecha y hora. Los valores de zonas horarias son:

AST/ADT Atlantic standard/daylight time

BST/BDT Bering standard/daylight time

CST/CDT Central standard/daylight time

EST/EDT Eastern standard/daylight time

GMT Greenwich mean time

HST/HDT Alaska-Hawaii standard/daylight time

MST/MDT Mountain standard/daylight time

NST Newfoundland standard time

PST/PDT Pacific standard/daylight time

YST/YDT Yukon standard/daylight time

NEXT_DAY(fecha,día) Indica cual es el día que corresponde a añadir a la fecha el día indicado. El

día puede ser el texto 'Lunes', 'Martes', 'Miércoles',... (si la configuración está en español) o el número de día de la semana (1=lunes, 2=martes,...)

NUMTODSINTERVAL(valor,formato) Convierte el argumento valor a un literal de intervalo de tiempo según las

unidades especificadas en el segundo argumento. El argumento formato

puede ser 'DAY', 'HOUR', 'MINUTE' o 'SECOND'.

NUMTOYMINTERVAL(valor,formato) Convierte el argumento valor a un literal de intervalo de tiempo según las

unidades especificadas en el segundo argumento. El argumento formato

puede ser 'YEAR' o 'MONTH'.

ROUND(fecha [,'formato']) Redondea la fecha al valor de aplicar el formato a la fecha. El formato

puede ser 'YEAR', 'MONTH', 'HH24' o 'DAY'.

SESSIONTIMEZONE Retorna el valor de la zona horaria de la sesión actual.

SYS_EXTRACT_UTC Extrae de la fecha actual al valor UTC (Coordinated Universal Time).

SYSDATE Obtiene la fecha y hora actuales.

SYSTIMESTAMP Obtiene la fecha y hora actuales en formato TIMESTAMP.

TO_CHAR(fecha,formato) Da formato a una fecha según el patrón especificado en el segundo argumento.

TO_DATE(texto,formato) Interprete un texto como una fecha según el patrón especificado en el segundo argumento.

TO_DSINTERVAL(texto) Convierte un texto de tipo CHAR, VARCHAR2, NCHAR o NVARCHAR2 a un tipo de intervalo entre días y segundos.

TO_TIMESTAMP(texto) Convierte un texto de tipo CHAR, VARCHAR2, NCHAR o NVARCHAR2 a un

valor de tipo TIMESTAMP.

TO_TIMESTAMP_TZ(texto) Convierte un texto de tipo CHAR, VARCHAR2, NCHAR o NVARCHAR2 a un

tipo TIMESTAMP con zona horaria.

TO_YMINTERVAL(texto) Convierte un texto de tipo CHAR, VARCHAR2, NCHAR o NVARCHAR2 a un tipo de intervalo entre años y meses.

TRUNC(fecha, [formato]) Trunca la fecha al valor de aplicar el formato a la fecha. El formato puede

ser 'YEAR', 'MONTH', 'HH24' o 'DAY'.

TZ_OFFSET(texto) Retorna la diferencia de zona horaria correspondiente al valor del argumento.

Una de las bazas de Oracle es su capacidad de almacenar y calcular fechas, y el número de segundos, minutos, horas, días, meses y años entre fechas. Además de las funciones de fecha básicas, Oracle soporta muchas

Page 45: Java y Oracle 11g

Oracle /45

funciones de conversión y la habilidad de dar formato a fechas en cualquier manera concebible. Aritmética de fechas. El tipo de dato para fechas en Oracle es DATE. Un tipo DATE es almacenado en un formato interno especial que incluye el día, mes, año, horas, minutos y segundos de la fecha. También se puede usar el tipo TIMESTAMP para almacenar fechas hasta fracciones de segundo. SQL*Plus y SQL reconocen columnas que tienen el tipo DATE e interpretan las instrucciones donde se utilizan operaciones aritméticas con fechas como aritmética de fechas y no como aritmética matemática. Por ejemplo, sumando el valor 1 a una fecha se obtiene otra fecha con el día siguiente; y si restamos dos fechas, obtenemos la diferencia de días entre ellas. Sin embargo, dado que las fechas de Oracle incluyen horas, minutos y segundos, la aritmética de fechas puede ser difícil de interpretar; por ejemplo, podríamos decir que ¡la diferencia entre hoy y mañana es de 0,516 días! Las funciones «SYSDATE», «CURRENT_DATE» y «SYSTIMESTAMP». Oracle consulta el sistema operativo del ordenador para obtener la fecha y hora actuales. Para ello proporciona una función especial llamada SYSDATE. Podemos ver SYSDATE como una función que siempre retorna la fecha y hora actual. Por ejemplo, la siguiente consulta: SELECT SYSDATE FROM DUAL;

Retorna una fila con una columna con el valor de la fecha (día, mes y año) actual del ordenador. Una segunda función, CURRENT_DATE, recupera la fecha del sistema en la zona horaria de la sesión (podemos asignar la zona horaria dentro de nuestra sesión local, la cual puede diferir de la zona horaria de la base de datos). Otra función, SYSTIMESTAMP, recupera la fecha del sistema en un formato más completo que incluye la hora e información de zona horaria. Cómo añadir y sustraer meses a una fecha. Se puede utilizar la función ADD_MONTHS para añadir un número de meses a una fecha dada. Por ejemplo ADD_MONTHS('1/2/2011', 6) da como resultado la fecha '1/8/2011'. Esta función siempre se mantendrá dentro del margen de fechas válidas. Por ejemplo, ADD_MONTHS('31/1/2011', 1) da como resultado la fecha '28/2/2011'. Añadiendo valores negativos a esta función conseguiremos restar meses a una fecha. Por ejemplo ADD_MONTHS('31/1/2011', -4) da como resultado '30/09/10'. Las funciones «GREATEST» y «LEAST». Las funciones GREATEST y LEAST trabajan sobre expresiones de tipo fecha igual que sobre otros tipos de datos, excepto si se aplican sobre una lista de valores literales. Por ejemplo GREATEST('2/1/2011','2/2/2000') da como resultado el valor '2/2/2000', que es claramente una fecha anterior a '2/1/2011'. Esto es así porque estas funciones interpretan los literales de fechas como strings. Para que estas funciones trabajen apropiadamente sobre valores literales de fecha debemos aplicar la función TO_DATE sobre los literales. Por ejemplo GREATEST(TO_DATE('2/1/2011'),TO_DATE('2/2/2000')) da ahora como resultado el valor '2/1/2011'. La función «NEXT_DAY». NEXT_DAY computa la fecha correspondiente al día de la semana indicado (esto es, lunes, martes, miércoles, jueves, viernes, sábado o domingo) después de una fecha dada. Por ejemplo, en una tabla de citas, la cita puede ser siempre el primer viernes después de una fecha inicial dada. En este caso podemos almacenar en la tabla la fecha en que se concierta la fecha y consultar la tabla para obtener la fecha concreta de la cita, de la siguiente manera: SELECT NEXT_DAY(FechaInicial, 'Viernes') FROM Citas;

Si el valor de FechaInicial es'1/11/2011' el resultado será '04/11/2011', que se corresponde con el viernes siguiente. Sin embargo si aplicamos NEXT_DAY('4/11/2011','Viernes') obtendremos como resultado el '11/11/2011', es decir, el siguiente viernes y no el actual. Si queremos evitar esto y mostrar el propio día si coincide con el viernes, nos basta con restar un día a la fecha, obteniendo así la consulta: SELECT NEXT_DAY(FechaInicial - 1,'Viernes') FROM Citas;

La función «LAST_DAY». LAST_DAY computa la fecha del último día del mes. Podemos aplicar esta función sobre una fecha dada para obtener la fecha del último día del mismo mes. Por ejemplo, LAST_DAY('4/2/2011') retorna el valor '28/02/11'. Meses entre dos fechas. La función MONTHS_BETWEEN permite computar el número de meses de diferencia entre dos fechas. Podemos utilizar esta función, por ejemplo, para calcular la edad actual (en años) de una persona. La siguiente

Page 46: Java y Oracle 11g

Oracle /46

consulta hacer esto sobre una persona nacida el '23/4/1987': SELECT FLOOR( MONTHS_BETWEEN(SYSDATE, '23/4/1987') / 12 ) FROM DUAL;

En este caso de aplica la función FLOOR para eliminar valores decimales del resultado. Redondear y truncar cálculos sobre fechas. Si asumimos que SYSDATE retorna el valor '23/3/2011' podemos realizar una resta de fechas como: SELECT TO_DATE('28/3/2011') - SYSDATE FROM DUAL;

Siendo el resultado 4,4135 en vez de 5 días. La razón de este número con decimales es porque SYSDATE incluye horas, minutos y segundos, y Oracle los utiliza para realizar la sustracción. Para simplificar alguna de las dificultades que podemos encontrarnos usando fracciones de días, Oracle hace algunas asunciones acerca de las fechas:

• Un literal de fecha, como '28/03/2011', asume por defecto la hora 12:00:00 A.M. • Una fecha introducida a través de SQL*Plus, a menos que se especifique, asume por defecto la hora 12:00:00 A.M. • SYSDATE siempre incluye la fecha y la hora, a menos que lo redondeemos explícitamente. Usar la función ROUND sobre una fecha la redondea a las 12 A.M. del mismo día si es antes del mediodía, y a las 12 A.M. del día siguiente si es después del mediodía. La función TRUNC actúa de forma similar, excepto que asigna la hora a las 12 A.M. del mismo día e incluyendo un segundo después de medianoche.

Para obtener el número redondeado de días debemos usar la siguiente consulta: SELECT TO_DATE('28/3/2011') – ROUND(SYSDATE) FROM DUAL;

Si la fecha actual es después del mediodía la diferencia será de 4 días. Formatos para «TO_DATE» y «TO_CHAR». TO_DATE y TO_CHAR son parecidos en la medida en que ambos tienen poderosas capacidades de formato. TO_DATE convierte una cadena o un número en una fecha, mientras que TO_CHAR convierte una fecha en una cadena de caracteres. Los formatos de ambas funciones son los siguientes: TO_CHAR( fecha [, formato [, parametrosNLS]])

TO_DATE( cadena [, formato [, parametrosNLS]])

El argumento fecha debe ser un valor literal (con un formato de fecha válido) o una columna de tipo DATE. El argumento cadena debe ser un literal de texto, un literal numérico o una columna de base de datos que contenga un string o un número. En cada caso, el formato de la cadena debe corresponderse con el descrito en el argumento formato. Si se omite el argumento de formato sólo se admiten los formatos por defecto: 'día/mes/año' o 'día-mes-año'. El argumento formato es una colección de opciones que podemos combinar para indicar un patrón de fecha. El argumento parametrosNLS es un string que asigna la opción NLS_DATE_LANGUAGE a un idioma específico, en vez de usar el idioma de la sesión actual. Normalmente no es necesario usar este argumento opcional. Oracle retorna nombres de días y meses en el idioma asignado por la sesión. Como ejemplo, la expresión TO_CHAR(SYSDATE, 'DD/MM/YYYY') retorna la fecha actual en un formato como '25/06/2012'. Mientras que la expresión TO_CHAR(SYSDATE, 'DD-MON-YYYY') retorna la fecha actual en un formato como '25-JUN-2012'. En el argumento de formato podemos usar las siguientes opciones para especificar un patrón de fecha:

Formato Significado

/ , - : . ; Signos de puntuación que serán incorporados en TO_CHAR e ignorados en TO_DATE.

A.D. ó AD Indicador de AD.

A.M. ó AM Muestra A.M. o P.M., dependiendo del momento del día.

B.C. ó BC Indicador de BC.

CC Siglo (por ejemplo, 21 para 2004).

SCC Siglo, para fechas BC prefijadas con guión.

D Número del día de la semana: de 1 a 7.

DAY El día con nombre completo.

DD Número del día en el mes: de 1 a 31.

DDD Número del día en el año (desde el 1 de enero): 1 a 366.

DL La fecha en un formato largo, como por ejemplo 'Martes 14 de Junio de 2011'.

DS La fecha en un formato corto, como por ejemplo '14/06/2011'.

DY Las tres letras de abreviatura del día. Por ejemplo, VIE para el viernes.

E Abreviatura del nombre de era (para calendarios Japanese Imperial, ROC Official y Thai Buddha).

Page 47: Java y Oracle 11g

Oracle /47

EE Versión a nombre completo del formato E.

FF [1..9] Fracciones de segundo. El número que sigue a FF especifica el número de dígitos en la parte de fracciones de segundo.

FM Suprime espacios en blanco de rastreo y administración. Sin FM, todos los meses y días son mostrados con el mismo ancho.

FX Especifica que case el formato exacto para el argumento de texto y de formato.

HH ó HH12 Hora del día: 1 a 12.

HH24 Hora del día: 0 a 23.

I Un dígito de año del estándar ISO.

IW Semanas en el año del estándar ISO: 1 a 53.

IY Dos dígitos de año del estándar ISO.

IYY Tres dígitos de año del estándar ISO.

IYYY Cuatro dígitos de año del estándar ISO.

J Valor Juliano (días desde el 31 de Diciembre de 4712 B.C.).

MI Minutos de la hora: 0 a 59.

MM Número del mes: 1 a 12.

MON Abreviatura de tres letras del mes (por ejemplo, AGO para agosto).

MONTH El nombre completo del mes.

P.M. Análogo a A.M.

Q Cuatrimestre del año: 1 a 4.

RM Número romano del mes.

RR Últimos dos dígitos del año relativo a la fecha actual.

RRRR Año redondeado, aceptando dos o cuatro dígitos.

SS Segundos del minuto: 0 a 59.

SSSSS Segundos desde medianoche: 0 a 86399.

TS Formato de hora corta, para usar con DL o DS.

TZD Información de tiempo de ahorros de luz del día.

TZH Hora de la zona horaria.

TZM Minuto de la zona horaria.

TZR Región de la zona horaria.

W Número de semanas en un mes (desde 1 para la primera semana del mes).

WW Número de semanas en un año (desde 1 para la primera semana del año).

X Carácter de raíz local.

YEAR ó

SYEAR

El año hablado en inglés. Por ejemplo, una fecha con el año 2011 se corresponde con 'twenty

eleven'.

YYYY ó

SYYYY

El año con cuatro dígitos.

Y,YYY Año con separador de miles.

Y Último dígito del año.

YY Dos últimos dígitos del año.

YYY Tres últimos dígitos del año.

Los formatos que sólo trabajan con TO_CHAR, pero no con TO_DATE, son los siguientes:

Formato Significado

TH Sufijo para un número. Por ejemplo, ddTH o DDTH produce 24th o 24TH, respectivamente.

SP Sufijo para un número que fuerza la versión hablada del número en inglés. Por ejemplo, MMSP

aplicado sobre junio muestra 'six' (seis en inglés).

SPTH Combinación de los sufijos SP y TH.

THSP Combinación de los sufijos TH y SP.

Además, podemos insertar texto literal en el formato encapsulándolo entre comillas dobles. Por ejemplo, TO_CHAR(SYSDATE,'("YYYY") YYYY') puede producir '(YYYY) 2011'. La función «NEW_TIME» para cambiar zonas horarias. La función NEW_TIME nos da la fecha y hora para otra zona horaria a la actual. El formato de esta función es el siguiente: NEW_TIME(fecha, zonaHorariaActaul, otraZonaHoraria)

El primer argumento es una fecha (y hora) de la zona horaria especificada en el segundo argumento. La fecha

Page 48: Java y Oracle 11g

Oracle /48

será retornada para la zona horaria especificada en el tercer argumento. Los argumentos de zonas horarias usan una abreviatura de tres letras. Esta función puede ser útil para comparar fechas de distintas zonas horarias. Por ejemplo, para comparar un campo fecha de una tabla Amigo de la zona de Europa del Este con la zona de Hawái, podemos usar la siguiente consulta: SELECT fecha, NEW_TIME(fecha, 'EST', 'HST') FROM Amigo;

Cálculos con «TO_DATE». TO_DATE sigue las mismas convenciones de formato que TO_CHAR, con algunas restricciones. El propósito de TO_DATE es convertir un string, como 'MAY 20, 1949', en un valor de tipo DATE. Esto permite que las fechas sean usadas para cálculos. Si queremos convertir un valor como '22-MAR-04' tenemos que usar la expresión TO_DATE('22-MAR-04','DD-MON-YY'). Si omitimos el argumento de formato, el primer argumento tiene que expresar una fecha en uno de los formatos estándar para fechas (separando la fecha con guiones o barras). Supongamos que obtenemos el día de mes en un campo de base de datos expresado como un número y queremos mostrarlo en su forma de nombre completo. La función TO_CHAR permite hacer esto sobre una fecha completa, pero no sobre un número. Por tanto, podemos convertir previamente el número de mes a fecha con la función TO_CHAR. La consulta sería como sigue: SELECT TO_CHAR( TO_DATE( numeroMes , 'MM') , 'MONTH' ) FROM Tabla;

En este ejemplo, la función TO_DATE convierte previamente un número en una fecha que incluye el día 1, el mes que casa con el número y al año actual. También podemos compactar una fecha en un único número del estilo 23022011. Podemos convertir este número en una fecha mediante la expresión TO_DATE(23022011,'DDMMYYYY'), que devuelva la fecha con valor '23/2/2011'. Como regla general, la función TO_DATE fallará si no seguimos las siguientes normas:

• No se permiten literales en el string a convertir. Por ejemplo 'La fecha es 12/1/2000' no es válido. • Los días no pueden ser expresados en su forma hablada. Siempre deben ser números. • Se permiten los signos de puntuación. • El formato fm no es necesario. Si se usa, se ignora. • Si se usa Month, el mes en el string debe estar en su forma hablada. Si se usa Mon, el mes debe expresarse con una abreviatura de tres letras. Las mayúsculas y minúsculas serán ignoradas.

Cómo tratar con varios siglos. Si nuestra aplicación usa sólo dos dígitos para los años, podemos encontrarnos con problemas relacionados con el año 2000. Si especificamos '98' para el año '1998', e insertamos la fecha en una base de datos después del año 2000, podemos tener problemas con el valor de siglo asignado a la fecha, puesto que se le asignará el siglo 21. Es por ello importante especificar siempre los años con cuatro dígitos cuando insertemos datos. Usando la función «EXTRACT». Podemos usar la función EXTRACT en lugar de TO_CHAR para seleccionar parte de una fecha. La sintaxis de esta función es la siguiente: EXTRACT( parte FROM fecha )

Donde parte puede ser uno de los siguiente valores: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TIMEZONE_HOUR, TIMEZONE_MINUTE, TIMEZONE_REGION o TIMEZONE_ABBR. Por ejemplo, si queremos obtener el año actual podemos usar la expresión EXTRACT(YEAR FROM SYSDATE). Usando el tipo de dato «TIMESTAMP». El tipo de dato DATE almacena la fecha y hora hasta segundos, el tipo TIMESTAMP almacena la fecha hasta billones de segundo. Por defecto, la precisión para fracciones de segundo en un TIMESTAMP es de 6, pero puede llegarse hasta 9 decimales. Al definir una columna de tipo TIMESTAMP podemos establecer esta precisión de la siguiente forma: CREATE TABLE X1 (TSCOLTIMESTAMP(5));

Si queremos que los valores insertados especifiquen la zona horaria podemos crear la columna con la opción WITH TIME ZONE de la siguiente forma: CREATE TABLE X1 (TSCOLTIMESTAMP(5) WITH TIME ZONE);

Oracle también soporta el tipo de dato TIMESTAMP(precisión) WITH LOCAL TIMEZONE, que permite normalizar los valores a la zona horaria donde es almacenada la base de datos. También podemos usar la función SYSTIMESTAMP para obtener la fecha y horas actuales como un tipo

Page 49: Java y Oracle 11g

Oracle /49

TIMESTAMP. Además del tipo TIMESTAMP, Oracle soporta dos tipos de datos para intervalos: INTERVALYEAR(precisión_del_año) TO MONTH

INTERVAL DAY (precisión_del_día) TO SECOND (precisión_fracción_segundos)

El tipo INTERVAL YEAR TO MONTH almacena un periodo de tiempo en años y meses, donde la precisión es el número de dígitos del año (siendo por defecto 2). El tipo INTERVAL DAY TO SECOND almacena un periodo de tiempo de días, horas, minutos y segundos; la precisión para el día y segundos acepta valores entre 0 a 9. Los tipos INTERVAL son normalmente usados en análisis estadísticos de datos. 2.3.6. Funciones que retornan valores del sistema.

USER Pseudo-columna de SQL*Plus que devuelve un valor VARCHAR2 que contiene el nombre de usuario actual de Oracle.

UID

Esta función retorna el ID de la se sesión. El ID de usuario es un identificador único para cada usuario en una base de datos y puede seleccionarse de la vista

DBA_USERS.

USERENV('atributo') Devuelve un valor VARCHAR2 que contiene información acerca de la sesión actual, según la opción seleccionada. Las opciones son:

CLIENT_INFO, para obtener al usuario actual de la sesión.

ENTRYID, para obtener el identificador de entrada.

INSTANCE, para obtener el número de identificador de la instancia actual.

ISDBA, para determinar si el usuario actual tiene privilegios DBA.

LANG, para obtener una abreviatura del lenguaje.

LANGUAGE, para obtener el lenguaje, territorio y caracteres de la sesión.

SESSIONID, para obtener el identificador de sesión.

TERMINAL, para obtener el identificador del sistema operativo.

Esta función se considera obsoleta y se recomienda SYS_CONTEXT.

SYS_CONTEXT('namespace',

'atributo' [, len])

Esta función permite recuperar información (como un VARCHAR2) asociada a un atributo de un espacio de nombres de la sesión actual. Por ejemplo, el espacio de

nombres relacionado con el usuario actual es USERENV, el cuál describe nuestra sesión.

Existen una gran diversidad de parámetros para el espacio de nombres USERENV, entre ellos:

CURRENT_SCHEMA, CURRENT_SCHEMAID,

HOST, INSTANCE, INSTANCE_NAME,

IP_ADDRESS, ISDBA,

LANG, LANGUAGE,

NETWORK_PROTOCOL, NLS_CALENDAR,

NLS_CURRENCY, NLS_DATE_FORMAT,

NLS_SORT, NLS_DATE_LANGUAGE,

NLS_TERRITORY, OS_USER,

SERVER_HOST, SERVICE_NAME,

SESSION_USER, SESSION_USERID,

SESSIONID, SID,

TERMINAL.

Oracle permite crear espacios de nombres en el contexto actual que permiten almacenar información asociada a un nombre de atributo. Se usa el comando CREATE CONTEXT para:

• Crear un espacio de nombres para un contexto. • Asociar el espacio de nombres con un paquete creado externamente, el cual asigna el contexto.

Podemos usar el procedimiento DBMS_SESSION.SET_CONTEXT dentro del paquete para asignar o reasignar los atributos del contexto. Para crear un espacio de nombres de contexto debemos tener el permiso del sistema CREATE ANY CONTEXT. El siguiente comando crea un espacio de nombres llamado Mi_Contexto asociado a un paquete llamado Paquete_Contexto: CREATE OR REPLACE CONTEXT Mi_Contexto USING Paquete_Contexto;

Podemos añadir un atributo (usuario) al espacio de nombres con el siguiente comando: DBMS_SESSION.SET_CONTEXT('MiContexto', 'usuario', 'Pedro');

Y podemos recuperar el valor del atributo como un VARCHAR2 con:

Page 50: Java y Oracle 11g

Oracle /50

SYS_CONTEXT('Mi_Contexto', 'usuario')

2.3.7. Funciones de conversión.

ASCIISTR Traslada un string a algún juego de caracteres y retorna un string ASCII en el juego de caracteres de la base de datos.

BIN_TO_NUM Convierte valores binarios en su equivalente numérico.

CAST Moldea un tipo predefinido o de colección a otro. Se usa normalmente con tablas anidadas y arrays variables.

CHARTOROWID Cambia un string de caracteres para que actúen como un identificador de fila

interno de Oracle, o RowID.

COMPOSE Traslada un string de algún tipo de dato o un string Unicode a una forma completa normalizada.

CONVERT Convierte una cadena de caracteres desde un lenguaje nacional a otro.

DECODE Selecciona un valor CHAR, VARCHAR2 o NUMBER de una lista según un valor.

DECOMPOSE Traslada un string de algún tipo a un string Unicode después de su descomposición canónica en el mismo juego de caracteres.

HEXTORAW Cambia una cadena de caracteres de números hexadecimales en un valor binario.

NUMTODSINTERVAL Convierte un número a un literal INTERVAL DAY TO SECOND.

NUMTOYMINTERVAL Convierte un número a un literal INTERVAL YEAR TO MONTH.

RAWTOHEX Cambia un string de números binarios a un string de caracteres de números hexadecimales.

RAWTONHEX Convierte un raw a un valor NVARCHAR2 que contiene su equivalente hexadecimal.

ROWIDTOCHAR Cambia un identificador de fila interno de Oracle, o RowID, a un string.

ROWIDTONCHAR Convierte un valor RowID a un valor NVARCHAR2.

SCN_TO_TIMESTAMP Convierte un número de cambio de sistema a su aproximado TIMESTAMP.

TIMESTAMP_TO_SCN Convierte un TIMESTAMP a número de cambio de sistema aproximado.

TO_BINARY_DOUBLE Cambia un valor binario a un número de doble precisión con coma flotante.

TO_BINARY_FLOAT Cambia un valor binario a un número de simple precisión con coma flotante.

TO_CHAR Convierte un NUMBER o DATE a un string.

TO_CLOB Convierte un valor NCLOB a un valor CLOB.

TO_DATE Convierte un NUMBER, CHAR o VARCHAR2a DATE.

TO_DSINTERVAL Convierte un string del tipo CHAR, VARCHAR2, NCHAR oNVARCHAR2a un valor

INTERVAL DAY TO SECOND.

TO_LOB Convierte un LONG a un LOB como parte de una inserción o selección.

TO_MULTI_BYTE Convierte caracteres de un byte en un string de caracteres de varios bytes.

TO_NCHAR Convierte un string, NUMBER o DATE a al formato nacional.

TO_NCLOB Convierte valores CLOB a valores NCLOB.

TO_NUMBER Convierte un CHAR o VARCHAR2 a número.

TO_SINGLE_BYTE Convierte un CHAR o VARCHAR2a bytes simples.

TO_TIMESTAMP Convierte un string a un valor TIMESTAMP.

TO_TIMESTAMP_TZ Convierte un string a un valor TIMESTAMP WITH TIME ZONE.

TO_YMINTERVAL Convierte un string del tipo CHAR, VARCHAR2, NCHAR o NVARCHAR2 a un valor

INTERVAL YEAR TO MONTH.

TRANSLATE Traslada caracteres en un string dentro de diferentes caracteres.

UNISTR Convierte un string a Unicode.

Las funciones más usadas para conversión de datos son las siguientes: • TO_CHAR. Transforma un DATE o NUMBER en un string. • TO_DATE. Transforma un NUMBER, CHAR o VARCHAR2 en un DATE. Para trabajar con valores timestamp podemos usar TO_TIMESTAMP o TO_TIMESTAMP_TZ. • TO_NUMBER. Transforma un CHAR o VARCHAR2 en un NUMBER. Se indica el formato de la conversión utilizando estos símbolos: 9 (posición del número), 0 (posición del número mostrando ceros), $ (formato dólar), L (símbolo local de la moneda), S (hace que aparezca el símbolo del signo), D (posición del símbolo decimal, la coma), G (posición del separador de grupo, el punto).

Conversión automática de tipos de datos. Oracle es capaz de convertir datos automáticamente a fin de que la expresión final tenga sentido. En ese

Page 51: Java y Oracle 11g

Oracle /51

sentido son fáciles las conversiones de texto a número y viceversa. Por ejemplo: SELECT 5 + '3' FROM DUAL -- El resultado es 8

SELECT 5 || '3' FROM DUAL -- El resultado es 53

También ocurre esto con la conversión de textos a fechas, de hecho es la forma habitual de asignar fechas. Las reglas básicas de conversión son las siguientes:

• Cualquier NUMBER o DATE puede convertirse a un string. Cualquier función de cadenas puede usarse sobre una columna de tipo NUMBER o DATE. • Un valor CHAR o VARCHAR2 serán convertidos a un NUMBER si contiene un valor numérico válido. • Un valor CHAR o VARCHAR2 será convertido a un DATE sólo si contiene una fecha con un formato por defecto. Esto es válido para todas las funciones excepto para GREATEST y LEAST, las cuales tratan los valores como strings.

Funciones especializadas de conversión. Oracle incluye varias funciones de conversión especializadas. Si esperamos usar SQL*Plus y Oracle simplemente para generar informes, probablemente no necesitaremos usar estas funciones. Las funciones de conversión generalmente toman un valor simple como entrada y retornan un valor simple convertido como salida. Por ejemplo, la función BIN_TO_NUM convierte valores binarios a un valor numérico decimal. Los valores de entrada son una lista de dígitos como valores binarios separados por comas. Así, BIN_TO_NUM(1,1,1,0) produce la salida 14 (=1110 en base 2). Cuando trabajamos con operaciones de recuperación flashback, podemos convertir números de cambio del sistema (SCN's) a valores timestamp mediante la función SCN_TO_TIMESTAMP; TIMESTAMP_TO_SCN retorna el SCN para un timestamp dado. Desde Oracle Database 10g, podemos usar las funciones TO_BINARY_DOUBLE y TO_BINARY_FLOAT para convertir valores entre doble y simple precisión respectivamente. Funciones de transformación. Hay dos funciones especiales que podemos usar para controlar la conversión según el tipo de entrada, en vez de simplemente realizar una transformación. Estas funciones son TRANSLATE y DECODE. TRANSLATE es una función simple que realiza una sustitución ordenada caracter a caracter dentro de un string. El formato de esta función es el siguiente: TRANSLATE( string , if, then)

TRANSLATE mira cada caracter dentro del string y comprueba el segundo argumento para ver si este caracter está también dentro del segundo argumento. Si lo encuentra guarda su posición en el segundo argumento y busca en el tercer argumento el caracter situado en la misma posición y lo envía como parte de la salida de la función. Si no encuentra el caracter del string en el segundo argumento se envía este caracter como parte de la salida de la función. Por ejemplo, la siguiente consulta SELECT TRANSLATE(7671234, 234567890,

'BCDEFGHIJ') FROM DUAL

produce la salida 'GFG1BCD'. Aunque TRANSLATE es técnicamente una función de cadenas, podemos ver que convierte automáticamente los datos y trabaja con strings y números. Por su parte, la función DECODE puede ser considerada como una función de sustitución valor por valor. Es similar a una estructura IF-THEN-ELSE para seleccionar un valor. Su sintaxis es: DECODE ( expresiónBase, comparando1, valor1, comparando2, valor2, ... [, valorPorDefecto] )

El primer argumento es una expresión base que se compara con otras. A continuación, cada dos argumentos definen una expresión de comparación y un valor. Si la expresión base coincide con la expresión de comparación la función retorna el valor asociado. Si no coincide ninguna comparación la función retorna el último argumento (que es opcional) con un valor por defecto. Todos los posibles valores devueltos deben ser del mismo tipo o convertibles al mismo tipo. Por ejemplo, la siguiente consulta retorna el valor 2: SELECT DECODE('abc' , 'a' , 1 , 'abc', 2 , 3) FROM DUAL;

2.3.8. Funciones de selección. Oracle proporciona una estructura y varias funciones para seleccionar valores basándose en una o varias condiciones. Las funciones de selección como DECODE, GREATEST o LEAST ya han sido vistas previamente. La estructura «CASE». La estructura CASE no se trata de una estructura de control, sino de una instrucción que evalúa una expresión booleana y retorna un valor (que podemos asignar a una variable o usar en la lista de campos de un SELECT).

Page 52: Java y Oracle 11g

Oracle /52

Admite dos sintaxis: CASE expresión

WHEN valor_expresion1 THEN valor_devuelto1

WHEN valor_expresion2 THEN valor_devuelto2

ELSE valor_devuelto_por_defecto

END

CASE

WHEN valor operador1 expresion1 THEN valor_devuelto1

WHEN valor operador2 expresion2 THEN valor_devuelto2

ELSE valor_devuelto_por_defecto

END

Todos los posibles valores devueltos deben ser del mismo tipo. Por ejemplo, la siguiente consulta retorna el valor 1 si la fecha actual es mayor que el '1/1/2010': SELECT CASE WHEN SYSDATE >'1/1/2010' THEN 1 ELSE 2 END FROM DUAL;

3. Trabajando con objetos

3.1. Introducción.

Según los estándares actuales, una base de datos de Oracle es un conjunto de objetos pensados para gestionar datos. Estos objetos están contenidos en esquemas, y los esquemas están asociados al perfil de un usuario concreto. En el estándar SQL-Oracle existe el concepto de catálogo, que sirve para almacenar esquemas. Así, el nombre completo de un objeto vendría dado por: catálogo.esquema.objeto

Si no se indica el catálogo, se toma el catálogo por defecto. Si no se indica el esquema, se entiende que el objeto está en el esquema actual. Básicamente, existen dos tipos de comandos SQL:

• Los DLL, que permiten crear y definir los objetos de bases de datos: tablas, campos e índices. CREATE Utilizado para crear nuevas tablas, campos e índices DROP Empleado para eliminar tablas e índices ALTER Utilizado para modificar las tablas agregando campos o cambiando la definición de los

campos. • Los DML, que permiten generar consultas para ordenar, filtrar y extraer datos de la base de datos.

SELECT Utilizado para consultar registros de la base de datos que satisfagan un criterio determinado

INSERT Utilizado para cargar lotes de datos en la base de datos en una única operación. UPDATE Utilizado para modificar los valores de los campos y registros especificados DELETE Utilizado para eliminar registros de una tabla de una base de datos

3.2. Diccionario de datos de Oracle.

El diccionario de datos (DD) es una parte fundamental de las bases de datos Oracle. Está formado por tablas, vistas y paquetes a los que se puede acceder para obtener información. Las tablas se crean automáticamente durante la instalación y permiten saber:

- La estructura lógica y física de la base de datos. - Los usuarios de la base de datos. - Las restricciones de integridad sobre las tablas de la base de datos. - El espacio asociado a cada objeto en la base de datos y la cantidad que se está utilizando por los distintos objetos creados por los usuarios de la base de datos.

El usuario SYS es el dueño del DD y tiene todos los permisos sobre cualquier objeto de la base de datos (también los de cualquier usuario). Componentes del DD son:

Tablas base: Una serie de tablas a las que el servidor de datos accede cada vez que se procesa una instrucción DDL de SQL o en algunos comandos DML.

Vistas estáticas: Decodifican y resumen la información contenida en las tablas base. Durante la creación de estas vistas se generan sinónimos públicos para proveer el acceso a los usuarios de la base de datos. Estas vistas deben ser utilizadas para las labores de administración rutinarias que necesiten información específica sobre configuración y estado de la base de datos. Tienen el nombre de estáticas porque no mantienen información relacionada con las sesiones. Se dividen en 3 categorías:

Page 53: Java y Oracle 11g

Oracle /53

• Vistas con prefijo USER_: Puede utilizarlas cualquier usuario de la base de datos y se refieren a objetos poseídos por dicho usuario. Por ejemplo:

SELECT * FROM USER_TABLES;

Muestra toda la información de las tablas del usuario actual. • Vistas con prefijo ALL_: Evidente, las podrá usar cualquier usuario y además añaden la columna OWNER al resto de información. Con estas vistas se puede tener acceso a la información de los objetos de los cuales el usuario es dueño además de los objetos públicos y a los que el usuario tiene acceso (por pertenecer a un grupo de seguridad o poseer ciertos privilegios). • Vistas con prefijo DBA_: Dan información sobre todos los objetos de la base de datos. Usualmente también tienen la columna OWNER. Sólo las puede utilizar el administrador o usuarios con privilegio "SELECT ANY TABLE" o pertenezca a un rol que incluya el privilegio.

Vistas dinámicas (o performance views): Incluyen información sobre las condiciones actuales de operación en la base de datos. La mayor parte son creadas durante la instalación y algunas se crean específicamente para monitorear cierta actividad. Todas se identifican por el prefijo V$. Por ejemplo, la vista dinámica V$_SESSION incluye información sobre las sesiones actuales y la vista V$SYSSTAT provee información estadística sobre el uso de la base de datos. Para obtener información general sobre las vistas del diccionario de datos se podría utilizar esta consulta:

SELECT * FROM Dictionary WHERE table_name LIKE '%indicador%';

Por ejemplo, para ver todas las vistas relacionadas con tablas podríamos ejecutar: SELECT * FROM Dictionary WHERE table_name LIKE '%TABLE%';

Algunas vistas con el prefijo USER_ (pueden verse en ALL_VIEWS) son: – USER_OBJECTS: Lista de todos los objetos pertenecientes al usuario (tablas, vistas, paquetes, índices, triggers, sinónimos...). – USER_TABLES: Lista de todas las tablas del usuario. – USER_VIEWS: Vistas del usuario. – USER_USERS: Diversos datos sobre el usuario. – USER_UPDATABLE_COLUMNS: Columnas que pueden ser modificadas. – USER_JOBS: Tareas pertenecientes al usuario. – USER_TRIGGERS: Disparadores (triggers) del usuario. – USER_SYNONYMS: Sinónimos pertenecientes al usuario. – USER_INDEXES: Índices pertenecientes al usuario. – USER_CONSTRAINTS: Restricciones pertenecientes al usuario. – USER_TAB_PRIVS: Permisos sobre objetos con el usuario involucrado. Si se pone _COL_ en vez de _TAB_ se refiere a las columnas. Se puede distinguir entre:

• USER_TAB_PRIVS_MADE: Permisos sobre los objetos del usuario. • USER_TAB_PRIVS_RECD: Permisos recibidos por el usuario.

– USER_TAB_COLUMNS: Descripciones de las columnas del usuario. – USER_TAB_COMMENTS y USER_COL_COMMENTS: Comentarios sobre las tablas y columnas del usuario, si se han insertado con el comando COMMENT:

COMMENT ON [TABLE|COLUMN] <Tabla>[.<Columna>] IS '<Texto>';

3.3. Crear y usar bases de datos.

Para crear una base de datos debemos usar la siguiente instrucción: CREATE DATABASE MiBaseDeDatos;

Una vez creada la base de datos, debemos indicar explícitamente que queremos trabajar con ella: USE MiBaseDeDatos;

Para eliminar una base de datos existente debemos usar: DROP DATABASE MiBaseDeDatos;

3.4. Crear y usar tablespaces.

Nos podemos encontrar con los siguientes tipos de tablespaces: ▪ El tablespace SYSTEM. Es el único que se crea con la base de datos (CREATE DATABASE). Este tablespace contiene: el Diccionario de Datos (incluidos los procedimientos almacenados), y el segmento de «rollback» del sistema. No debe usarse para contener datos de aplicaciones. ▪ Tablespaces TEMPORALES. Son aquellos en los que solamente puede haber objetos temporales. No se pueden crear objetos permanentes como pueden ser los índices, las tablas o los segmentos de «rollback».

Page 54: Java y Oracle 11g

Oracle /54

Se utilizan para optimizar operaciones de ordenación. ▪ De tipo deshacer cambios (desde Oracle 9i). Se utilizan para gestionar poder deshacer las transacciones incompletas. ▪ Con tamaño de bloque variable (desde Oracle 9i). ▪ De tipo BigFile (desde Oracle 10g).

3.4.1. Estado del tablespace. Su estado puede ser ONLINE u OFFLINE. Existe una vista que nos da información sobre los tablespaces de nuestra base de datos. Esta vista es la siguiente: SELECT Tablespace_Name, Status FROM DBA_TABLESPACES;

Para poder realizar una copia de seguridad del tablespace, estando completamente seguros de que nadie está modificando los objetos del tablespace, es necesario establecer el modo OFFLINE. Así mismo, se actuará de igual forma para poder actualizar una aplicación que se basa en los objetos de este tablespace sin que ningún usuario pueda modificar los datos en medio de la actualización. 3.4.2. Para crear un tablespace: La sintaxis general para crear un tablespaces es: CREATE [UNDO] TABLESPACE nombre_tablespace

DATAFILE Opciones_Datafile Opciones_Almacenamiento ;

Donde Opciones_Datafile tiene la sintaxis: 'nombre fichero' [AUTOEXTEND OFF]

'nombre fichero' [AUTOEXTEND ON [NEXT int K | M] [MAXSIZE int K | M]]

La opción AUTOEXTEND MAXSIZE es por defecto UNLIMITED si no se especifica valor. Donde Opciones_Almacenamiento tiene la sintaxis: DEFAULT [COMPRESS|NOCOMPRESS] STORAGE storage_clause

MINIMUM EXTENT int {K|M}

BLOCKSIZE int K

LOGGING | NOLOGGING

FORCE LOGGING

ONLINE | OFFLINE

PERMANENT | TEMPORARY

EXTENT MANAGEMENT {DICTIONARY | LOCAL {AUTOALLOCATE | UNIFORM [SIZE int K | M]} }

SEGMENT SPACE MANAGEMENT {MANUAL | AUTO}

3.4.3. Para aumentar el tamaño del tablespace: Se utiliza la instrucción ALTER DATABASE TABLESPACE, como por ejemplo: ALTER DATABASE TABLESPACE prueba ADD DATAFILE 'c:\oracleexe\oradata\XE\prueba02.dbf'SIZE 50M;

/

ALTER DATABASE DATAFILE'/users/oradata/orcl/prueba01.dbf'RESIZE 150M;

3.4.4. Para borrar un tablespace: Se utiliza la instrucción DROP TABLESPACE nombre_tablespace;

3.4.5. Tablespaces temporales: Para crear un tablespace temporal simplemente hay que añadir la palabra TEMPORARY a la instrucción utilizada para crear tablespaces normales. CREATE TABLESPACE prueba DATAFILE '/users/oradata/orcl/prueba01.dbf' SIZE 100M TEMPORARY;

/

ALTER USER nombre_de_usuario TEMPORARY TABLESPACE nombre_de_tablespace;

/

SELECT username, temporary_tablespace FROM dba_users;

/

SELECT tablespace_name, contents FROM dba_tablespaces;

3.4.6. Tablespaces read-only (de solo lectura): Los tablespace de solo lectura permiten consultar los datos de los objetos, pero no se puede ni borrar ni insertar nada en ellos. La principal ventaja de un tablespace read-only es que no hace falta hacer un backup del mismo. Ejemplo de los tablespaces read-only: SQL>ALTER TABLESPACE DataCursoxy READ ONLY;

Tablespace modificado.

SQL>INSERT INTO tabla01 VALUES ('PRIMERA FILA');

Page 55: Java y Oracle 11g

Oracle /55

ORA-00372: el fichero 3 no puede ser modificado en este momento

ORA-01110: fichero de datos 3: '/u02/oradata/CURSOxy/datacursoxy01.dbf'

SQL> DROP TABLE TABLA01;

Tabla borrada.

SQL> alter tablespace DataCursoxy READ WRITE;

Tablespace modificado.

SQL>INSERT INTO tabla02 VALUES ('PRIMERA FILA');

1 fila creada.

SQL>COMMIT;

Validación terminada.

3.4.7. Tablespace de Undo (deshacer): Podemos tener varios tablespaces de "undo", pero sólo uno de ellos estará activo. No se pueden crear objetos sobre un tablespace de "undo". Al cambiar de tablespace "undo" activo (con UNDO TABLESPACE), los segmentos de «rollback» que contiene el nuevo tablespace pasan a estar ONLINE, mientras que los del tablespace anterior se ponen OFFLINE. Se crean de dos formas:

- Mediante CREATE DATABASE. - Mediante CREATE TABLESPACE:

CREATE UNDO TABLESPACE undotbs02 DATAFILE 'c:\oraclexe\oradata\ex\undo02.dbf'SIZE 25M

REUSE AUTOEXTEND ON;

Parámetros de inicialización de los espacios de tablas de deshacer: - Undo_Management (valores MANUAL/AUTO). Si AUTO se gestionará de forma automática el espacio de deshacer. No es dinámico, cuando se cambia de estado se debe re arrancar la instancia. - Undo_tablespace (MANUAL/AUTO). Se usa en entornos RAC (Real Application Clusters).

3.5. Crear esquemas.

Cada esquema está asociado a un usuario de Oracle. Por tanto, para crear un esquema debemos crear primero el usuario, y también los tablespaces dónde ubicar los objetos que creemos dentro del esquema (aunque se pueden utilizar tablespaces ya existentes). Para poder realizar estos pasos es necesario iniciar la sesión en la base de datos con un usuario con permisos de administración, lo más sencillo es utilizar directamente el usuario SYSTEM. Para crear un tablespace para datos y otro para índices, usaremos CREATE TABLESPACE "APPDAT" LOGGING

DATAFILE '/export/home/oracle/oradata/datafiles/APPDAT.dbf' SIZE 1024M

EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;

para los datos, con tamaño inicial de 1024 Mb, y auto extensible; y usaremos CREATE TABLESPACE "APPIDX" LOGGING

DATAFILE '/export/home/oracle/oradata/datafiles/APPIDX.dbf' SIZE 512M

EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;

para los índices, con tamaño inicial de 512 Mb, y auto extensible. Para crear el usuario que va a trabajar sobre estos tablespaces, y que será el propietario de los objetos que se creen en ellos usaremos CREATE USER "APP" PROFILE "DEFAULT" IDENTIFIED BY "APPPWD"

DEFAULT TABLESPACE "APPDAT" TEMPORARY TABLESPACE "TEMP" ACCOUNT UNLOCK;

Si no se especifica un tablespace, la BD le asignará el tablespace USERS, que es el tablespace que se utiliza por defecto para los nuevos usuarios. Se puede apreciar también que no hay ninguna referencia al tablespace de índices APPIDX que hemos creado. Si queremos mantener datos e índices separados habrá que acordarse de especificar este tablespace en las sentencias de creación de índices de este usuario, sino se crearán en APPDAT: CREATE INDEX mi_indice ON mi_tabla(mi_campo)

TABLESPACE APPIDX;

Sólo falta asignar al usuario los permisos necesarios para trabajar. Si se le asignan los roles "Connect" y "Resource" ya tiene los permisos mínimos, podrá conectarse y podrá realizar las operaciones más habituales de consulta, modificación y creación de objetos en su propio esquema. GRANT "CONNECT" TO "APP";

GRANT "RESOURCE" TO "APP";

En el momento que el usuario crea el primer objeto en la BD, se crea automáticamente el esquema.

Page 56: Java y Oracle 11g

Oracle /56

3.6. Crear y usar tablas.

3.6.1. Crear el esquema de una tabla. Para crear una tabla se usa la siguiente instrucción SQL: CREATE TABLE tabla (

campo1 tipo (tamaño) restricciones1 ,

...,

campoN tipo (tamaño) restriccionesN ,

CONSTRAINT nombre_restricción tipo_restricción (columnas)

. . .

) ;

En donde tabla es el nombre de la tabla que se va a crear; campo1...campoN son el nombre de los campos que se van a crear en la nueva tabla (la nueva tabla debe contener, al menos, un campo); tipo es el tipo de datos de cada campo; tamaño es el tamaño del campo según su tipo; restricciones1... restriccionesN son cláusulas de restricción opcionales que restringen el comportamiento del campo; CONSTRAINT define restricciones con nombre propio (véase la siguiente sección). Como ejemplo crearemos una tabla para almacenar empleados: CREATE TABLE Empleado (

ID INTEGER PRIMARY KEY,

Nombre VARCHAR2(25)NOT NULL,

Apellidos VARCHAR2(50) NOT NULL,

Fecha_Nacimiento DATE,

Categoria VARCHAR2(1) DEFAULT 'A');

Se crea una nueva tabla llamada Empleado con un campo Nombre de tipo texto y longitud 25 y otro llamado Apellidos con longitud 50 que no admiten valores nulos; crea otro campo llamado Fecha_Nacimiento de tipo DATE, y el campo Categoria admite un único caracter cuyo valor por defecto es 'A'; y crea el campo ID de tipo entero, que se establece como clave principal. 3.6.2. Crear una tabla a partir de otra. Oracle permite crear una nueva tabla al aire, basándose en una consulta sobre una tabla existente. Por ejemplo, el siguiente comando crea una versión simplificada de la tabla Empleado. CREATE TABLE Empleado_2 AS

SELECT Apellidos, Categoria FROM Empleado;

La única restricción de este comando es que no trabaja con consultas que devuelven columnas del tipo LONG. Si se describe la nueva tabla (con el comando DESCRIBE), se revela que hereda la definición de sus columnas de la tabla Empleado. La nueva tabla creada será poblada con los datos procedentes de la consulta. Si deseamos filtrar los registros que se deben insertar en la nueva tabla podemos especificar condiciones con la cláusula WHERE. El siguiente comando crea una nueva tabla con columnas para los apellidos, categoría y año de nacimiento de aquellos empleados nacidos antes de 1980: CREATE TABLE Empleado_3 AS

SELECT Apellidos, Categoria, EXTRACT(Year from Fecha_Nacimiento) AS AñoNacimiento

FROM Empleado

WHERE EXTRACT(Year from Fecha_Nacimiento) < 1980;

Si queremos crear la nueva tabla sin insertarle ningún registro basta con especificar una condición que no cumpla ninguno de los registros de la consulta. Esta forma de crear una nueva tabla permite utilizar consultas que devuelven columnas nuevas, bien como producto de funciones o por la combinación de otras columnas. Las columnas basadas en caracteres se ajustarán al tamaño necesario para contener los datos de las columnas nuevas. Las columnas numéricas que proceden de la computación de columnas de la tabla original que especifican una precisión, se simplifican al tipo NUMBER, sin especificar la precisión, en la nueva tabla. Podemos crear la nueva tabla sin generar entradas de registro de deshacer (registros cronológicos de acciones de base de datos usadas durante la recuperación de la base de datos). Se evita la generación de estas entradas usando la palabra clave NOLOGGING en el comando CREATE TABLE. Haciendo esto se aumenta el rendimiento del comando porque tendrá menos trabajo qué hacer. Sin embargo, haciendo esto la tabla no podrá ser recreada si, después de un fallo de la base de datos, se utilizan los archivos de deshacer para recuperar la base de datos. El siguiente ejemplo muestra cómo usar la palabra clave NOLOGGING. CREATE TABLE Empleado_2 NOLOGGING AS

Page 57: Java y Oracle 11g

Oracle /57

SELECT * FROM Empleado;

3.6.3. Eliminar una tabla. Para eliminar una tabla se usa la instrucción: DROP TABLE nombreTabla ;

3.6.4. Cambiar de nombre. La orden RENAME permite el cambio de nombre de cualquier objeto. Sintaxis: RENAME nombreViejo TO nombreNuevo ;

3.6.5. Borrar el contenido de tablas. La orden TRUNCATE TABLE seguida del nombre de una tabla, hace que se elimine el contenido de la tabla, pero no la tabla en sí. Incluso borra del archivo de datos el espacio ocupado por la tabla. TRUNCATE TABLE nombreTabla ;

3.6.6. Modificar el esquema de una tabla. Una vez creado el esquema de una tabla podemos modificarlo con la instrucción ALTER TABLE. Para añadir un nuevo campo a una tabla creada: ALTER TABLE Empleado ADD Tipo VARCHAR(20) NULL;

Para cambiar el tipo de datos y propiedades de una determinada columna: ALTER TABLE Empleado MODIFY (Tipo INT);

Para eliminar un campo existente de una tabla: ALTER TABLE Empleado DROP COLUMN Tipo;

Reglas para añadir o modificar una columna. Las reglas para añadir una columna a una tabla son las siguientes:

• Podemos añadir una columna directamente si no se ha especificado NOT NULL sobre ella. • Podemos añadir una columna NOT NULL en tres pasos:

1. Añadir la columna sin especificar NOT NULL. 2. Llenar esta columna con datos en cada fila. 3. Modificar la columna para ser NOT NULL.

Nota. Si usamos la cláusula DEFAULT cuando añadimos una columna, Oracle actualizará cada registro con el valor por defecto cuando se añada la columna.

Las reglas para modificar una columna son las siguientes: • Podemos incrementar el tamaño de una columna de caracteres. • Podemos incrementar el tamaño de una columna numérica. • Podemos incrementar o decrementar el número de lugares decimales de una columna numérica. • Podemos pasar de CHAR a VARCHAR2 y viceversa (si no se modifica el tamaño). • Podemos pasar de DATE a TIMESTAMP y viceversa.

Además, si una columna tiene el valor NULL en cada registro de la tabla, podemos hacer los siguientes cambios:

• Podemos cambiar el tipo de dato de la columna. • Podemos decrementar el tamaño de una columna de caracteres. • Podemos decrementar el número de dígitos de una columna numérica.

Hay una notable excepción en las restricciones de cambio de tipo de dato. Oracle soporta el cambio de columnas LONG a un tipo LOB, incluso si hay datos en la columna LONG. Otros modos de eliminar columnas. Podemos eliminar varias columnas de una tabla en un único comando. Por ejemplo, el siguiente comando elimina los campos Nombre y Apellidos de la tabla Empleado: ALTER TABLE Empleado DROP (Nombre, Apellidos);

Si eliminamos columnas que son parte de la clave primaria o tienen la restricción UNIQUE, necesitaremos también usar la cláusula CASCADE CONSTRAINTS al final del comando ALTER TABLE. Cuando se elimina una columna que es la clave primaria, también se elimina su índice. Eliminar una columna es más complejo internamente que añadir o modificar una columna, ya que requiere un trabajo adicional por parte de Oracle. Quitar la columna de la lista de columnas de la tabla es más fácil. Quitar una columna hace que se oculte cuando se consulta la tabla, pero no modifica el espacio actual reservado físicamente para ella. Podemos ocultar una columna inmediatamente marcándola como UNUSED, y eliminarla permanentemente más tarde, cuando el impacto de rendimiento por tener que redimensionar todos los registros de la tabla no afecte a nuestro trabajo con la base de datos.

Page 58: Java y Oracle 11g

Oracle /58

En el siguiente ejemplo se oculta una columna de la tabla Empleado: ALTER TABLE Empleado SET UNUSED COLUMN Fecha_Nacimiento;

Al marcar la columna como "unused" no liberamos el espacio previamente asignado para cada valor de esta columna en cada registro, hasta que eliminamos las columnas no usadas de la siguiente forma: ALTER TABLE Empleado DROP UNUSED COLUMNS;

Para ver todas las tablas con columnas marcadas como no usadas, podemos consultar las vistas USER_UNUSED_COL_TABS, ALL_UNUSED_COL_TABS y DBA_UNUSED_COL_TABS. 3.6.7. Obtener el esquema de una tabla. El comando DESCRIBE permite obtener la estructura de una tabla. Ejemplo: DESCRIBE Empleado;

Y aparecerán los campos de la tabla Empleado, de forma parecida a la siguiente: Nombre Null? Tipo

------------------------- -------–------ ----------------------

ID NOT NULL NUMBER(38,0)

NOMBRE NOT NULL VARCHAR2(25)

APELLIDOS NOT NULL VARCHAR2(50)

FECHA_NACIMIENTO DATE

CATEGORIA VARCHAR2(1)

Esta instrucción no es parte del SQL estándar, pero casi es considerada así ya que casi todos los SGBD la soportan. 3.6.8. Añadir comentarios a las tablas. Se le pueden poner comentarios a las tablas y las columnas. Un comentario es un texto descriptivo utilizado para documentar la tabla. Su sintaxis es: COMMENT ON { TABLE NombreTabla | COLUMN tabla.nombreColumna } IS 'Comentario'

Para mostrar los comentarios puestos se usan las siguientes vistas del diccionario de datos mediante la instrucción SELECT:

- USER_TAB_COMMENTS. Comentarios de las tablas del usuario actual. - USER_COL_COMMENTS. Comentarios de las columnas del usuario actual. - ALL_TAB_COMMENTS. Comentarios de las tablas de todos los usuarios (sólo administradores). - ALL_COL_COMMENTS. Comentarios de las columnas de todos los usuarios (sólo administradores).

3.6.9. Determinar si existe una tabla. Para determinar si ya existe una tabla en un esquema determinado podemos consultar las siguientes vistas del diccionario de datos del sistema: DBA_TABLES, USER_TABLES o ALL_TABLES. Por ejemplo, para saber si existe la tabla MITABLA dentro del esquema actual usaríamos: SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME='MITABLA'

Si esta consulta no retorna registros es que no existe dicha tabla. Si queremos obtener toda la información sobre una tabla llamada MITABLA perteneciente a un usuario llamado USER1 usaríamos: SELECT * FROM ALL_TABLES WHERE TABLE_NAME='MITABLA' AND OWNER='USER1';

Nota. En las vistas del diccionario de datos, todos los nombres de objetos se guardan en mayúsculas.

3.6.10. Uso de tablas temporales. Podemos crear tablas que existan únicamente para nuestra sesión o cuyos datos persistan sólo durante nuestra transacción. Podemos usar tablas temporales para soportar consultas de resumen especializadas o requerimientos específicos de la aplicación. Para crear una tabla temporal se usa el comando CREATE GLOBAL TEMPORARY TABLE. Cuando se crea una tabla temporal podemos especificar si sus datos deberían durar hasta que termine nuestra sesión (mediante la cláusula ON COMMIT PRESERVE ROWS) o si las filas deben ser eliminadas cuando la transacción se complete (mediante la cláusula ON COMMIT DELETE ROWS). Al contario que una tabla permanente, una tabla temporal no reserva automáticamente espacio cuando es creada. El espacio será asignado dinámicamente a mediada que las filas son insertadas. El siguiente ejemplo muestra cómo crear una tabla temporal: CREATE GLOBAL TEMPORARY TABLE FALTA_ROLLUP (

año NUMBER(4),

mes VARCHAR2(9),

contador NUMBER)

Page 59: Java y Oracle 11g

Oracle /59

ON COMMIT PRESERVE ROWS;

Podemos ver la duración de nuestros datos en FALTA_ROLLUP consultando la columna Duration de la vista USER_TABLES para esta tabla. En este caso, el valor de Duration es SYS$SESSION. Si hubiésemos especificado la cláusula ON COMMIT DELETE ROWS, el valor de Duration debería ser SYS$TRANSACTION. Ahora que existe la tabla FALTA_ROLLUP, podemos poblarla mediante un comando INSERT a partir de una subconsulta sobre otra tabla. Podemos entonces usar la tabla temporal como parte de una combinación con otras tablas.

3.7. Restricciones.

Una restricción es una condición de obligado cumplimiento para una o más columnas de la tabla. A cada restricción se le pone un nombre (en el caso de no poner un nombre, entonces el propio Oracle lo crea a partir del nombre de tabla, columna y tipo de restricción). Los nombres de restricción no se pueden repetir para el mismo esquema, por lo que es buena idea incluir de algún modo el nombre de la tabla, los campos involucrados y el tipo de restricción en el nombre de la misma. Por ejemplo pieza_id_pk podría indicar que el campo id de la tabla pieza tiene una clave principal (PRIMARY KEY). 3.7.1. Tipos de restricciones. Oracle admite las siguientes restricciones:

▪ NULL, para indicar que el campo admite nulos (por defecto). CREATE TABLE cliente ( dni VARCHAR2(9) NULL ); /* o también */

CREATE TABLE cliente ( dni VARCHAR2(9) );

▪ NOT NULL, para indicar que el campo no admite nulo. CREATE TABLE cliente (dni VARCHAR2(9) NOT NULL); /* o también */

CREATE TABLE cliente(dni VARCHAR2(9) CONSTRAINT cli_dni_nn NOT NULL);

▪ UNIQUE, para indicar no repetidos en el campo. CREATE TABLE cliente (dni VARCHAR2(9) UNIQUE); /* o también */

CREATE TABLE cliente(dni VARCHAR2(9) CONSTRAINT dni_u UNIQUE);

▪ PRIMARY KEY, para indicar que el campo es la clave primaria. CREATE TABLE cliente(dni VARCHAR2(9) PRIMARY KEY) ; /* o también */

CREATE TABLE cliente(dni VARCHAR2(9) CONSTRAINT cliente_pk PRIMARY KEY) ;

Se puede crear una clave primaria sobre más de un campo: CREATE TABLE alquiler(

dni VARCHAR2(9), cod_pelicula NUMBER(5),

CONSTRAINT alquiler_pk PRIMARY KEY(dni, cod_pelicula)

);

▪ FOREIGN KEY ...REFERENCES, para indicar que el campo es una clave foránea, y de esta forma activar la integridad referencial.

En la siguiente instrucción, CREATE TABLE alquiler(

dni VARCHAR2(9) CONSTRAINT dni_fk REFERENCES cliente(dni),

cod_pelicula NUMBER(5),

CONSTRAINT pelicula_fk FOREIGN KEY (cod_pelicula) REFERENCES pelicula(cod),

CONSTRAINT alquiler_pk PRIMARY KEY(dni,cod_pelicula));

Se indica que el campo dni se relaciona con la columna dni de la tabla clientes, y que el campo cod_pelicula se relaciona con la columna cod de la tabla película. La integridad referencial puede provocar varios problemas en cuanto a borrados. Por ello Oracle ofrece soluciones a añadir tras la cláusula REFERENCES: - ON DELETE SET NULL, coloca nulos todas las claves secundarias relacionadas con la borrada. - ON DELETE CASCADE, borra todos los registros cuya clave secundaria es igual que la clave del registro borrado. En esas cláusulas se podría sustituir la palabra DELETE por la palabra UPDATE, haciendo que el funcionamiento se refiera a cuando se modifica un registro de la tabla principal.

▪ DEFAULT valor_por_defecto, establece un valor por defecto para el campo. CREATE TABLE cliente (dni VARCHAR2(9)DEFAULT '00000000X');

▪ CHECK, establece una condición que deben cumplir los contenidos de una o más columnas. Una misma columna puede tener múltiples CHECK en su definición (se pondrían varios CONSTRAINT seguidos, sin comas). Por ejemplo:

Page 60: Java y Oracle 11g

Oracle /60

CREATE TABLE ingreso (

cod NUMBER(5) PRIMARY KEY,

importe NUMBER(11,2) CONSTRAINT importe_min_ck CHECK (importe>0)

CONSTRAINT importe_max_ck CHECK (importe<8000)

);

O bien: CREATE TABLE ingreso (

cod NUMBER(5) PRIMARY KEY,

importe NUMBER(11,2),

CONSTRAINT importe_rango_ck CHECK (importe>0 AND importe<8000)

);

En este caso, CHECK prohíbe añadir datos cuyo importe no esté entre 0 y 8000. 3.7.2. Reglas para nemotécnicas. Oracle aconseja esta regla a la hora de poner nombre a las restricciones:

- Tres letras para el nombre de la tabla - Carácter de subrayado - Tres letras con la columna afectada por la restricción - Carácter de subrayado - Dos letras con la abreviatura del tipo de restricción. La abreviatura puede ser: NN (NOT NULL), PK (PRIMARY KEY), UK (UNIQUE), FK (FOREIGN KEY) y CK (CHECK).

3.7.3. Añadir restricciones. Es posible querer añadir restricciones tras haber creado la tabla. En ese caso se utiliza la siguiente sintaxis: ALTER TABLE tabla ADD [CONSTRAINT nombre] tipoDeRestricción (columnas);

Donde tipoDeRestricción puede ser CHECK, UNIQUE, PRIMARY KEY o FOREIGN KEY. Las restricciones NOT NULL deben indicarse mediante ALTER TABLE ... MODIFY colocando NOT NULL en el campo que se modifica. Para hacer NOT NULL una columna en una tabla: ALTER TABLE tabla MODIFY (campo NOT NULL);

Para cambiar el valor por defecto de una columna: ALTER TABLE tabla MODIFY campo VARCHAR2(135) DEFAULT 'ABC...';

3.7.4. Borrar restricciones. La sintaxis general para eliminar una restricción es: ALTER TABLE tabla DROP PRIMARY KEY | UNIQUE (campos) | CONSTRAINT nombreRestricción [CASCADE]

La opción PRIMARY KEY elimina una clave principal (también quitará el índice UNIQUE sobre las campos que formaban la clave). UNIQUE elimina índices únicos. La opción CONSTRAINT elimina la restricción indicada. La opción CASCADE hace que se eliminen en cascada las restricciones de integridad que dependen de la restricción eliminada. 3.7.5. Desactivar restricciones. A veces conviene temporalmente desactivar una restricción para saltarse las reglas que impone. La sintaxis generales: ALTER TABLE tabla DISABLE CONSTRAINT nombre [CASCADE]

La opción CASCADE hace que se desactiven también las restricciones dependientes de la que se desactivó. 3.7.6. Activar restricciones. Para desactivar la anulación de una restricción: ALTER TABLE tabla ENABLE CONSTRAINT nombre [CASCADE]

Sólo se permite volver a activar si los valores de la tabla cumplen la restricción que se activa. Si hubo desactivado en cascada, habrá que activar cada restricción individualmente. 3.7.7. Cambiar de nombre a las restricciones. Para hacerlo se utiliza este comando: ALTER TABLE tabla RENAME CONSTRAINT nombreViejo TO nombreNuevo;

3.7.8. Mostrar restricciones. El trabajo con restricciones ya se ha visto que es complejo. Por eso todas las bases de datos suelen proporcionar una vista (o más) del diccionario de datos que permite consultar las restricciones. En el caso de Oracle, se puede utilizar la vista del diccionario de datos USER_CONSTRAINTS. Esta vista permite identificar las restricciones colocadas por el usuario (ALL_CONSTRAINTS permite mostrar las restricciones de todos los usuarios, pero sólo está permitida a los administradores). En esa vista aparece toda la información que el diccionario de datos posee sobre las restricciones.

Page 61: Java y Oracle 11g

Oracle /61

SELECT * FROM USER_CONSTRAINTS;

3.8. Crear y eliminar índices.

Los índices son esquemas que hacen que una base de datos acelere las operaciones de consulta y ordenación sobre los campos a los que el índice hace referencia. Se almacenan aparte de la tabla a la que hacen referencia, lo que permite crearlos y borrarlos en cualquier momento. Lo que proporcionan es una lista ordenada a la que Oracle puede acceder para facilitar la búsqueda de los datos. Cada vez que se añade un nuevo registro, los índices involucrados se actualizan a fin de que su información esté al día. De ahí que cuantos más índices haya, más le cuesta a Oracle añadir registros, pero más rápidas se realizan las instrucciones de consulta. La mayoría de los índices se crean de manera implícita, como consecuencia de las restricciones PRIMARY KEY (que obliga a crear un índice único sobre los campos clave). La restricción UNIQUE crea también un índice único, y FOREIGN KEY crea un índice con posibilidad de repetir valores (índice con duplicados). Éstos son índices obligatorios, porque los crea la propia base de datos. El nombre que se les da a esos índices suele ser el mismo que el nombre de la restricción que los genera. 3.8.1. Especificación de los tablespaces para índices. Las restricciones UNIQUE y PRIMARY KEY crean índices. A menos que se especifique otra cosa, estos índices son ubicados en el tablespace por defecto. Para especificar un tablespace diferente debemos usar la cláusula USING INDEX TABLESPACE en el comando de creación de la tabla, tal como se muestra en el siguiente ejemplo: CREATE TABLE Autor (

Nombre VARCHAR2(50),

Comentarios VARCHAR2(100),

CONSTRAINT Autor_PK PRIMARY KEY (Nombre) USING INDEX TABLESPACE USERS

);

En este ejemplo se especifica que el índice asociado con la restricción de clave primaria Autor_PK será ubicado en el tablespace USERS. (En la mayoría de instalaciones se crea el tablespace USERS, y se establece como tablespace por defecto.) 3.8.2. Creación de índices explícitos. Aparte de los índices obligatorios comentados anteriormente, se pueden crear índices de forma explícita. Éstos se crean para aquellos campos sobre los cuales se realizarán búsquedas e instrucciones de ordenación frecuente. La sintaxis para crear un índice es la siguiente: CREATE [BITMAP | UNIQUE] INDEX nombreIndice

ON nombreTabla (columna [,columna] . . .) [REVERSE];

Donde nombreIndice debe ser un nombre único y seguir las convenciones de nombrado de Oracle; nombreTabla es el nombre de la tabla para la que se establecerá el índice; y columna son los nombres de las columnas de la tabla que serán indexadas. La opción BITMAP permite crear índices sobre columnas con muy pocos valores distintos. La palabra clave REVERSE le dice a Oracle que invierta los bytes del valor del índice, lo cual puede mejorar la distribución durante la inserción de muchos datos secuenciales. Por ejemplo: CREATE INDEX nombre_completo

ON cliente (apellido1, apellido2, nombre);

Crea un índice para el valor conjunto de los campos apellido1, apellido2 y nombre. Esto no es lo mismo que crear un índice para cada campo; este índice es efectivo cuando se buscan u ordenan clientes usando los tres campos a la vez. Se aconseja crear índices en campos que:

- Contengan una gran cantidad de valores. - Contengan una gran cantidad de nulos. - Son parte habitual de cláusulas WHERE, GROUP BY u ORDER BY. - Son parte de listados de consultas de grandes tablas sobre las que casi siempre se muestran como mucho un 4% de su contenido.

No se aconseja en campos que: - Pertenezcan a tablas pequeñas. - No se usan a menudo en las consultas.

Page 62: Java y Oracle 11g

Oracle /62

- Pertenecen a tablas cuyas consultas muestran más de un 6% del total de registros. - Pertenecen a tablas que se actualizan frecuentemente. - Se utilizan en expresiones

Los índices se pueden crear utilizando expresiones complejas: CREATE INDEX nombre_complejo ON clientes (UPPER(nombre));

Estos índices tienen sentido si en las consultas se utilizan exactamente esas expresiones. 3.8.3. Forzando unicidad. Cuando se crea el índice implícito para la clave de una tabla se aplica por defecto la opción de unicidad en los valores del índice. Esto queda establecido por el uso de la restricción PRIMARY KEY. Puede ser importante para nuestra base de datos garantizar unicidad de valores en columnas que no formen parte de la clave. Por ejemplo, supongamos la tabla Biblioteca, en la que queremos aplicar unicidad sobre los campos Titulo y Autor, de forma que no puedan existir dos registros con el mismo autor y título de libro simultáneamente. Existen tres formas alternativas de lograr esto:

• Creando una restricción de clave primaria sobre los dos campos al crear la tabla, o bien alterándola después de crearla.

ALTER TABLE Biblioteca

ADD CONSTRAINT B_PK PRIMARY KEY (Titulo, Autor);

• Creando una restricción UNIQUE sobre las columnas cuando se crea la tabla, o bien alterando la tabla después de crearla.

ALTER TABLE Biblioteca ADD CONSTRAINT B_UK UNIQUE (Titulo, Autor);

• Creando un índice de unicidad. Si creamos primero el índice de unicidad, todavía podremos crear la clave primaria en la tabla, y además Oracle usará el índice existente como el índice primario clave. El siguiente comando muestra cómo crear el índice de unicidad para estas dos columnas:

CREATE UNIQUE INDEX BA$Titulo_Autor

ON Biblioteca (Titulo, Autor);

3.8.4. Crear un índice bitmap. Para ayudar a realizar consultas que usan columnas que admiten un rango limitado de valores, podemos usar índices bitmap. Los índices bitmap sólo deberían ser usados si los datos raras veces se actualizan, porque añaden un coste adicional a todas las transacciones de manipulación de datos sobre las tablas que incluyen el índice.

Nota. Los índices bitmap no deberían usarse con tablas involucradas en aplicaciones que procesan transacciones online, debido a los mecanismos internos que usa Oracle para mantenerlos.

Para entender estos índices pensemos que la tabla Biblioteca tiene una columna Valoracion, en la cual se usan unos pocos valores: del 1 al 5. Crear un índice B*-tree tradicional sobre la columna Valoracion no suele ser habitual, a menos que sea una columna muy usada para filtrar las consultas en la cláusula WHERE. Sin embargo, esta columna puede tomar todas las ventajas de un índice bitmap. Internamente, un índice bitmap mapea los diversos valores de la columna a cada registro. Para nuestro ejemplo, como hay 5 valoraciones diferentes para un libro, habrá 5 entradas bitmap diferentes en el índice. Por ejemplo, si los cuatro primeros registros tienen una valoración de 1, y los siguientes seis registros tiene una valoración de 3, entonces las entradas del índice bitmap tendrán los siguientes valores:

1: 1 1 1 1 0 0 0 0 0 0

2: 0 0 0 0 0 0 0 0 0 0

3: 0 0 0 0 1 1 1 1 1 1

4: 0 0 0 0 0 0 0 0 0 0

5: 0 0 0 0 0 0 0 0 0 0

Cada columna de 0's y 1's representa un registro de la tabla. En este caso se muestran sólo diez columnas porque esos son los registros considerados en este ejemplo. El optimizador de Oracle puede convertir dinámicamente entradas de índices bitmap a RowID's durante el procesado de consultas. Esta conversión permite al optimizador usar índices sobre columnas que tienen muchos valores distintos y sobre aquellas que tienen pocos valores distintos. Para crear el índice bitmap se usa la cláusula BITMAP, tal como se muestra a continuación: CREATE BITMAP INDEX B$Bitmap_Valoracion

ON Biblioteca (Valoracion);

Si elegimos usar índices bitmap, debemos sopesar las ventajas de rendimiento durante las consultas frente al coste de rendimiento durante los comandos de manipulación de datos. Cuantos más índices de bitmap hay en

Page 63: Java y Oracle 11g

Oracle /63

una tabla, más grande es el coste durante cada transacción. No debemos usar índices bitmap sobre columnas que tienen frecuentemente nuevos valores añadidos. 3.8.5. Situar un índice en la base de datos. Podemos especificar dónde debe ubicarse el índice de una tabla asignándolo a un tablespace específico. Un tablespace se compone de ficheros de datos físicos en los cuales se almacenan los registros e índices de cada tabla. Para incrementar la disponibilidad y las opciones de administración, los índices de una tabla y la misma tabla deberían estar ubicados en tablespaces situados sobre unidades de disco físicamente separadas. Para especificar el tablespace en el cual colocar un índice se utiliza la palabra clave TABLESPACE en el comando CREATE INDEX, tal como se muestra a continuación: CREATE UNIQUE INDEX B$Titulo_Autor

ON Biblioteca (Titulo, Autor)

TABLESPACE B_Indices;

En este ejemplo, B_Indices es el nombre de un tablespace creado previamente por el administrador de la base de datos. Cuando creamos una clave primaria o una restricción de unicidad, Oracle crea automáticamente un índice. A menos que especifiquemos otra cosa, este índice será creado en el mismo tablespace que el de la tabla, y usará los parámetros de almacenamiento por defecto. Ya que esta localización de almacenamiento es normalmente indeseable, deberíamos usar la cláusula USING INDEX al crear la clave primaria o restricción de unicidad. La cláusula USING INDEX permite especificar parámetros de almacenamiento y ubicación de tablespace para el índice creado. En el siguiente ejemplo, se crea la clave primaria de la tabla Biblioteca y se ubica en el tablespace B_Indices. Este ejemplo asume que no existe un índice sobre las columnas especificadas. ALTER TABLE Biblioteca

ADD CONSTRAINT B_PK PRIMARY KEY (Titulo, Autor)

USING INDEX TABLESPACE B_Indices;

3.8.6. Eliminar índices. La instrucción DROP INDEX seguida del nombre del índice permite eliminar el índice en cuestión. Por ejemplo, para eliminar el índice llamado B_PK: DROP INDEX B_PK;

3.8.7. Reconstruir un índice. Oracle proporciona una capacidad rápida de reconstruir índices, que permite recrear un índice sin tener que eliminarlo. Esta capacidad permite usar el índice existente como origen de datos para el índice reconstruido, en vez de usar la tabla como origen de datos. Durante la reconstrucción del índice podemos cambiar los parámetros de almacenamiento y el tablespace asignado. En el siguiente ejemplo, el índice B_PK se reconstruye, y se cambian sus parámetros de almacenamiento para usar una extensión de tamaño inicial de 8MB y un nuevo tamaño de extensión de 4MB, en el tablespace B_Indices. ALTER INDEX B_PK REBUILD

STORAGE (INITIAL 8M NEXT 4M PCTINCREASE 0)

TABLESPACE B_Indices;

Nota. Cuando se reconstruye el índice B_PK, debemos tener espacio suficiente para ambos índices, el antiguo y el nuevo. Después de que el nuevo índice es creado, el antiguo se elimina.

Cuando creamos un índice basado en columnas previamente indexadas, Oracle puede ser capaz de usar los índices existentes como origen de datos para el nuevo índice. El optimizador de Oracle puede usar parte de un índice compuesto existente cuando sea necesario en consultas, así que podemos no necesitar crear muchos índices para soportar la mayoría de consultas. Podemos reconstruir índices mientras están siendo accedidos, usando la cláusula REBUILD ONLINE del comando ALTER INDEX. 3.8.8. Índices basados en funciones. Podemos crear índices basados en funciones. Cualquier consulta que realice una función sobre una columna generalmente no usa el índice de esta columna. Por lo tanto, la siguiente consulta podría no usar un índice sobre la columna Titulo: SELECT * FROM Biblioteca WHERE UPPER(Titulo) = 'La Fundación';

Podemos crear índices que permitan accesos basados en funciones que soporten acceso por índices. En vez

Page 64: Java y Oracle 11g

Oracle /64

de crear un índice sobre la columna Titulo, podemos crear un índice sobre la expresión UPPER(Titulo), tal como se muestra a continuación: CREATE INDEX Biblioteca$UPPER_Titulo

ON Biblioteca (UPPER(Titulo));

Aunque los índices basados en funciones pueden ser útiles, debemos considerar las siguientes cuestiones antes de crearlos:

• ¿Podemos determinar de antemano todas las funciones que serán usadas sobre la columna? • ¿Tenemos el espacio de almacenamiento adecuado para los índices adicionales? • Cuando eliminamos la tabla se eliminarán todos sus índices. ¿Cómo impacta al tiempo requerido para eliminar la tabla?

Los índices basados en funciones son útiles, pero deberíamos usarlos con moderación. Cuantos más índices creemos sobre una tabla, más largas serán las operaciones de inserción, actualización y borrado. 3.8.9. Lista de índices. Para ver la lista de índices se utiliza la vista USER_INDEXES. Mientras que la vista USER_IND_COLUMNS muestra la lista de columnas que son utilizadas por índices. 3.8.10. Creación de tablas organizadas por índice. Una tabla organizada por índice guarda sus registros ordenados según los valores de la clave de la tabla, y almacena los datos como si la tabla entera fuese almacenada en un índice. Un índice normal sólo almacena las columnas del índice; una tabla organizada por índice almacena todas las columnas en el índice. Para crear una tabla organizada por índice, se usa la cláusula ORGANIZATION INDEX, como en el siguiente ejemplo: CREATE TABLE Empleado (

Nombre VARCHAR2(13),

Apellidos VARCHAR2(30),

Fecha_Nacimiento DATE,

CONSTRAINT Empleado_PK PRIMARY KEY (Nombre, Apellidos)

) ORGANIZATION INDEX;

Para crear la tabla organizada por índice, hay que crear primero una restricción de clave primaria. Esta tabla Empleado es apropiada para ser organizada por índice si siempre accedemos a los datos filtrándolos por nombre y apellidos. Para minimizar la cantidad de actividad requerida para gestionar el índice, deberíamos usar esta opción sólo si los datos de la tabla son muy estáticos. Si los datos de la tabla cambian frecuentemente, deberíamos usar índices normales. En general, una tabla organizada por índice es más efectiva cuando la clave primaria está formada por muchas columnas de la tabla. Si la tabla contiene muchos accesos a columnas que no son parte de la clave primaria, la tabla necesitará repetidamente acceder al área de desbordamiento. A pesar de esta desventaja, podemos decidir usar tablas organizadas por índice para aprovechar una característica importante que no está disponible con las tablas estándar: la capacidad de usar la opción MOVE ONLINE del comando ALTER TABLE. Se puede usar esta opción para mover una tabla desde un tablespace a otro mientras está siendo accedida por operaciones de inserción, actualización o borrado. No se puede usar esta la opción MOVE ONLINE para tablas particionadas organizadas por índice.

3.9. Uso de tablas particionadas.

Podemos distribuir las filas de una misma tabla en varias partes. Dividir los datos de una tabla de esta manera es lo que se conoce como particionado de tabla; la tabla que es particionada se llama tabla partida, y las partes se denominan particiones. El particionado en habitual en tablas muy grandes. Al distribuir las filas de una tabla grande en varias particiones pequeñas conseguimos varios objetivos importantes:

• El rendimiento de las consultas sobre las tablas puede mejorar porque Oracle puede tener que buscar solo en una partición en vez de en la tabla entera para resolver la consulta. • La tabla puede ser más fácil de administrar. Puede ser más fácil cargar y eliminar datos de las particiones que de la tabla entera. • Las operaciones de copias de respaldo y recuperación pueden realizarse mejor. Debido a que las particiones son más pequeñas que la tabla particionada, podemos tener más opciones para copiar y recuperar las particiones que para toda la tabla.

El optimizador de Oracle sabrá que una tabla está particionada; y podremos especificar la partición a usar en una consulta.

Page 65: Java y Oracle 11g

Oracle /65

3.9.1. Crear una tabla particionada. Para crear una tabla particionada debemos especificar cómo establecer las particiones de los datos de la tabla como parte del comando CREATE TABLE. Normalmente las tablas son divididas por rangos de valores (conocido como el rango de la partición). Consideremos una tabla Biblioteca: CREATE TABLE Biblioteca (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(20),

Categoria VARCHAR2(20) );

Si almacenamos un gran número de registros en la tabla Biblioteca, podemos querer separara los registros entre varias particiones. Para dividir los registros se usa la cláusula PARTITION BY RANGE en el comando CREATE TABLE, tal como se muestra a continuación. Los rangos determinarán los valores almacenados en cada colección. CREATE TABLE Biblioteca_Partida (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(20),

Categoria VARCHAR2(20) )

PARTITION BY RANGE (Categoria) (

PARTITION Parte1 VALUES LESS THAN ('B') TABLESPACE Parte1_TS,

PARTITION Parte2 VALUES LESS THAN (MAXVALUE) TABLESPACE Parte2_TS );

En este ejemplo, la tabla Biblioteca_Partida será particionada según el valor de la columna Categoria. Para categorías cuyo nombre sea menor que 'B' (por ejemplo la categoría 'ADULTO'), los registros serán almacenados en la partición llamada Parte1, que será almacenada en el tablespace Parte1_TS. Cualquier otra categoría será almacenada en la partición Parte2. En este último caso no necesitamos especificar un valor máximo; la palabra clave MAXVALUE le dirá a Oracle que use la partición para almacenar cualquier dato que no haya sido almacenado en las otras particiones. Si queremos crear más particiones debemos especificar en cada una de ellas un valor máximo de rango, de forma que el valor mínimo del rango queda determinado por la definición de la partición previa. Además de particiones de rango, Oracle también soporta particiones de hash. Una partición de hash determina la ubicación física de los datos aplicando una función de hash sobre los valores de la clave de la partición. En particiones de rango, valores consecutivos de la clave son normalmente almacenados en la misma partición. En particiones de hash, valores consecutivos de la clave no son normalmente almacenados en la misma partición; sino que distribuye un conjunto de registros sobre un conjunto mayor de particiones, decrementando potencialmente la probabilidad de contenciones de entrada/salida en los accesos a disco. Para crear una partición de hash se usa la cláusula PARTITION BY HASH en vez de la cláusula PARTITION BY

RANGE, tal como se muestra en el siguiente ejemplo: CREATE TABLE Biblioteca_Hash (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(20),

Categoria VARCHAR2(20) )

PARTITION BY HASH (Categoria)

PARTITIONS 10;

Podemos nombrar cada partición y especificar su tablespace con el siguiente añadido: . . .

PARTITION BY HASH (Categoria)

PARTITIONS 2

STORE IN (Parte_TS, Parte2_TS);

Después de la línea PARTITION BY HASH (Categoria), tenemos dos opciones de formato: • Tal como se muestra en el ejemplo previo, podemos especificar el número de particiones y los tablespaces a usar:

PARTITIONS 2

STORE IN (Parte_TS, Parte2_TS);

Este método creará particiones con nombres generados por el sistema con el formato SYS_Pnnn, donde nnn es un número secuencial. El número de tablespaces especificados en la cláusula STORE IN no tiene que ser igual al número de particiones. Si especificamos más tablespaces, las particiones serán asignadas a los tablespaces según un algoritmo rotatorio "round-robin".

Page 66: Java y Oracle 11g

Oracle /66

• Podemos especificar el nombre de las particiones: PARTITION BY HASH (Categoria) (

PARTITION Parte1 TABLESPACE Parte1_TS,

PARTITION Parte2 TABLESPACE Parte2_TS);

3.9.2. Particiones de lista. Podemos usar particiones de lista en lugar de particiones de rango y de hash. En las particiones de lista se le dan a Oracle todos los posibles valores y se especifica la partición en la cual guardar los registros correspondientes a cada valor de la lista. El siguiente ejemplo muestra otra versión de la tabla Biblioteca particionada por un lista con los posibles valores de categoría. CREATE TABLE Biblioteca_LIST (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(20),

Categoria VARCHAR2(20) )

PARTITION BY LIST (Categoria) (

PARTITION Parte1 VALUES ('ADULTO') TABLESPACE Parte1_TS,

PARTITION Parte2 VALUES ('JOVEN', 'NIÑO') TABLESPACE Parte2_TS);

3.9.3. Crear subparticiones. También podemos crear subparticiones; esto es, particiones de particiones. Podemos usar subparticiones para combinar dos tipos de particiones: de rango y de hash. Podemos usar particiones de hash en combinación con particiones de rango, creando particiones de hash en cada partición de rango. Para tablas muy grandes, esta combinación de particiones puede ser un modo efectivo de separar datos en divisiones manejables. El siguiente ejemplo particiona por rango la tabla Biblioteca según el autor, y particiona por hash cada partición de autor por los valores de categoría: CREATE TABLE Biblioteca_RANGE_HASH (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(20),

Categoria VARCHAR2(20) )

PARTITION BY RANGE (Autor)

SUBPARTITION BY HASH (Categoria)

SUBPARTITIONS 3 (

PARTITION Parte1 VALUES LESS THAN ('M') TABLESPACE Parte1_TS,

PARTITION Parte2 VALUES LESS THAN (MAXVALUE) TABLESPACE Parte2_TS );

La tabla será particionada por rango en dos particiones, usando el valor de autor para cada rango. Cada una de estas dos particiones será particionada en 3 por hash según la categoría. 3.9.4. Indexación de las particiones. Cuando creamos una tabla particionada debemos crear un índice en la tabla. El índice puede ser particionado de acuerdo al mismo rango de valores usados para particionar la tabla. En el siguiente ejemplo, se muestra cómo crear un índice basado en la categoría para la tabla Biblioteca particionada. Cada índice particionado es ubicado en los tablespaces Parte1_NDX_TS y Parte2_NDX_TS. CREATE INDEX Biblioteca_LIST_Categoria

ON Biblioteca_LIST (Categoria)

LOCAL (

PARTITION Parte1 TABLESPACE Parte1_NDX_TS,

PARTITION Parte2 TABLESPACE Parte2_NDX_TS );

Nótese la palabra clave LOCAL. En el comando CREATE INDEX no se especifican los rangos; sino que la palabra clave LOCAL le dice a Oracle que cree un índice separado por cada partición de la tabla Biblioteca_LIST. En este caso, como hay dos particiones en la tabla, el índice crea dos particiones de índices independientes (uno por cada partición de la tabla). Debido a que hay un índice por partición se dice que las particiones de índice son "locales". También podemos crear índices "globales". Un índice global puede contener valores de varias particiones de la tabla. Por ejemplo: CREATE INDEX Biblioteca_LIST_Categoria_G

ON Biblioteca_LIST (Autor)

GLOBAL;

La cláusula GLOBAL de este comando CREATE INDEX permite crear un índice no particionado (tal como se hace por el campo autor), o especificar rangos para los valores de índice que son diferentes de los rangos de

Page 67: Java y Oracle 11g

Oracle /67

las particiones. Los índices locales son más fáciles de administrar que los globales; sin embargo, los índices globales pueden realizar verificaciones de unicidad más rápido que los índices locales.

Nota. No podemos crear índices globales para particiones de hash o subparticiones.

3.9.5. Administrar tablas particionadas. Podemos usar el comando ALTER TABLE para añadir (ADD), eliminar (DROP), cambiar (EXCHANCE), mover (MOVE), renombrar (RENAME), dividir (SPLIT) y truncar (TRUNCATE) particiones. Estas opciones del comando ALTER TABLE permiten alterar la estructura de partición existente, lo cual puede ser requerido después de que una tabla particionada haya sido usada intensivamente. Por ejemplo, la distribución de valores del campo Categoría de la tabla particionada Biblioteca pueden cambiar, o el valor máximo puede incrementarse. Durante una inserción dentro de la tabla particionada, Oracle usa las definiciones de las particiones para determinar en qué partición debe insertarse el nuevo registro. Así, podemos usar una tabla particionada como si fuese una tabla normal, y confiar en Oracle para administrar la separación interna de los datos.

3.10. Clústeres (o cubos).

El "clustering" es un método de almacenar tablas que están relacionadas íntimamente y normalmente unidas en la misma área de disco. Por ejemplo, podemos tener una tabla Libro y una tabla Autor relacionadas dentro de una misma área de disco. Esta área se denomina clúster o cubo. La clave de clúster es la columna o columnas a través de las cuales las tablas son normalmente combinadas en una consulta (por ejemplo, las tablas Libro y Autor pueden tener ambas una columna idAutor). Para crear clústeres en tablas debemos ser propietarios de las tablas. El formato básico del comando CREATE CLUSTER es el siguiente: CREATE CLUSTER nombreCluster

(columna tipoDeDato [, columna tipoDeDato] . . . ) [otras opciones];

Después del nombre del clúster le sigue la convención de nombrado para definir columnas con su tipo de dato, las cuales definirán la clave del clúster. El nombre de las columnas puede corresponderse con una de las columnas de una tabla, o puede ser cualquier otro nombre válido. Por ejemplo: CREATE CLUSTER LibroAutor (Col1 INT);

Esto crea un clúster vacío. El uso de Col1 en la clave del clúster es irrelevante; nunca se volverá a usar. Sin embargo, esta definición debería casar con la clave primaria de la tabla que será añadida. A continuación creamos las tablas que serán incluidas en este clúster. CREATE TABLE Autor (

IdAutor INT PRIMARY KEY,

Nombre VARCHAR2(100) )

CLUSTER LibroAutor (idAutor);

Antes de insertar filas dentro de Autor, debemos crear un índice de clúster: CREATE INDEX LibroAutorNDX

ON CLUSTER LibroAutor;

Recalcar que la presencia de una cláusula CLUSTER aquí excluye el uso de una cláusula TABLESPACE o STORAGE. Nótese cómo esta estructura difiere del comando estándar de creación de tablas, donde al final se indica el nombre del clúster, LibroAutor, donde se almacenará la tabla, y entre paréntesis se indica la columna, idAutor, de la tabla que será almacenada en la clave del clúster, Col1. Es posible tener varias claves de clúster y tener varias columnas almacenadas en estas claves. La lista de columnas se hará corresponder por orden con la lista de claves. Ahora añadimos una segunda tabla al clúster: CREATE TABLE Libro (

IdLibro INT PRIMARY KEY,

Titulo VARCHAR2(100),

IdAutor INT REFERENCES Autor(idAutor) )

CLUSTER LibroAutor (idAutor);

Cuando estas dos tablas son almacenadas en el clúster, cada id de autor es almacenado una sola vez en la clave del clúster. Cada una de estas claves es asociada a las columnas de ambas tablas, Autor y Libro. Los datos de ambas tablas son almacenados en una única localización, casi como si el clúster fuese una gran tabla que contuviese los datos de las tablas involucradas. Una opción adicional, HASH CLUSTER, usa los valores de las columnas del clúster para determinar la ubicación física en la cual son almacenados los registros. La sintaxis para esta opción es la siguiente:

Page 68: Java y Oracle 11g

Oracle /68

CREATE CLUSTER nombreCluster

(columnatipoDeDato, . . . ) HASH IS columna;

3.11. Secuencias.

Una secuencia sirve para generar automáticamente números distintos. Son útiles para generar valores en campos que se utilizan como clave forzada (claves cuyo valor no interesa, sólo sirven para identificar los registros de una tabla). Es decir, se utilizan en los identificadores de las tablas (campos que comienzan con la palabra id), siempre y cuando no importe qué número se asigna a cada fila. Es una rutina interna de la base de datos la que realiza la función de generar un número distinto cada vez. Las secuencias se almacenan independientemente de la tabla, por lo que la misma secuencia se puede utilizar para diversas tablas. 3.11.1. Creación de secuencias. La sintaxis para crear una secuencia es la siguiente: CREATE SEQUENCE nombre_secuencia

[INCREMENT BY n]

[START WITH n]

[{MAXVALUE n|NOMAXVALUE}]

[{MINVALUE n|NOMINVALUE}]

[{CYCLE|NOCYCLE}]

[{CACHE n|NOCACHE}

Donde: INCREMENT BY, indica cuánto se incrementa la secuencia cada vez que se usa. Por defecto se incrementa de uno en uno. START WITH, indica el valor inicial de la secuencia (por defecto 1). MAXVALUE, es el máximo valor que puede tomar la secuencia. Sino se toma NOMAXVALUE, que permite llegar hasta el 1027. MINVALUE, es el mínimo valor que puede tomar la secuencia. Por defecto -1026. CYCLE, hace que la secuencia vuelva a empezar si se ha llegado al máximo valor. NOCYCLE, (valor por defecto) hace que la secuencia no vuelva a empezar si se ha llegado al máximo valor. CACHE, especifica cuántos valores de la secuencia serán almacenados en la memoria para accesos rápidos. El valor asignado debe ser menor que el valor de INCREMENT BY. NOCACHE, (valor por defecto) especifica que no se almacenen valores en la memoria.

Nota. Si el sistema falla, todos los valores almacenados en caché se perderán y no serán usados cuando se restaure el sistema. Para recuperar la secuencia de valores perdidos podemos usar el comando ALTER SEQUENCE para reiniciar el contador al valor correcto.

Un ejemplo de creación de una secuencia es el siguiente. CREATE SEQUENCE numeroPlanta

INCREMENT 100 STARTS WITH 100 MAXVALUE 2000;

3.11.2. Para ver la lista de secuencias. La vista del diccionario de datos USER_SEQUENCES muestra la lista de secuencias del usuario actual. La columna LAST_NUMBER muestra cuál será el siguiente número de secuencia disponible. 3.11.3. Uso de la secuencia. Los métodos NEXTVAL y CURRVAL se utilizan para obtener el siguiente número y el valor actual de la secuencia respectivamente. Un ejemplo de uso es el siguiente: SELECT numeroPlanta.NEXTVAL FROM DUAL;

Eso muestra en pantalla el siguiente valor de la secuencia. Realmente NEXTVAL incrementa la secuencia y devuelve el valor actual. CURRVAL devuelve el valor de la secuencia, pero sin incrementar la misma. Ambas funciones pueden ser utilizadas en:

- Una consulta SELECT que no lleve DISTINCT, ni grupos, ni sea parte de una vista, ni sea subconsulta de otro SELECT, UPDATE o DELETE. - Una subconsulta SELECT en una instrucción INSERT. - La cláusula VALUES de la instrucción INSERT. - La cláusula SET de la instrucción UPDATE.

No se puede utilizar (y siempre hay tentaciones para ello) como valor para la cláusula DEFAULT de un campo de tabla.

Page 69: Java y Oracle 11g

Oracle /69

Su uso más habitual es como apoyo al comando INSERT: INSERT INTO plantas (num, uso) VALUES (numeroPlanta.NEXTVAL, 'Suites');

3.11.4. Modificar secuencias. Se pueden modificar las secuencias, pero la modificación sólo puede afectar a los futuros valores de la secuencia, no a los ya utilizados. Se usa el comando ALTER SEQUENCE para ello: ALTER SEQUENCE secuencia

[INCREMENT BY n]

[START WITH n]

[{MAXVALUE n|NOMAXVALUE}]

[{MINVALUE n|NOMINVALUE}]

[{CYCLE|NOCYCLE}]

3.11.5. Borrar secuencias. Se pueden eliminar secuencias con el comando DROP SEQUENCE seguido del nombre de la secuencia a borrar. Por ejemplo: DROP SEQUENCE numeroPlanta;

3.12. Sinónimos.

Un sinónimo es un nombre que se asigna a un objeto cualquiera. Normalmente es un nombre menos descriptivo que el original, a fin de facilitar la escritura del nombre del objeto en diversas expresiones. 3.12.1. Creación. Se crea un sinónimo con la siguiente sintaxis: CREATE [PUBLIC] SYNONYM nombre FOR objeto;

Donde objeto es el objeto al que se referirá el sinónimo. La cláusula PUBLIC hace que el sinónimo esté disponible para cualquier usuario de la base de datos. Sólo se permite utilizar la cláusula PUBLIC si disponemos de privilegios administrativos.

Nota. Los sinónimos públicos se crean a nivel de la base de datos, por tanto no pueden existir dos sinónimos públicos con el mismo nombre en una base de datos.

Por ejemplo, si el usuario Andres tiene una tabla llamada VENTAS, y quiere que otros usuarios que tienen permiso de acceso a dicha tabla puedan utilizar un nombre más corto en vez de Andres.VENTAS, puede crear el siguiente sinónimo: CREATE PUBLIC SYNONYM VENTAS FOR Andres.VENTAS;

De esta forma, si el usuario Juan tiene permisos de consulta sobre la tabla VENTAS, podrá consultarla desde su cuenta con el siguiente comando: SELECT * FROM VENTAS;

3.12.2. Borrar sinónimos. Se puede eliminar un sinónimo con el comando DROP SYNONYM seguido del nombre del sinónimo. DROP SYNONYM sinónimo;

3.12.3. Lista de sinónimos. La vista USER_SYNONYMS permite observar la lista de sinónimos del usuario actual, y la vista ALL_SYNONYMS permite mostrar la lista completa de sinónimos de toda la base de datos.

3.13. Inserción de registros.

Para insertar un nuevo registro o tupla en una tabla se utiliza la instrucción INSERT con la siguiente sintaxis: INSERT

INTO nombre-tabla [ ( campo1 [ , campo2] … ] ]

VALUES ( valor1 [ , valor2 ] … ] ;

El orden del listado de campos puede ser arbitrario e incluso pueden omitirse los nombres de campos que admitan valores nulos o valores por defecto. Si se omite el listado de campos se considerará una referencia a todos los campos en el orden en que fueron listados en la creación de la tabla. El listado de valores debe corresponderse con el orden del listado de campos. Al crear un registro donde se omita un campo que admita un valor por defecto o un valor nulo, se insertará el valor por defecto o el valor NULL en dicho campo. En el siguiente ejemplo, insertaremos un nuevo registro de Empleado: INSERT

INTO Empleado (Nombre, Apellidos, Fecha_Nacimiento)

VALUES ('Juan', 'Pérez Pérez', '2/10/1960');

Page 70: Java y Oracle 11g

Oracle /70

Para insertar valores por defecto, también podemos forzar a que la inserción se realice con los datos por defecto establecidos para la tabla (o NULL si no tienen valores por defecto). INSERT INTO Empleado DEFAULT VALUES

3.13.1. Inserciones a partir de una consulta. Podemos realizar varias inserciones con un comando INSERT, siempre que los datos procedan de una consulta. En este caso, la sintaxis del comando es la siguiente: INSERT

INTO nombre-tabla [ ( campo1 [ , campo2] … ] ]

Consulta ;

Donde Consulta debe ser sustituida por un comando SELECT que proporcione un valor para cada campo especificado en al inserción. Por ejemplo, si tenemos dos tablas Empleado y Temp con el mismo esquema, podemos poblar la tabla Temp con los datos de aquellos empleados cuya fecha de nacimiento sea anterior a 1980. INSERT

INTO Temp (Nombre, Apellidos, Fecha_Nacimiento)

SELECT Nombre, Apellidos, Fecha_Nacimiento

FROM Empleado

WHERE EXTRACT(Year FROM Fecha_Nacimiento) < 1980;

3.13.2. Uso del indicador «APPEND» para mejorar el rendimiento de inserciones. Oracle usa un optimizador para determinar el modo más eficiente de realizar cada comando SQL. Para los comandos de inserción, Oracle intenta insertar cada nuevo registro dentro de un bloque de datos existente ya asignado a la tabla. Este plan de ejecución optimiza el uso del espacio requerido para almacenar los datos. Sin embargo, esto puede afectar al rendimiento adecuado cuando insertemos varios registros en un mismo comando (usando una consulta). Podemos corregir el plan de ejecución usando el indicador APPEND para mejora el rendimiento de inserciones con procesos largos. El indicador APPEND le dice a la base de datos que encuentre el último bloque de la tabla del cual se hizo una inserción. Los nuevos registros serán insertados a partir del siguiente bloque después del último previamente usado. Además, los datos insertados son escritos directamente a los ficheros de datos, evitando la caché de datos. Por consiguiente, durante la inserción hay mucho menos trabajo de administración del espacio para la base de datos para hacer. Por lo tanto, la inserción puede ser completada más rápido cuando se usa el indicador APPEND. Se especifica el indicador APPEND con la siguiente sintaxis: INSERT /*+ APPEND */ INTO Nombre_Tabla . . .

Debido a que los nuevos registros no reutilizan el espacio disponible ya usado por la tabla, los requerimientos para la tabla se incrementan. En general, sólo deberíamos usar el indicador APPEND cuando insertamos un gran volumen de datos dentro de tablas con un espacio reutilizado pequeño. El punto en el cual los registros añadidos son insertados se llama la "gran marca de agua" de la tabla, y el único modo de reinicializar esta marca es truncando la tabla con el comando TRUNC. Debido a que truncar una tabla elimina todos los registros de forma que ya no pueden ser recuperados, deberíamos asegurarnos de hacer una copia de seguridad de la tabla antes de realizar la operación de truncado. 3.13.3. Inserciones multitabla. Es posible también realizar varias inserciones sobre tablas distintas en un mismo comando. En estos casos podemos hacer inserciones incondicionales o podemos especificar condiciones. Para ilustrar esto, supongamos las siguientes tablas:

Tabla Temperaturas Tabla Temp Tabla TempC

Metal Celsius Fahrenheit Metal Escala Valor Metal Valor

Hierro -18 -2

Plata 12,5 54 Tabla TempF

Cobre -9,8 14 Metal Valor

Oro 14,4 57

La tabla Temperaturas almacena valores de temperatura para varios metales tanto para la escala Celsius como para escala Fahrenheit. Se pretende poblar la tabla Temp con los registros de Temperaturas, pero especificando en cada fila un único valor de temperatura y la escala utilizada (Celsius o Fahrenheit). De esta forma, la tabla Temp tendrá dos filas por cada registro de la tabla Temperaturas. El comando para poblar Temp con los datos de Temperaturas de forma incondicional es especificando la

Page 71: Java y Oracle 11g

Oracle /71

cláusula ALL con INSERT: INSERT ALL

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Celsius', Celsius)

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Fahrenheit', Fahrenheit)

SELECT Metal, Celsius, Fahrenheit FROM Temperaturas

Esta consulta le dice a Oracle que por cada registro procedente de la consulta SELECT realice dos inserciones (especificadas con cada INTO). La consulta retorna 4 registros, cuyos valores se utilizan para ambas inserciones; pero en la primera inserción se especifica el literal 'Celsius' para el campo Escala, mientras que en la segunda inserción se especifica el literal 'Fahrenheit' para el campo Escala. Como resultado, la tabla Temp se puebla con los siguientes datos:

Metal Escala Valor

Hierro Celsius -18

Plata Celsius 12,5

Cobre Celsius -9,8

Oro Celsius 14,4

Hierro Fahrenheit -2

Plata Fahrenheit 54

Cobre Fahrenheit 14

Oro Fahrenheit 57

Pero si ahora queremos poblar la tabla TempC con las temperaturas en Celsius y la tabla TempF con las temperaturas en Fahrenheit, también podemos hacerlo: INSERT ALL

INTO TempC (Metal, Valor) VALUES (Metal, Celsius)

INTO TempF (Metal, Valor) VALUES (Metal, Fahrenheit)

SELECT Metal, Celsius, Fahrenheit FROM Temperaturas

Para realizar inserciones múltiples condicionadas debemos combinar la cláusula ALL con la cláusula WHEN. Por ejemplo, al poblar la tabla Temp restringiremos los valores de temperatura a solo valores positivos (independientemente de la escala). INSERT ALL

WHEN Celsius >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Celsius', Celsius)

WHEN Fahrenheit >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Fahrenheit', Fahrenheit)

SELECT Metal, Celsius, Fahrenheit FROM Temperaturas

En este caso, como resultado, la tabla Temp se puebla con los siguientes datos:

Metal Escala Valor

Plata Celsius 12,5

Oro Celsius 14,4

Plata Fahrenheit 54

Cobre Fahrenheit 14

Oro Fahrenheit 57

Si en vez de ALL se utiliza la cláusula FIRST, sólo se insertará como máximo una fila por registro a través de la inserción que primero cumpla la condición de su respectivo WHEN. De esta forma, el comando siguiente: INSERT FIRST

WHEN Celsius >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Celsius', Celsius)

WHEN Fahrenheit >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Fahrenheit', Fahrenheit)

SELECT Metal, Celsius, Fahrenheit FROM Temperaturas

Produce como resultado:

Metal Escala Valor

Plata Celsius 12,5

Oro Celsius 14,4

Cobre Fahrenheit 14

Aunque las filas (Plata, Fahrenheit, 54) y (Oro, Fahrenheit, 57) cumplen la condición de inserción para el segundo

Page 72: Java y Oracle 11g

Oracle /72

WHEN, los registros de los cuales proceden ya produjeron una inserción a través del primer WHEN, y por ello no son enviados a la tabla Temp. Con INSERT FIRST podemos subordinar varios INTO a una misma condición: INSERT FIRST

WHEN Celsius >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Celsius', Celsius)

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Celsius', -Celsius)

WHEN Fahrenheit >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Fahrenheit', Fahrenheit)

SELECT Metal, Celsius, Fahrenheit FROM Temperaturas

En este ejemplo previo, si un registro de Temperaturas cumple con la primera condición (Celsius >= 0) se insertarán dos registros en la tabla Temp: uno con el valor positivo, y otro con el valor negativo. Por último, podemos también especificar una cláusula ELSE para realizar inserciones en otra tabla si un registro de la consulta no cumple con ninguna condición de los WHEN. Como ejemplo, supongamos la existencia de una tercera tabla, Temperaturas2, con el mismo esquema que la tabla Temperaturas. El siguiente comando: INSERT ALL

WHEN Celsius >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Celsius', Celsius)

WHEN Fahrenheit >= 0 THEN

INTO Temp (Metal, Escala, Valor) VALUES (Metal, 'Fahrenheit', Fahrenheit)

ELSE

INTO Temperaturas2

SELECT Metal, Celsius, Fahrenheit FROM Temperaturas

Produce como resultado que la tabla Temperaturas2 se pueble con los registros de Temperaturas que no provocaron una inserción en Temp:

Metal Celsius Fahrenheit

Hierro -18 -2

3.14. Actualización de registros.

Para modificar los valores en un registro se utiliza la instrucción UPDATE con la siguiente sintaxis: UPDATE nombre-tabla

SET campo = expresión-escalar

[ , campo = expresión_escalar ] . . .

[ WHERE condición ]

Todos los registros que cumplan la condición serán actualizados de acuerdo con las asignaciones. Si no se especifica una condición todos los registros de la tabla serán modificados. En el siguiente ejemplo modificaremos un solo registro. En este caso cambiaremos el nombre y apellidos del empleado con ID 7: UPDATE Empleado

SET Nombre = 'José Luís' ,Apellidos = 'Serantes García'

WHEREID =7

3.15. Eliminación de registros.

Para eliminar registros se utiliza la instrucción DELETE con la siguiente sintaxis: DELETE

FROM nombre_tabla

[ WHERE condición ] ;

Si se omite la condición se borrarán todos los registros de la tabla indicada. En el siguiente ejemplo se elimina un solo registro. En este caso, el empleado de ID 10: DELETE

FROM Empleado WHERE ID = 10

Para eliminar todos los registros de una tabla se puede hacer de dos maneras: DELETE FROM Empleado;

TRUNCATE TABLE Empleado;

Siendo la instrucción TRUNCATE más rápida.

Page 73: Java y Oracle 11g

Oracle /73

3.16. Combinar registros con MERGE.

Oracle proporciona la instrucción MERGE para actualizar (UPDATE) o insertar (INSERT) registros en una misma operación dependiendo de alguna condición. Los datos para actualizar o insertar deben provenir de una tabla o consulta diferente a la tabla que se quiere actualizar. MERGE es una instrucción determinista, esto es, no permite actualizar el mismo registro en la tabla destino varias veces. Como ejemplo, supongamos las siguientes tablas con datos insertados:

ID Nombre ID Nombre Saldo

Tabla "Persona" 1 Juan Pérez Tabla "Cliente" 2 María López 10000

2 María López 4 José Pérez 90000

3 Ramón Llull 6 Javier Llull 15000

4 José Pérez

La siguiente instrucción modifica la tabla Cliente según los registros de la tabla Persona: MERGE INTO Cliente C

USING (SELECT ID, Nombre FROM Persona) P ON (C.ID = P.ID)

WHEN MATCHED THEN UPDATE SET C.Saldo = C.Saldo + 200

DELETE WHERE (C.Saldo > 80000)

WHEN NOT MATCHED THEN INSERT (C.ID, C.Nombre, C.Saldo)

VALUES (P.ID, P.Nombre, 100);

Realiza inserciones, actualizaciones y borrados de registros en la tabla "Cliente" según las siguientes condiciones:

- Se incrementa en 200 unidades el saldo de los registros de Cliente si existe un registro de Persona con el mismo ID. - Se eliminan los registros de Cliente con un saldo superior a 80000 unidades, siempre que sea uno de los registros que se iba a actualizar. Es decir, el ID del registro a eliminar debe corresponderse con un registro de la tabla Persona. - En la tabal Cliente se insertan aquellos registros de la tabla Persona cuyos ID's todavía no existen en la tabla Cliente, y se les asigna un saldo inicial de 100 unidades.

Como resultado de esta instrucción, el contenido de la tabla Cliente será el siguiente:

ID Nombre Saldo

Tabla "Cliente" 1 Juan Pérez 100

modificada 2 María López 10200

3 Ramón Llull 100

6 Javier Llull 15000

4. Consultas de selección

Las consultas de selección se utilizan para indicar al motor de base de datos que devuelva información de una o varias tablas; esta información es devuelta en forma de un conjunto de registros que, en un programa java, se pueden manipular mediante un objeto ResultSet. Este capítulo se ilustrará con ejemplos de consultas basadas en la base de datos representada en la siguiente figura:

Page 74: Java y Oracle 11g

Oracle /74

4.1. Consultas básicas.

La sintaxis básica de una consulta de selección es la siguiente: SELECT campos FROM Tabla;

En donde campos es la lista de campos o columnas que se desean recuperar y tabla es el origen de los mismos. Por ejemplo, la consulta SELECT nombre, apellidos FROM Alumno;

devuelve un conjunto de registros con dos columnas: los valores de nombre y apellidos de la tabla Alumno. El resultado podría ser algo como lo siguiente:

NOMBRE APELLIDOS

----------------- --------------------------

Juan Martínez Pan

Marian López Sobrado

Por regla general, las columnas del resultado son asociadas con un nombre igual al de la expresión especificada en el comando SELECT.

4.2. Alias.

En determinadas circunstancias es necesario asignar un nombre concreto a alguna columna de un conjunto devuelto; otras veces se hace por simple capricho o por otras circunstancias. Para resolver esto se dispone de la palabra reservada AS, que se encarga de asignar el nombre concreto a la columna deseada. Como ejemplo, podemos hacer una consulta que retorne una columna con los apellidos de alumnos, pero en lugar de llamarse apellidos (igual que el campo devuelto) se llame Alumno. En este caso procederíamos de la siguiente forma: SELECT apellidos AS Alumno FROM Alumno; -- o bien:

SELECT apellidos Alumno FROM Alumno; -- no es necesaria la palabra AS

Si queremos asignar un alias que incluya espacios en blanco o caracteres especiales, debemos escribir el alias entre comillas dobles: SELECT apellidos AS "Apellidos del alumno" FROM Alumno;

4.3. Ordenar los registros.

Adicionalmente se puede especificar el orden en que se desean recuperar los registros de las tablas mediante la cláusula ORDER BY. Ejemplo: SELECT nif, nombre, apellidos FROM Alumno ORDER BY apellidos;

Esta consulta devuelve los campos nif, nombre, y apellidos de la tabla Alumno ordenados por el campo apellidos. Se pueden ordenar los registros por más de un campo, como por ejemplo: SELECT nif, nombre, apellidos FROM Cliente ORDER BY apellidos, nombre;

Esta consulta ordena los registros primero por apellidos, y cuando se repita este valor en varios registros, en cada grupo se ordena por nombre. Incluso se puede especificar el orden de los registros: ascendente mediante la cláusula ASC (se toma este valor por defecto) o descendente (DESC) SELECT nif, nombre, apellidos FROM Cliente ORDER BY apellidos DESC , nombre ASC;

4.3.1. Sintaxis para ordenar mediante el ordinal y alias de las columnas. Oracle también permite referenciar una columna del SELECT mediante su ordinal. Por ejemplo, la siguiente consulta: SELECT nif, nombre, apellidos FROM Cliente ORDER BY 3;

MATRICULA idMatricula nif año

NOTA idNota valor nif idModulo

MODULO idModulo nombre

FALTA idFalta fecha idMatricula idModulo

ALUMNO nif nombre apellidos localidad

n

1 1

1

n

n

n

n

1

1

ESQUEMA DE LA BASE DE DATOS USADA PARA LOS EJEMPLOS

Page 75: Java y Oracle 11g

Oracle /75

Ordena los resultados por la columna apellidos (la tercera en las expresiones del SELECT). Así mismo, Oracle permite utilizar los alias asignados a las columnas para ordenar. Por ejemplo, la siguiente consulta: SELECT nif, nombre, localidad AS Ciudad FROM Cliente ORDER BY Ciudad;

Ordena los resultados por la columna localidad. 4.3.2. Ordenar con expresiones que no pertenecen al resultado. La cláusula ORDER BY asocia una expresión a cada registro resultante para poder ordenarlo. Normalmente esta expresión está asociada con una columna dela fila resultante; sin embargo, es posible usar expresiones que no se corresponden exactamente con los valores de las columnas resultantes de la consulta. Por ejemplo, podemos realizar una consulta sobre la tabla FALTA y ordenar los resultados por mes en vez de por la fecha completa. SELECT * FROM FALTA ORDER BY EXTRACT(MONTH FROM fecha)

Aún más, la expresión de ordenación no tiene por qué corresponderse con un dato de salida. Por ejemplo, podemos hacer una consulta para obtener los registros de NOTA ordenados por aprobados y por suspensos. Un aprobado implica que el campo valor sea mayor o igual que 5, y un suspenso implica que valor sea menor que 5. La consulta podría ser como sigue: SELECT * FROM NOTA ORDER BY CASE WHEN valor>=5 THEN 'aprobado' ELSE 'suspenso' END, valor;

Para esta consulta las filas resultantes se ordenarán por el orden alfabético de las palabras 'aprobado' y 'suspenso' y después por el valor de la nota, formándose así dos grupos: primero los aprobados y después los suspensos. Por último, se puede ordenar también por una expresión que no aparezca en la salida. Por ejemplo, consideremos la siguiente consulta: SELECT nombre, apellidos FROM ALUMNO ORDER BY localidad;

En esta consulta se ordenan los alumnos por el campo localidad de la tabla base ALUMNO, pero este dato no aparece en las filas de salida. Esto es sólo posible si existe una correspondencia 1 a 1 entre las filas de salida y las filas de la tabla base.

4.4. Consultas con predicado.

Un predicado es una palabra clave que modifica el comportamiento de una consulta. El predicado se incluye entre la cláusula SELECT y el primer nombre del campo a recuperar. Los posibles predicados son:

ALL Devuelve todos los campos de la tabla.

DISTINCT Omite los registros cuyos campos seleccionados coincidan totalmente

4.4.1. Predicado «ALL». Si no se incluye ninguno de los predicados se asume ALL. El Motor de base de datos selecciona todos los registros que cumplen las condiciones de la instrucción SQL. El predicado «ALL *» obliga al motor de la base de datos a analizar la estructura de la tabla para averiguar los campos que contiene, siendo por tanto más rápido indicar el listado de campos deseados. SELECT ALL * FROM Alumno; -- es equivalente a:

SELECT * FROM Alumno;

4.4.2. Predicado «DISTINCT». El predicado DISTINCT omite los registros que contienen datos duplicados en los campos seleccionados. Para que se incluya un registro en la salida de la instrucción SELECT el valor conjunto de todos los campos incluidos debe ser único. Por ejemplo, varios alumnos listados en la tabla Alumno pueden tener los mismos apellidos. Si dos registros contienen López en el campo apellidos, la siguiente instrucción SQL devuelve un único registro por apellido: SELECT DISTINCT apellidos FROM Alumno;

Debe quedar claro que el predicado DISTINCT se aplica sobre los registros de salida del SELECT y no sobre los registros de entrada del FROM.

4.5. Recuperación de valores calculados.

Es posible obtener valores calculados a partir de los valores originales de la tabla. Por ejemplo, en la siguiente consulta SELECT idAlumno, idModulo, valor + 1FROM Nota;

Se obtiene un listado de todas las notas pero incrementadas en una unidad. Si el valor original es 7 en el resultado se mostrará un 8. Podemos operar también con cadenas de caracteres. En el siguiente ejemplo obtenemos el nombre y

Page 76: Java y Oracle 11g

Oracle /76

apellidos de los alumnos con el formato: "apellidos, nombre". SELECT 'Apellidos y nombre:' , apellidos ||', '|| nombre AS "Nombre completo" FROM Alumno;

En la consulta, como primer campo insertamos un valor literal de tipo cadena de caracteres, y en el segundo campo se utiliza el operador "||" para concatenar los valores de los campos apellidos y nombre de la tabla Alumno. Un resultado posible de esta consulta es:

APELLIDOS Y NOMBRE: Nombre completo

------------------------------ ----------------------------------

Apellidos y nombre: Martínez Pan, Juan

Apellidos y nombre: López Sobrado, Marian

4.6. La cláusula «WHERE».

La cláusula WHERE puede usarse para determinar qué registros de las tablas enumeradas en la cláusula FROM aparecerán en los resultados de la instrucción SELECT. Después de escribir esta cláusula se deben especificar las condiciones. Si no se emplea esta cláusula, la consulta devolverá todas las filas de la tabla. WHERE es opcional, pero cuando aparece debe ir a continuación de FROM. Algunos ejemplos son: -- Matrículas del año 2009:

SELECT idMatricula, nif FROM Matricula WHERE año = 2009;

-- Módulo y nota donde ha aprobado el alumno de nif '11111111A':

SELECT idModulo, valor FROM Nota WHERE nif = '1111111A' AND valor >= 5;

4.7. Consultas que incluyen nulos

Si un registro tiene un nulo (valor NULL) en un campo, significará que se desconoce el valor de ese campo en el registro en cuestión. Debemos tener en cuenta que las expresiones escalares de cálculo en las cuales uno de los operandos es nulo dan nulo como resultado, y las expresiones escalares de comparación en las cuales uno de los comparandos es nulo dan como resultado el valor lógico desconocido. En SQL, los nulos provocan más problemas de los que resuelven y conviene evitarlos. Supongamos que, para el siguiente ejemplo, existen registros de alumnos con valor nulo en el nombre y apellidos. Podemos obtener el nombre completo (nombre + apellidos) como una única cadena de caracteres de alumnos: SELECT nombre ||''|| apellidos

FROM Alumno

WHERE nombre IS NOT NULL AND apellidos IS NOT NULL

En esta consulta se concatenan el valor de nombre y el valor de apellidos, excluyendo las filas donde alguno de dichos valores sea nulo.

5. Criterios de selección

En el capítulo anterior se vio la forma de recuperar los registros de las tablas, pero las formas empleadas devolvían todos los registros de las tablas. A lo largo de este capítulo se estudiarán las posibilidades de filtrar los registros con el fin de recuperar solamente aquellos que cumplan unas condiciones preestablecidas. Antes de comenzar el desarrollo de este capítulo hay que recalcar tres detalles de vital importancia:

- El primero de ellos es que cada vez que se desee establecer una condición referida a un campo de texto la condición de búsqueda debe ir encerrada entre comillas simples. - La segunda es que no es posible establecer condiciones de búsqueda en los campos binarios grandes (tipos de imagen o memorando dentro de campos tipo LOB). - La tercera y última hace referencia a las fechas. En Oracle se deben escribir entre comillas separando el día, mes y año con guiones o barras inclinadas. El formato válido de la fecha depende del gestor de base de datos; en Oracle podemos usar los formatos'20-03-2009' y '20/03/2009'.

5.1. Operadores lógicos.

Los operadores lógicos soportados por SQL son: AND, OR, IS y NOT. A excepción del último (que es unario) todos poseen la siguiente sintaxis: expresión1 operador expresión2

En donde expresión1 y expresión2 son las condiciones a evaluar; el resultado de la operación varía en función del operador lógico. La tabla adjunta muestra los diferentes posibles resultados:

Page 77: Java y Oracle 11g

Oracle /77

expresión1 Operador expresión2 Resultado

Verdad AND Falso Falso Verdad AND Verdad Verdad

Falso AND Verdad Falso Falso AND Falso Falso

Verdad OR Falso Verdad Verdad OR Verdad Verdad

Falso OR Verdad Verdad Falso OR Falso Falso

Si a cualquiera de las anteriores condiciones le anteponemos el operador NOT el resultado de la operación será el contrario al devuelto sin el operador NOT. El operador denominado IS se emplea para comparar dos variables de tipo objeto: Objeto1 IS Objeto2. Este operador devuelve verdad si los dos objetos son iguales. (Véase el modelo objeto-relacional de Oracle.) Algunos ejemplos de uso de operadores lógicos son los siguientes: -- Matrículas realizadas entre 1991 y 1999 inclusive:

SELECT * FROM Matricula WHERE año> 1990 AND año<2000;

-- Notas del alumno de nif '11111111A' con valores entre 5 y 8 inclusive:

SELECT * FROM Nota WHERE (valor>= 5 AND valor <9) OR nif= '11111111A';

5.2. Intervalos de valores.

Para indicar que deseamos recuperar los registros según el intervalo de valores de un campo emplearemos el operador BETWEEN, cuya sintaxis es: campo [NOT] BETWEEN valor1 AND valor2

En este caso la consulta devolvería los registros que contengan en campo un valor incluido en el intervalo valor1 y valor2 (ambos inclusive). Si anteponemos la condición NOT devolverá aquellos valores no incluidos en el intervalo. La siguiente consulta devuelva las matriculas realizadas entre el año 2008 y 2010 inclusive: SELECT * FROM Matricula WHERE año BETWEEN 2008 AND 2010;

5.3. El operador «Like».

Se utiliza el operador LIKE para comparar una expresión de cadena con un patrón en una expresión SQL. Su sintaxis de uso es: expresión LIKE modelo

En donde expresión es una cadena de texto, y modelo es un patrón contra el que se compara expresión. Se puede utilizar el operador LIKE para encontrar valores en los campos que coincidan con el modelo especificado. El modelo puede especificar un valor completo ('Ana María'), o se pueden utilizar caracteres comodín (LIKE 'An%') como los siguientes:

% representa cualquier cadena de texto de cero o más caracteres de cualquier longitud. _ representa un carácter.

El operador LIKE se puede utilizar en una expresión para comparar un valor de un campo con una expresión de cadena. Por ejemplo, si introduce LIKE 'C%' en una consulta SQL, la consulta devuelve todos los valores de campo que comiencen por la letra C. En una consulta con parámetros se puede hacer que el usuario escriba el modelo que se va a utilizar. En la tabla siguiente se muestra cómo utilizar el operador LIKE para comprobar expresiones con diferentes modelos.

Tipo de coincidencia Modelo Planteado Coincide No coincide

Varios caracteres a%a 'aa', 'aBa', 'aBBBa' 'aBC' Varios caracteres ab% 'abcdefg', 'abc' 'cab', 'aab' Un solo carácter a_a 'aaa', 'a3a', 'aBa' 'aBBBa'

Además Oracle proporciona la función REGEXP_LIKE(), la cual utiliza expresiones regulares que amplían las capacidades del operador LIKE para construir patrones. Un ejemplo de uso de esta función es el siguiente, donde se obtienen los alumnos cuyos apellidos comienzan por las letras M o B: SELECT * FROM Alumno WHERE REGEXP_LIKE(apellidos, '[MB]')

5.4. El operador «In».

El operador IN permite retornar aquellos registros en donde una expresión coincida con alguno de los valores de una lista o una subconsulta. Su sintaxis de uso es:

Page 78: Java y Oracle 11g

Oracle /78

expresión [NOT] IN (valor1, valor2, ...) ;

expresión [NOT] IN ( subconsulta ) ;

Donde los valores incluidos en la lista o los valores que retorna la subconsulta deben ser del mismo tipo y estructura que expresión. Por ejemplo, la siguiente consulta retorna los alumnos residentes en Madrid, Barcelona o Sevilla. SELECT * FROM Alumno WHERE localidad IN ('Madrid', 'Barcelona', 'Sevilla');

Pero también podemos comparar con listas de elementos compuestos. La siguiente consulta devuelve el nif de los alumnos cuyo par nota y módulo corresponde con alguno de la lista: SELECT * FROM Nota WHERE (valor, idModulo) IN ( (5, 1) , (6, 3) , (5, 2) );

6. Consultas sobre más de una tabla

La posibilidad de combinar datos de dos o más tablas en una de las características más poderosas de los sistemas relacionales. Esta característica se resuelve en el modelo ANSI mediante un producto cartesiano o mediante la operación de "Join", que es más restrictiva que el producto cartesiano. En SQL, para realizar un producto cartesiano entre tablas basta con listar dichas tablas en la cláusula FROM separadas por comas. En el siguiente ejemplo se obtienen todas las combinaciones posibles de alumnos y matrículas donde el nif de alumno sea diferente del nif de matrícula: SELECT Alumno.nif, Matricula.nif

FROM Alumno, Matricula

WHERE Alumno.nif <> Matricula.nif;

Nótese que el nombre de la tabla antecede al nombre del campo separado por un punto (.). Esto es así porque estamos utilizando dos campos (nif) de tablas distintas que poseen el mismo nombre identificador. El motor de base de datos resuelve esta consulta realizando un producto cartesiano entre los registros de ambas tablas. Sólo aquellos registros combinados que cumplan la condición serán incluidos en el resultado.

6.1. Reunión de una tabla consigo misma.

Cuando nos pidan una consulta donde deban combinarse los registros de una tabla entre sí, la solución es considerar que existen dos copias independientes de dicha tabla. Para referenciar cada copia de la misma tabla usaremos un alias diferente para cada una. Por ejemplo, la siguiente consulta empareja los nif's de aquellos alumnos que viven en la misma localidad: SELECT A1.nif , A2.nif

FROM Alumno A1 , Alumno A2

WHERE A1.localidad = A2.localidad;

Esta consulta implica la reunión de la tabla Alumno consigo misma (con base en igualdad de localidades). En la cláusula FROM asignamos dos alias a dicha tabla: A1 y A2. En el resto de la consulta trabajamos con la suposición de que A1 y A2 son tablas diferentes, y operamos como en el caso de reunión de dos tablas. La consulta tal como está establecida empareja el nif de un alumno con su mismo nif, lo cual no tiene mucho sentido. Para evitar los emparejamientos de un alumno consigo mismo, debemos introducir una condición adicional: SELECT A1.nif , A2.nif

FROM Alumno A1 , Alumno A2

WHERE A1.localidad = A2.localidad AND A1.nif <> A2.nif;

6.2. Consultas de unión internas.

SQL incorpora la cláusula JOIN para optimizar las operaciones de combinación entre tablas que tengan concordancia de valores en un campo común. Su sintaxis es: SELECT campos FROM tb1 JOIN tb2 ON tb1.campo1 comp tb2.campo2 ;

SELECT campos FROM tb1 JOIN tb2 USING (campoComún) ;

En donde: tb1 y tb2 son los nombres de las tablas desde las que se combinan los registros; campo1 y campo2 son los nombres de los campos que se combinan; comp es un operador relacional (normalmente se utiliza =); y campoComún es un campo común a ambas tablas con el mismo nombre y tipos de datos compatibles. Existen cuatro variantes de la cláusula JOIN:

INNER JOIN, crea una combinación por equivalencia, conocida también como unión interna. Combina los registros de dos tablas siempre que haya concordancia de valores en campos comunes a ambas tablas. (Esta variante es soportada desde la versión 9i, y en realidad es equivalente a la cláusula JOIN sola.) Por ejemplo, la siguiente consulta obtiene las notas de cada alumno en cada módulo:

SELECT A.nif, N.idModulo, N.valor

Page 79: Java y Oracle 11g

Oracle /79

FROM Alumno A INNER JOIN Nota N ON A.nif = N.nif;

Esta misma consulta en formato ANSI sería así: SELECT A.nif, N.idModulo, N.valor

FROM Alumno A, Nota N

WHERE A.nif = N.nif;

LEFT OUTER JOIN o LEFT JOIN, incluye todos los registros de la primera tabla y los combina con aquellos de la segunda tabla que concuerden en un campo común. En aquellos registros de la primera tabla que no combinen con la segunda tabla, se pone a nulo los campos correspondientes a la segunda tabla. Por ejemplo, la siguiente consulta obtiene las notas de cada alumno en cada módulo, y si un alumno no tiene nota se crea un registro con valor nulo en las columnas idModulo y valor:

SELECT A.nif, N.idModulo, N.valor

FROM Alumno A LEFT JOIN Nota N ON A.nif = N.nif;

Esta misma consulta en formato ANSI sería así: SELECT A.nif, N.idModulo, N.valor

FROM Alumno A, Nota N

WHERE A.nif = N.nif(+);

RIGHT OUTER JOIN o RIGHT JOIN, incluye todos los registros de la segunda tabla y los combina con aquellos de la primera tabla que concuerden en un campo común. En aquellos registros de la segunda que no combinen con la primera tabla, se pone a nulo los campos correspondientes a la primera tabla. Por ejemplo, la siguiente consulta obtiene las notas de cada módulo para cada alumno, y si módulo no tiene nota se crea un registro con valor nulo en las columnas nif y valor:

SELECT M.idModulo, N.nif, N.valor

FROM Nota N RIGHT JOIN Modulo M ON N.idModulo = M.idModulo;

Esta misma consulta en formato ANSI sería así: SELECT M.idModulo, N.nif, N.valor

FROM Nota N, Modulo M

WHERE N.idModulo(+) = M.idModulo;

FULL OUTER JOIN ó FULL JOIN, incluye todos los registros ambas tablas. Por ejemplo, la siguiente consulta combina los registros de nota con los registros de falta por el módulo en común. Fuerza que aparezcan todos los registros de ambas tablas en el resultado:

SELECT N.idModulo, N.nif, N.valor, F.fecha

FROM Nota N FULL JOIN FaltaF ON N.idModulo = F.idModulo;

Para Oracle 9i no existe una sintaxis alternativa. Este tipo de consultas se resolvía creando dos consultas de selección externa, una por la izquierda y otra por la derecha, y aplicando una unión.

Si se intenta combinar campos que contengan datos largos (tipo LOB), se produce un error. Se pueden combinar dos campos numéricos cualesquiera, incluso si son de diferente tipo de datos. JOIN/USING permite establecer relaciones indicando qué campo (o campos) común a las dos tablas hay que utilizar: SELECT nif, A.nombre, A.apellidos, N.valor FROM Alumno A JOIN NotaN USING (nif);

Nota. Cuando se utiliza JOIN/USING sobre un campo común, en la combinación resultante se genera una única columna para el campo común. Esto implica que no puede referenciarse dicho campo a través del nombre de su tabla. Por ejemplo, sería errónea la siguiente consulta: SELECT ALUMNO.nif FROM ALUMNO JOIN NOTA USING (nif);

JOIN/ON permite establecer relaciones cuya condición se establece manualmente, lo que permite realizar asociaciones más complejas o bien asociaciones cuyos campos en las tablas no tienen el mismo nombre: SELECT * FROM Alumno JOIN Nota ON (Alumno.nif=Nota.nif AND valor >= 5);

Nota. Cuando se utiliza JOIN/ON sobre la igualdad de un campo común, en la combinación resultante se genera una columna para el campo común por cada tabla. Esto implica que debe referenciarse dicho campo a través del nombre de su tabla. Por ejemplo, sería errónea la siguiente consulta: SELECT nif FROM ALUMNO JOIN NOTA ON ALUMNO.nif = NOTA.nif;

También se pueden combinar más de dos tablas usando varios JOIN. Por ejemplo, La siguiente consulta obtiene los apellidos de alumno, el nombre de un módulo y la nota correspondiente: SELECT A.apellidos, M.nombre, N.valor

Page 80: Java y Oracle 11g

Oracle /80

FROM (Alumno A JOIN Nota N ON A.nif = N.nota) JOIN Modulo M ON N.idModulo = M.idModulo;

Otra sintaxis permite obtener las faltas de los alumnos en cada módulo en el año 2009: SELECT N.nif

FROM Matricula M JOIN (Falta F JOIN Modulo M ON F.idModulo=M.idModulo) ON M.idMatricula=F.idMatricula;

Por último, dos reglas de anidamiento de JOIN: • Un LEFT JOIN o un RIGHT JOIN pueden anidarse dentro de un INNER JOIN. • Un INNER JOIN no puede anidarse dentro de un LEFT JOIN o un RIGHT JOIN.

6.3. Consultas con operaciones de conjuntos.

Hay tres tipos de operaciones con conjuntos: UNION, MINUS, e INTERSECT. 6.3.1. Consultas de unión externa. Se utiliza la operación UNION para crear una consulta de unión, combinando los resultados de dos o más consultas con el mismo esquema. Su sintaxis es: consulta1 UNION [ALL] consulta2 [UNION [ALL] . . . consultaN]

En donde consulta1...consultaN son instrucciones de tipo SELECT. Se pueden combinar los resultados de dos o más consultas SELECT, en cualquier orden, en una única operación UNION siempre y cuando posean el mismo esquema. El ejemplo siguiente combina los alumnos con las matrículas para obtener los nif's: SELECT nif FROM Alumno UNION SELECT nif FROM Matricula ;

Si no se indica lo contrario, no se devuelven registros duplicados cuando se utiliza la operación UNION, no obstante puede incluirse el predicado ALL para asegurar que se devuelvan todos los registros repetidos. Esto hace que la consulta se ejecute más rápidamente. Todas las consultas en una operación UNION deben pedir el mismo número de campos, no obstante los campos no tienen porqué tener el mismo tamaño o el mismo tipo de datos. Se puede utilizar una cláusula GROUP BY y HAVING en cada consulta para agrupar los datos devueltos. A partir de Oracle 10i se puede utilizar una cláusula ORDER BY al final de las consultas de la unión. Anteriormente si queríamos establecer una ordenación al resultado de la unión teníamos que subordinarla a un SELECT

superior. Por ejemplo, la unión ordenada de los nif's se obtiene con: SELECT nif

FROM (SELECT nif FROM Alumno UNION SELECT nif FROM Matricula)

ORDER BY nif;

O bien: SELECT nif FROM Alumno UNION SELECT nif FROM Matricula

ORDER BY nif;

6.3.2. Consultas de diferencia: La operación MINUS devuelve la diferencia (resta) de dos o más conjuntos de resultados. Por ejemplo, la siguiente consulta obtiene los nif's de alumnos todavía no matriculados. SELECT nif FROM Alumno

MINUS

SELECT nif FROM Matricula;

6.3.3. Consultas de intersección: La operación INTERSECT devuelve la intersección entre dos o más conjuntos de resultados en uno. Por ejemplo, la siguiente consulta obtiene los nif's de alumnos de 'Madrid' matriculados: SELECT nif FROM Alumno WHERE localidad='Madrid'

INTERSECT

SELECT nif FROM Matricula;

7. Agrupaciones

7.1. Funciones de agregado.

SQL incluye un conjunto de funciones estándar predefinidas que nos permiten obtener valores resumen sobre los resultados de una consulta. Se denominan funciones de agregado o de grupo. 7.1.1. Funciones de grupo estándar. Las funciones de agregado estándar en SQL son:

▪ AVG. Calcula la media aritmética de un conjunto de valores contenidos en los registros especificados de una consulta. Su sintaxis es la siguiente

AVG(expr)

Page 81: Java y Oracle 11g

Oracle /81

En donde expr representa el campo que contiene los datos numéricos para los que se desea calcular la media o una expresión que realiza un cálculo con los datos de un registro. La media calculada por AVG es la media aritmética (la suma de los valores dividido por el número de valores). Para realizar el cálculo resumen, la función AVG no incluye a ningún registro con la expresión a NULL. Por ejemplo, la siguiente consulta obtiene la media de las notas del alumno de nif '11111111A':

SELECT AVG(valor) AS "Nota media" FROM Nota WHERE nif='11111111A';

▪ COUNT. Calcula el número de registros devueltos por una consulta. Su sintaxis es la siguiente COUNT(expr)

En donde expr puede ser el símbolo *, el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o definida por el usuario, pero no otras de las funciones de agregado de SQL). Puede contar cualquier tipo de datos, incluso texto. Aunque expr puede realizar un cálculo sobre un campo, COUNT simplemente cuenta el número de registros sin tener en cuenta qué valores se almacenan en los registros. La función COUNT no cuenta los registros cuya expresión evalúa a NULL a menos que expr sea el carácter comodín asterisco (*). En ese sentido, COUNT(*) es considerablemente más eficiente que COUNT(expresión). La siguiente consulta calcula el número de alumnos registrados:

SELECT COUNT(*) AS Total FROM Alumno;

Si el campo localidad admite valores nulos, la siguiente consulta calcula el número de alumnos registrados que tengan asignada alguna localidad:

SELECT COUNT(localidad) AS Total FROM Alumno;

También se puede especificar la cláusula DISTICNT en la expresión para no contar aquellos registros con valores repetidos en dicha expresión. Por ejemplo, la siguiente consulta cuenta el número de alumnos que viven en localidades distintas:

SELECT COUNT( DISTINCT localidad) AS Total FROM Alumno;

▪ MAX, MIN. Devuelven el mínimo o el máximo de un conjunto de valores obtenidos de una expresión de una consulta. Su sintaxis es:

MIN(expr)

MAX(expr)

En donde expr es el campo o expresión sobre el que se desea realizar el cálculo. Expr puede incluir el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o definida por el usuario, pero no otras de las funciones de agregado de SQL). La siguiente consulta obtiene la nota mínima y máxima del alumno de nif '11111111A':

SELECT MIN(valor) AS 'Nota mínima', MAX(valor) AS 'Nota máxima'

FROM Nota

WHERE nif = '11111111A';

▪ SUM. Devuelve la suma del conjunto de valores obtenidos de una expresión de una consulta. Su sintaxis es:

SUM(expr)

En donde expr representa el nombre de un campo que contiene los datos que desean sumarse o una expresión que realiza un cálculo sobre un registro. Los operandos de expr pueden incluir el nombre de un campo de una tabla, una constante o una función (la cual puede ser intrínseca o definida por el usuario pero no otras de las funciones de agregado de SQL). La siguiente consulta obtiene la suma de las notas del alumno de nif '11111111A':

SELECT SUM(valor) AS 'Suma de notas' FROM Nota WHERE nif = '11111111A';

7.1.2. Funciones de agregado propias de Oracle. Además, Oracle añade otro conjunto propio de funciones de grupo:

▪ CORR(expr1, expr2). Calcula coeficientes de correlaciones de un conjunto de pares de números. La función CORR_K soporta correlaciones no parametrizadas, y la función CORR_S soporta correlaciones de fila. ▪ COVAR_POP(expr1, expr2). Calcula la covarianza demográfica de un conjunto de pares de valores. ▪ COVAR_SAMP(expr1, expr2). Calcula la covariancia típica de un conjunto de pares de valores. ▪ MEDIAN(expr). Retorna el valor medio de un grupo de valores ignorando los nulos. ▪ STDDEV(expr).Calcula la desviación estándar de todos los valores. ▪ STDDEV_POP(expr). Calcula de desviación demográfica estándar. ▪ STDDEV_SAMP(expr). Calcula la desviación típica.

Page 82: Java y Oracle 11g

Oracle /82

▪ VAR_POP(expr). Calcula la variancia demográfica. ▪ VAR_SAMP(expr). Calcula la variancia típica. ▪ VARIANCE(expr). Calcula la variancia de todos los valores del grupo. ▪ WIDTH_BUCKET(expr,min,max,num). Permite crear histogramas por igualdad de tamaño.

7.1.3. Cómo afectan los nulos en valores de funciones de grupo. Las funciones de grupo tratan los valores NULL de forma diferente a cómo lo hacen las funciones de valor simple. Las funciones de grupo ignoran los valores nulos y calculan el resultado sin tenerlos en cuenta. Sin embargo, una función de agregado que realiza cálculos sólo sobre valores nulos siempre retorna el valor NULL (excepto COUNT). Para analizar como afectan los nulos a las funciones de grupo, tomemos AVG como ejemplo. Supongamos que tenemos una lista de 100 amigos y sus edades. Si cogemos 20 al azar y calculamos el promedio de sus edades, nos encontraremos con un valor cercano a si calculamos el promedio de otra lista diferente de 20 amigos elegidos al azar o el promedio sobre los 100 amigos. Esto es así porque AVG es bastante insensible a la falta de registros, incluso si representan un alto porcentaje del número total de registros disponibles. Esta relativa insensibilidad de AVG ante la falta de datos puede ser contrastada con la función SUM. Si calculamos la suma de edades sobre 20 amigos obtendremos valores muy diferentes si hacemos el cálculo sobre otros grupos. Si ahora, de los 100 amigos, sólo 20 proporcionan una edad y el resto tienen la edad a valor NULL, ¿qué estadística sería más fiable sobre el grupo entero y menos sensible a la ausencia de datos? Si no sabemos cuántos registros están a NULL, podemos usar la siguiente consulta para obtener un resultado razonable respecto al promedio de edades: SELECT AVG(edad) FROM Amigo;

Sin embargo, no podemos obtener un resultado razonable para sumas de edades con: SELECT SUM(edad) FROM Amigo;

Otras funciones relativamente insensibles a los nulos son STDDEV y VARIANCE. Las funcione MAX y MIN obtienen valores extremos sobre nuestros datos. Pueden fluctuar desordenadamente mientras que AVG se queda relativamente constante. Si añadimos un hombre de 100 años a un grupo de 99 personas que tienen 50 años, la edad media sólo se acerca a 50.5, pero la edad máxima se ha doblado. Si añadimos un bebé recién nacido, el promedio vuelve a 50, pero la edad mínima es ahora 0. Parece claro que la omisión o desconocimiento de valores nulos puede afectar profundamente a MAX, MIN y SUM, así que tenemos que ser cautelosos usándolos, en particular si un porcentaje significativo de los datos es NULL. La función COUNT es un caso especial. Esta función siempre retorna un valor diferente de NULL. Si se evalúa sobre una expresión, cuenta el número de filas donde la expresión es distinta de NULL. Esto quiere decir que la consulta: SELECT COUNT(edad) FROM Amigo;

Retorna el valor 20, puesto que en 80 filas el valor para edad es NULL. Sin embargo, la consulta: SELECT COUNT(*) FROM Amigo;

Retorna el valor 100, puesto que no se tendrán en cuenta los nulos, y simplemente contará todas las filas existentes. 7.1.4. Combinando funciones de grupo y de valor simple. Podemos combinar fácilmente funciones de grupo con funciones simples para obtener resultados más complejos o ajustados. Por ejemplo, supongamos que queremos obtener para el año 2010 el último mes en el cual un alumno faltó en cualquier módulo. La tabla FALTA proporciona un campo fecha sobre el cual podemos aplicar la función MAX para obtener la fecha más próxima, y después podemos aplicar una función simple como TO_CHAR para extraer el mes de la fecha. En este caso podemos obtener dos soluciones equivalentes: SELECT MAX( TO_CHAR(fecha, 'MM') )

FROM FALTA WHERE idMatricula=1 AND TO_CHAR(fecha, 'YYYY')=2010;

/

SELECT TO_CHAR( MAX(fecha), 'MM')

FROM FALTA WHERE idMatricula=1 AND TO_CHAR(fecha, 'YYYY')=2010;

Podemos combinar funciones de valor simple dentro de funciones de grupo y viceversa, aunque el orden de aplicación de las funciones no siempre produce los mismos resultados. En el ejemplo previo, la primera consulta aplica la función de valor simple sobre cada fila obteniendo un número de mes, y la función de grupo calcula el valor más alto. La segunda consulta aplica la función de grupo para obtener la fecha más

Page 83: Java y Oracle 11g

Oracle /83

próxima, y la función de valor simple extrae el valor de mes de dicha fecha. Sin embargo, si intentamos anidar funciones de grupo entre sí produciremos errores. Por ejemplo SELECT SUM( AVG(valor)) FROM NOTA;

Provocará el error: ORA-00978: función de grupo anidada sin GROUP BY

Además, si esto en realidad funcionase debería producir el mismo resultado que AVG(valor). Puesto que AVG(valor) produce un resultado simple, el SUM de un valor simple es justo el propio valor simple. Lo que sí podemos hacer es operar con los resultados de funciones de grupo. Por ejemplo, la siguiente consulta sería válida: SELECT MAX(valor) - MIN(valor) FROM NOTA;

7.1.5. «DISTINCT» en funciones de grupo. Todas las funciones de grupo admiten la opción DISTINCT o ALL. Cuando no se indica, se toma la opción ALL por defecto. El significado de estas opciones podemos verlo con un ejemplo de uso en la función COUNT: SELECT COUNT(DISTINCT localidad) "Nº localidades", COUNT(localidad) "Nº registros", COUNT(*)

FROM ALUMNO;

Un posible resultado podría ser el siguiente Nº localidades Nº registros COUNT(*)

----------------- -------------- -------------

10 25 30

La opción DISTINCT fuerza que COUNT cuente sólo el número de filas con localidades diferentes; mientras que la opción ALL (por defecto) cuenta todas las filas donde el valor de localidad no sea NULL aunque se repitan nombres de localidades. En este ejemplo, por los resultados, se ve que hay 5 filas donde la localidad está a valor nulo. Aunque puede hacerse, el uso de DISTINCT sobre las demás funciones de grupo suele ser raro, excepto para algún tipo de cálculo estadístico. MAX y MIN producen el mismo resultado con o sin DISTINCT.

7.2. La cláusula «Group by».

La cláusula GROUP BY permite realizara agrupaciones lógicas entre los registros de una tabla de consulta, de tal forma que dentro de cada grupo todas las filas tengan el mismo valor en los campos indicados con GROUP BY. Esto es sumamente útil cuando queremos utilizar funciones de agregado sobre grupos de registros en vez de sobre todos los registros. Por ejemplo, para obtener los apellidos y nota media de los alumnos matriculados en el curso 2004: SELECT A.apellidos, AVG(N.valor)

FROM Alumno A JOIN Matricula M ON A.nif=M.nif JOIN Nota N ON A.nif=N.nif

WHERE M.año=2004

GROUP BY A.apellidos ;

La consulta realiza el join entre las tablas Alumno, Matricula y Nota, descartando aquellas filas que no cumplen la condición. Las filas válidas se agrupan por el mismo nombre de alumno. De cada grupo se calcula la media del campo valor. En la tabla resultante, por cada grupo, se muestra un registro con los apellidos y el promedio calculado. GROUP BY no implica ordenación. Si queremos presentar el resultado ordenado por apellidos debemos especificar la cláusula «ORDER BY A.apellidos» después de la cláusula GROUP BY. Cada expresión en las columnas de la cláusula SELECT debe producir un único valor por grupo; es decir, puede ser un campo (o alguna expresión sobre dicho campo) de GROUP BY, o un literal, o una función de agregado. La cláusula GROUP BY también admite expresiones. Por ejemplo, la siguiente consulta obtiene el número de faltas de cada alumno por mes en el año 2008: SELECT M.nif, EXTRACT(Month FROM fecha) AS "Mes", COUNT(*) AS "Nº de faltas"

FROM Matricula M JOIN Falta F ON M.idMatricula=F.idMatricula

WHERE M.año=2008

GROUP BY EXTRACT(Month FROM fecha), M.nif ;

Una consideración: en la consulta anterior no podríamos incluir el campo fecha como una columna del SELECT. Esto es así porque fecha no se corresponde con una expresión de la cláusula GROUP BY. 7.2.1. Empleo de HAVING. HAVING es a los grupos lo que WHERE a las filas (si se especifica HAVING, deberá haberse especificado

Page 84: Java y Oracle 11g

Oracle /84

también GROUP BY). Es decir, HAVING permite indicar condiciones para filtrar grupos de la misma manera que WHERE nos permite filtrar las filas de las tablas base. Las expresiones de condición en la cláusula HAVING deben producir un solo valor por grupo. En el siguiente ejemplo se listan los alumnos matriculados y el número de faltas de asistencia cuando superan las 4 faltas: SELECT M.nif, COUNT(*) AS "Nº de faltas"

FROM Matricula M JOIN Falta F ON M.idMatricula=F.idMatricula

GROUP BY M.nif

HAVINGCOUNT(*) > 4

8. Subconsultas

8.1. Introducción.

Una subconsulta es una instrucción SELECT anidada dentro de una instrucción SELECT, SELECT...INTO, INSERTINTO, DELETE, o UPDATE o dentro de otra subconsulta. Se pueden utilizar tres formas de sintaxis para crear una subconsulta: comparación [ANY | ALL | SOME] (instrucción_sql)

expresión [NOT] IN (instrucción_sql)

[NOT] EXISTS (instrucción_sql)

En donde: comparación es una expresión seguida de un operador de comparación que compara la expresión con el resultado de la subconsulta; expresión es una expresión por la que se busca el conjunto resultante de la subconsulta; instrucción_sql es una instrucción SELECT, que sigue el mismo formato y reglas que cualquier otra instrucción SELECT. Se puede utilizar una subconsulta en lugar de una expresión en la lista de campos de una instrucción SELECT, como un origen de registros en la cláusula FROM, o en una cláusula WHERE o HAVING. En una subconsulta, se utiliza una instrucción SELECT para proporcionar un conjunto de uno o más valores especificados para evaluar en la expresión de la cláusula WHERE o HAVING. Se puede utilizar el predicado ANY o SOME, los cuales son sinónimos, para recuperar registros de la consulta principal, que satisfagan la comparación con cualquier otro registro recuperado en la subconsulta. Por ejemplo, la siguiente consulta devuelve los datos de matrícula de aquellos alumnos que no tienen faltas en el módulo 1: SELECT *

FROM Matricula

WHERE idMatricula <> ALL (SELECT DISTINCT idMatricula FROM Falta WHERE idModulo = 1);

La subconsulta devuelve la lista de matrículas que tiene alguna falta en el Módulo 1. El predicado ALL se utiliza para recuperar únicamente aquellos registros de la consulta principal que satisfacen la comparación con todos los registros recuperados en la subconsulta. En este caso, el id de Matricula no debe coincidir con ninguno de la lista establecida por la subconsulta. Si queremos obtener los alumnos que han aprobado el módulo 1 podemos plantear la siguiente consulta: SELECT *

FROM Alumno

WHERE nif = ANY (SELECT nif FROM Nota WHERE idModulo = 1 AND valor>=5);

La subconsulta devuelve la lista de nif's de alumnos que han aprobado el módulo 1. En este caso basta con que el nif del Alumno sea alguno de los que están en la lista establecida por la subconsulta.

8.2. Recuperación de datos con subconsulta

Si una subconsulta retorna un único registro con una sola columna, el valor de la columna puede ser tratado como si fuese un literal. Por ejemplo, la siguiente consulta recupera las matrículas del alumno José Pérez: SELECT idMatricula, año

FROM MATRICULA

WHERE nif = (SELECT nif FROM ALUMNO WHERE nombre='José' AND apellidos="Pérez")

En esta consulta se compara el nif de cada registro de MATRICULA con el valor del nif del alumno José Pérez, obtenido mediante una subconsulta. Si existe un único alumno llamado José Pérez la subconsulta devuelve una única fila con un único valor. Pero si existen varios alumnos con ese mismo nombre, la subconsulta retornará varios registros y se producirá un error del tipo siguiente: ERROR en línea 3:

ORA-01427: la subconsulta de una sola fila devuelve más de una fila

Page 85: Java y Oracle 11g

Oracle /85

Si nos solicitan los apellidos de alumnos que tienen faltas en el módulo 3, podemos resolverlo con la siguiente consulta: SELECT DISTINCT Alumno.apellidos

FROM Alumno JOIN Matricula USING(nif) JOIN Falta USING (idMatricula)

WHERE Falta.idModulo = 3;

Pero también podríamos resolverlo utilizando subconsultas: SELECT Alumno.apellidos

FROM Alumno

WHERE nif IN (SELECT DISTINCT nif

FROM Matricula

WHERE Matricula.id IN ( SELECT DISTINCT idMatricula FROM Falta WHERE idModulo = 3 ));

En este caso las subconsultas se utilizan mediante una condición IN. Para evaluar la consulta completa, el sistema evalúa primero las subconsultas anidadas, desde la más interior a la más exterior. La subconsulta más interior produce como resultado el conjunto de id's de matriculados que tienen faltas en el módulo de id igual a 3. Es como si el resultado de la subconsulta fuese una lista de id's. La siguiente subconsulta obtiene la lista de nif's correspondientes a las matrículas que tienen faltas. La consulta principal evalúa que nif's de alumnos pertenecen a dicha lista, y éstos son precisamente los que tienen una falta en el módulo 3. Análogamente se puede utilizar NOT IN para recuperar únicamente aquellos registros de la consulta principal para los que no hay ningún registro de la subconsulta que contenga un valor igual.

8.3. Subconsultas correlacionadas.

Al utilizar subconsultas debe estudiarse si la búsqueda es correlacionada o no. Diremos que la búsqueda en subconsultas es correlacionada si el resultado de la subconsulta depende de un resultado de la consulta principal. En el ejemplo de la sección anterior, el resultado de las subconsultas es independiente de la consulta principal (se obtienen id's de matricula que cumplen una condición particular independiente de los valores obtenidos en la consulta principal). Un ejemplo de subconsulta correlacionada es el siguiente: obtener el id de matrículas pertenecientes a alumnos que tienen más de una falta. SELECT DISTINCT F1.idMatricula

FROM Falta F1

WHERE F1.idMatricula IN ( SELECT F2.idMatricula

FROM Falta F2

WHERE NOT (F2.idMatricula=F1.idMatricula AND F2.fecha=F1.fecha) );

La estrategia en esta subconsulta es obtener el listado de registros de la tabla Falta excluyendo el registro evaluado en la consulta principal. Si este listado incluye un registro con el id evaluado en la consulta principal, quiere decir que el alumno correspondiente a ese id posee más de una falta. En la consulta principal, por cada registro de F1 se ejecuta la subconsulta recorriendo los registros de F2 cada vez. Es decir, la subconsulta se ejecuta tantas veces como registros contenga la tabla Falta. En las subconsultas no correlacionadas el número de accesos viene dado por la suma de los accesos en cada SELECT. En las subconsultas correlacionadas el número de accesos es producto de la multiplicación, pues debe evaluarse la SELECT interior para cada registro de la SELECT exterior. Por ejemplo, obtener el nif y los apellidos de alumnos que se hayan matriculado en más de un ciclo. SELECT DISTINCT Alumno.nif , Alumno.apellidos

FROM Alumno JOIN Matricula M1 USING (nif)

WHERE M1.nif IN ( SELECT M2.nifFROM Matricula M2WHERE M2.idMatricula<> M1.idMatricula )

8.4. Subconsultas con operador de comparación distinto de «IN».

Podemos usar una subconsulta para obtener un único valor y entonces compararlo con los operadores de comparación (=, <>, > , >=, <, <=). Por ejemplo, la siguiente consulta obtiene el nombre y apellidos de los alumnos que residen en la misma localidad que el alumno de nif '11111111A'. SELECT nombre , apellidos

FROM Alumno

WHERE localidad = ( SELECT localidad FROM Alumno WHERE nif = '11111111A' )

Si la subconsulta retornase más de un valor, entonces se produciría un error en la ejecución de la consulta externa.

Page 86: Java y Oracle 11g

Oracle /86

8.5. Funciones de agregado en subconsultas.

Se suelen utilizar funciones de agregado en subconsultas para obtener un valor de grupo que podamos comparar con valores individuales en cada registro. En este ejemplo, se obtiene el nif de los alumnos que tienen una nota en el módulo 4 superior a su nota media. El cálculo de la nota media se realiza mediante la subconsulta. SELECT n1.nif

FROM Nota n1

WHERE n1.idModulo = 4

AND n1.valor > ( SELECT AVG(*) FROM Nota n2WHERE n1.nif = n2.nif );

En este ejemplo, se obtienen los nif's de alumnos matriculados en 2004 cuya nota media es superior a la nota media de todos los alumnos. SELECT A.nif

FROM Matricula M JOIN Alumno AON M.nif=A.nif JOIN Nota N ON A.nif=N.nif

WHERE M.año = 2004

GROUP BY A.nif

HAVING AVG (valor) > ( SELECT AVG(valor)FROM Nota );

8.6. Subconsultas con «EXISTS».

EXISTS es el cuantificador existencial y puede aplicarse sobre subconsultas. La expresión «EXISTS (SELECT …

FROM …)» da como resultado un valor verdadero si y solo si el resultado de evaluar la subconsulta no es el conjunto vacío; en otras palabras, si existe al menos un registro en el resultado. Por ejemplo, la siguiente consulta obtiene el id de matrícula de alumnos del curso 2004-05 que tienen alguna falta en el módulo 5. SELECT idMatricula

FROM Matricula

WHERE año = 2004

AND EXISTS ( SELECT 1 FROM Falta WHERE Falta.idMatricula AND Falta.idModulo=5 )

Nota. En una subconsulta evaluada mediante el operador EXISTS la lista de columnas que devuelve el comando SELECT carece de importancia. Por ello es habitual que este tipo de consultas retornen una única columna con una expresión literal.

La forma negada, NOT EXISTS, es importante en cierto tipo de consultas complejas. Como primer ejemplo obtendremos el id de matrícula de alumnos del curso 2004-05 que no tienen ninguna falta en los módulos 4 y 5. SELECT idMatricula

FROM Matricula

WHERE año = 2004

AND NOT EXISTS ( SELECT 1 FROM Falta WHERE Falta.idMatricula AND Falta.idModulo IN (4, 5 ) )

El siguiente ejemplo es más complejo, y obtiene los id de matrícula de alumnos del curso 2004-05 que tienen faltas en todos los módulos. SELECT Matricula.idMatricula

FROM Matricula

WHERE Matricula.año = 2004

ANDNOT EXISTS ( SELECT 1FROM Modulo

WHERE NOT EXISTS ( SELECT 1FROM Falta

WHERE Falta.idMatricula = Matricula.idMatricula AND Falta.idModulo = Modulo.idModulo )) ;

Para comprender la consulta, podríamos enunciarla como: «obtener los id de matricula de alumnos del curso 2004-05 que no tengan módulos en los que no tengan faltas».

8.7. Expresiones de columna con subconsultas

Es también posible usar una subconsulta en una de las expresiones de columna de una SELECT siempre y cuando la subconsulta retorne un único registro con un valor simple. Un primer ejemplo sencillo es obtener los id's de matrícula, los apellidos de alumnos y el año de curso. SELECT M.idMatricula, (SELECT apellidos FROM Alumno A WHERE A.nif=M.nif), M.año

FROM Matricula M;

Como alternativa al uso de la cláusula GROUP BY, podemos obtener el nif de alumnos y el número de módulos que han cursado.

Page 87: Java y Oracle 11g

Oracle /87

SELECT nif , (SELECT COUNT(*) FROM Nota WHERE Nota.nif=Alumno.nif)

FROM Alumno;

8.8. Subconsultas como origen de registros para «FROM».

En la cláusula FROM se pueden usar como orígenes de datos: - Una tabla - Una vista - Una función que devuelva una tabla - Una subconsulta

Por ejemplo, podemos obtener en una subconsulta registros con el nif de un alumno, el nombre de un módulo y la nota. Podemos utilizar dicha subconsulta como origen de datos para obtener la media de notas por módulo. SELECT nombre AS "Módulo" , AVG (valor) AS "Nota media"

FROM (SELECT N.nif, M.nombre, N.valor FROM Nota N JOIN Modulo M USING (idModulo))

GROUP BY nombre;

Algo más complejo es obtener un valor de nota (entre 1 y 10) y el número de alumnos que han conseguido dicha nota en el módulo 1. Para la consulta se debe forzar un registro por cada una de las notas, de forma que si ningún alumno obtuvo esa nota se muestre el valor 0. Podemos obtener el número de alumnos por nota registrada en la tabla Nota: SELECT valor, COUNT(*)

FROM Nota

GROUP BY valor;

Pero esta consulta no garantiza que se genere necesariamente un registro por cada valor de nota posible (por ejemplo, si ningún alumno tiene la nota 7, no se generará un registro con la nota 7). Para forzar esto necesitamos una subconsulta con todos los valores de nota y forzar un JOIN externo: SELECT valor, NVL(num, 0)

FROM

(SELECT 1 valor FROM DUAL UNION SELECT 2 valor FROM DUAL UNION SELECT 3 valor FROM DUAL

UNION SELECT 4 valor FROM DUAL UNION SELECT 5 valor FROM DUAL UNION SELECT 6 valor FROM

DUAL UNION SELECT 7 valor FROM DUAL UNION SELECT 8 valor FROM DUAL UNION

SELECT 9 valor FROM DUAL UNION SELECT 10 valor FROM DUAL) T1

LEFT JOIN

(SELECT valor, COUNT(*) num FROM Nota GROUP BY valor) T2

ON T1.valor = T2.valor;

Aquellos valores de nota no incluidos en la tabla Nota no combinarán en el LEFT JOIN y por tanto producirán un registro con las columnas combinadas (valor y num) a valor NULL. Por ello se utiliza la función NVL(), que verifica si el campo num tiene valor NULL y en ese caso lo sustituye por un cero.

9. Vistas

9.1. Introducción.

Una vista no es más que una consulta almacenada que puede ser reutilizada tantas veces como se desee. Una vista no contiene datos sino la instrucción SELECT necesaria para generarla; eso asegura que los resultados de la vista sean siempre coherentes con los datos actuales almacenados en las tablas. Por todo ello, las vistas gastan muy poco espacio de disco. Las vistas se emplean para:

- Realizar consultas complejas más fácilmente, ya que permiten dividir la consulta en varias partes. - Proporcionar el contenido de tablas con datos completos. - Utilizar visiones especiales de los datos. - Ser utilizadas como tablas que resumen todos los datos. - Ser utilizadas como cursores de datos en los lenguajes procedimentales (como PL/SQL).

Hay dos tipos de vistas: • Simples. Las forma una sola tabla y no contienen funciones de agrupación. Su ventaja es que permiten siempre realizar operaciones DML sobre ellas. • Complejas. Obtienen datos de varias tablas, y pueden utilizar funciones de agrupación. No siempre permiten operaciones DML.

Podemos usar la cláusula ORDER BY al crear una vista, pero esto puede influir en un rendimiento negativo,

Page 88: Java y Oracle 11g

Oracle /88

porque obliga a operaciones adicionales.

9.2. Crear y consultar vistas.

Las sintaxis para crear una vista es la siguiente: CREATE [OR REPLACE] [FORCE|NOFORCE] VIEW nombre_vista [(alias[, alias2...]]

AS consultaSelect

[WITH CHECK OPTION [CONSTRAINT restricción]]

[WITH READ ONLY [CONSTRAINT restricción]]

Donde: OR REPLACE, indica que si la vista ya existía se cambie por la actual. FORCE, crea la vista aunque los datos de la consulta SELECT no existan. alias, es la lista de alias que se establecen para las columnas devueltas por la consulta SELECT en la que se basa esta vista. El número de alias debe coincidir con el número de columnas devueltas por SELECT. WITH CHECK OPTION, hace que sólo las filas que se muestran en la vista puedan ser añadidas (INSERT) o modificadas (UPDATE). La restricción que sigue a esta sección es el nombre que se le da a esta restricción de tipo CHECK OPTION. WITH READ ONLY, hace que la vista sea de sólo lectura. Permite grabar un nombre para esta restricción.

Lo bueno de las vistas es que tras su creación se utilizan como si fueran una tabla virtual. Por ejemplo, la siguiente vista recupera el número de faltas de cada alumno en cada módulo de cada año: CREATE VIEW ResumenFaltas (nif, modulo, faltas)

AS

( SELECT M.nif, Mo.nombre, COUNT(*) FROM

Matricula M JOIN Falta F ON M.idMatricula=F.idMatricula JOIN Modulo Mo ON F.idModulo=Mo.idModulo

);

/

SELECT DISTINCT nif, modulo FROM ResumenFaltas;

9.3. Ejecución de comandos DML sobre vistas.

Las instrucciones DML (INSERT, UPDATE y DELETE) ejecutadas sobre las vistas permiten añadir o modificar los datos de las tablas relacionadas en la consulta subyacente de la vista. Ahora bien, no es posible ejecutar instrucciones DML sobre vistas que:

- Utilicen funciones de grupo (SUM, AVG, etc.) - Usen GROUP BY o DISTINCT. - Posean columnas con cálculos.

Además no se pueden añadir datos a una vista si en las tablas referenciadas en la consulta SELECT hay campos NOT NULL que no aparecen en la consulta (es lógico ya que al añadir el dato se tendría que añadir el registro colocando el valor NULL en el campo ausente). Por ejemplo, podemos utilizar la siguiente vista para hacer inserciones de matrículas: CREATE VIEW MatriculaAmpliada

AS

SELECT idMatricula, nif, apellidos || ', ' || nombre AS alumno, año

FROM Matricula LEFT JOIN Alumno USING (nif);

/

INSERT INTO MatriculaAmpliada (idMatricula, nif, año) VALUES (67, '77777777J', 2008);

Podremos también hacer inserciones sobre una vista basada en varias tablas si Oracle puede determinar los registros apropiados a insertar. En vistas multitabla Oracle determina qué tablas proporcionan su clave. Si una vista contiene muchas columnas de una tabla para identificar la clave primaria de esta tabla, se conserva la clave y Oracle puede ser capaz de insertar filas en la tabla a través de la vista.

9.4. Estabilidad de una vista.

Recuérdese que los resultados de una vista son construidos al instante sobre la tabla (o tablas) subyacente cuando ejecutamos la vista. Como consecuencia, si la tabla subyacente es eliminada la validez de la vista desaparece. Intentar una consulta sobre una vista cuya tabla subyacente ha sido eliminada produce un mensaje de error.

Nota. La única excepción a esta regla es usar vistas materializadas. Una vista materializada es una tabla que almacena datos que se obtendrían normalmente a través de una vista.

Page 89: Java y Oracle 11g

Oracle /89

Crear vistas basadas en una consulta que usa el asterisco para recuperar las columnas presenta un caso especial. Por ejemplo, si creamos una vista sobre la tabla Alumno: CREATE OR REPLACE VIEW Alumno_View AS

SELECT * FROM Alumno;

Y entonces alteramos la tabla subyacente: ALTER TABLE Alumno ADD (pais VARCHAR2(30));

Ocurre que, a pesar del cambio de la tabla subyacente la vista es todavía válida; pero la columna pais no será visible a través de la vista. Si después de alterar la tabla, volvemos a compilar la vista, la nueva columna se verá a través de la vista.

9.5. Mostrar la lista de vistas.

La vista del diccionario de datos de Oracle USER_VIEWS permite mostrar una lista de todas las vistas que posee el usuario actual. Es decir, para saber qué vistas hay disponibles se usa: SELECT * FROM USER_VIEWS;

La columna TEXT de esa vista contiene la sentencia SQL que se utilizó para crear la vista (sentencia que es ejecutada cada vez que se invoca a la vista).

9.6. Borrar vistas.

Se utiliza el comando DROP VIEW para eliminar una vista, como por ejemplo: DROP VIEW nombreDeVista;

10. Comandos internos en SQL*PLUS e iSQL*Plus

Lo que se comenta en este apartado son comandos y operaciones que no pertenecen al lenguaje SQL, sino que son comandos que sirven para dar instrucciones al programa SQL*Plus o iSQL*Plus. Las operaciones que se comentan aquí son interpretadas por el cliente SQL*Plus y no por Oracle. Estas operaciones sirven sobre todo para variar la forma en la que se muestran los resultados de las consultas SQL. Hay que tener en cuenta que hay cierta diferencia entre los comandos SQL*Plus e iSQL*Plus.

10.1. Variables de sustitución.

Se utilizan variables de sustitución para poder pasar parámetros a una consulta. Por ejemplo, si a menudo se realiza un listado de clientes en el que queremos mostrar los datos de un cliente identificado por su DNI, entonces se puede utilizar una variable de sustitución para el DNI, de modo que cada vez que se ejecute esa consulta se pedirá el nuevo valor de la variable. 10.1.1. Operador &. La primera forma de utilizar variables de sustitución es mediante el comando &. Este símbolo, utilizado en cualquier parte de la consulta, permite rellenar el contenido de una variable de sustitución. Por ejemplo: SELECT * FROM Piezas WHERE modelo=&mod;

Al ejecutar esa sentencia, desde el cliente SQL*Plus se nos pedirá rellenar el valor de la variable mod. Esa variable no se puede volver a usar, si se usa se nos invitará a indicar el valor que le damos. La ventaja de esta técnica está en que cada vez que ejecutemos podremos dar un valor a la variable, lo que nos permite reutilizar consultas una y otra vez para distintos valores, sin tener que rescribirla. En el caso de que la variable sea de texto, hay que colocar el símbolo & dentro de las comillas que delimitan el texto. Ejemplo: SELECT * FROM Piezas WHERE tipo='&tip';

Es decir, se trata de una macro-sustitución en la que el contenido de la variable se sustituye por su contenido antes de pasar la instrucción a Oracle, de ahí que sea necesario colocar las comillas, de otro modo Oracle indicaría que la instrucción es errónea. 10.1.2. Comando «DEFINE». Se pueden utilizar variables de sustitución que se definan como variables de usuario mediante el comando DEFINE. La sintaxis de este comando es: DEFINE variable=valor;

La variable se sobreentiende que es de tipo texto. El valor es el contenido inicial de la variable. La variable así creada tiene vigencia durante toda la sesión de usuario. Se elimina en el cierre de la sesión o si se usa el comando UNDEFINE indicando el nombre de la variable a eliminar. Para cambiar el valor de la variable se debe utilizar otra vez el comando DEFINE. La ventaja respecto al método anterior está en que la misma variable de sustitución se puede utilizar para varios SELECT. La desventaja está en que requiere tocar el código para cambiar el valor de la variable. Ejemplo:

Page 90: Java y Oracle 11g

Oracle /90

DEFINE tip='TU';

SELECT * FROM piezas WHERE tipo='&tip';

SELECT * FROM existencias WHERE tipo='&tip';

En el ejemplo, los dos SELECT muestran piezas cuyo tipo sea TU. SQL*Plus no preguntará por el valor de la variable tip. El comando DEFINE sin nada más permite mostrar una lista de todas las variables definidas en ese momento. 10.1.3. Operador &&. Se trata de una mezcla entre las opciones anteriores. Cuando en una consulta se utiliza una variable de sustitución mediante dos símbolos ampersand, entonces al ejecutar la consulta se nos preguntará el valor. Pero luego ya no, la variable queda definida como si se hubiera declarado con DEFINE. El resto de veces que se utilice la variable, se usa con un solo &. El cambio de valor de la variable habrá que realizarle con DEFINE.

10.2. Comando «SET».

Este comando permite cambiar el valor de las variables de entorno del programa. Su uso es: SET nombreVariable valor

Las variables más interesantes a utilizar son:

Variable Posibles valores Explicación

ECHO ON y OFF Repite el comando SQL antes de mostrar su resultado.

TIMING ON y OFF Permite mostrar estadísticas sobre el tiempo de ejecución en cada consulta SQL que se ejecute (interesante para estadísticas).

HEADING ON y OFF Hace que el encabezado con los alias de las columnas se active o no.

WRAP ON y OFF Activado, trunca un texto si sobrepasa la anchura máxima.

COMPATIBILITY V7, V8, NATIVE Permite indicar la versión con la que se comprueba la

compatibilidad de los comandos. NATIVE indica que el propio servidor Oracle decide la compatibilidad.

DEFINE &, caracter, ON y OFF Permite activar y desactivar la posibilidad de usar variables de sustitución. Permite indicar el carácter utilizado para la sustitución de variables.

PAGESIZE n Indica el número de filas que se muestran antes de repetir el encabezado de la consulta

LINESIZE n Indica la anchura máxima de la línea de la consulta. Si una línea de la consulta sobrepasa este valor, los datos pasan a la siguiente. También influye sobre los tamaños y posiciones de los encabezados y pies de los informes.

NULL valor Indica qué valor se muestra cuando hay nulos.

NUMFORMAT formato Permite especificar un formato que se aplicará a todos los números. (Véase formato de columnas, más adelante).

NUMWIDTH valor Indica la anchura máxima utilizada para mostrar números. Si un número sobrepasa esta anchura, es redondeado.

FEEDBACK n, ON y OFF Hace que se muestren el número total de registros de la consulta cuando el resultado supera los n registros.

LONG ancho Anchura máxima para los campos de tipo LONG.

El comando SHOW seguido del nombre de uno de los parámetros de la tabla anterior, permite mostrar el estado actual del parámetro indicado. Si se usa SHOW ALL, entonces se muestran todos.

10.3. Encabezado y pie de informe.

Los parámetros BTITLE y TTITLE permiten, respectivamente, indicar un texto de pie y de encabezado para la consulta. El formato es {B|T}TITLE {texto|ON|OFF}

El texto es lo que se desea en el encabezado o pie. Ese texto puede incluir las palabras LEFT, RIGHT o CENTER para hacer que el texto vaya a izquierda, centro o derecha respectivamente. Se pueden indicar incluso las tres cosas a la vez: TTITLE LEFT 'informe1' RIGHT 'estudio de clientes';

Se puede también usar la palabra COL seguida del número de columna en el que se desea el texto:

Page 91: Java y Oracle 11g

Oracle /91

TTITLE COL 50 'informe1';

También se puede indicar la palabra TAB seguida de un número que representará tabulaciones, haciendo que SQL*Plus deje ese espacio en los encabezados o pies.

10.4. Comando «COLUMN».

Permite especificar un formato de columna. Si se usa sin modificador (sólo COLUMNS o su abreviatura COL) se muestran las configuraciones de formato de columnas actualmente en uso. Si se añade el nombre de una columna, se indica el formato actual (si lo hay) para esa columna. 10.4.1. Añadir formato de columna. Si se usa COLUMN (o COL) con el parámetro HEADING seguido de un texto, el texto se convierte en la cabecera de la columna (sustituyendo al alias de la columna). Ese texto sólo sirve para ser mostrado, no puede formar parte de una sentencia SQL. COLUMN precio_venta HEADING 'Precio de|venta';

SELECT tipo, modelo, precio_venta FROM piezas;

En el ejemplo, la barra vertical provoca un salto de línea en el texto. El parámetro FORMAT permite indicar una máscara de formato para el texto. Se usan códigos especiales para ello. A los textos se les puede colocar una anchura máxima de columna. Eso se hace con una A seguida del número que indica esa anchura. COLUMN tipo FORMAT 'A10';

SELECT tipo, modelo, precio_venta FROM piezas;

Para los números se usan códigos de posición:

Código Significado

9 Posición para un número. Si el valor es 0 o vacío, entonces no se muestra nada

0 Posición para un número. Si el valor es 0 o vacío, entonces se muestra 0

$ Posición para el signo de dólar

MI Muestra un signo menos tras el número, si el número es negativo (ejemplo: '999MI')

S Muestra el signo del número (+ ó -) en la posición en la que se coloca el signo

PR Muestra los números negativos entre < y >

D Muestra el signo decimal en esa posición

G Muestra el signo de grupo en esa posición

. Muestra el punto (separador decimal)

, Muestra la coma (separador de miles)

L Muestra el símbolo de moneda nacional en esa posición

RN Muestra el número en romano (mayúsculas)

rn Muestra el número en romano (minúsculas)

Las fechas deben ser formateadas desde la propia instrucción SQL mediante la función TO_CHAR (vista en un tema anterior) 10.4.2. Parámetro «LIKE». Permite copiar atributos de una columna a otra: COLUMN precio_venta FORMAT '9G990D00L';

COLUMN precio_compra LIKE precio_venta;

Las dos columnas tendrán el mismo formato (separador de miles, decimales y moneda tras los dos decimales) 10.4.3. Parámetro «NULL». Indica un texto que sustituirá a los valores nulos. 10.4.4. Parámetro «CLEAR». Elimina el formato de la columna. Lógicamente se pueden combinar varias acciones a la vez.

10.5. Comando «BREAK».

Es uno de los comandos más poderosos. Permite realizar agrupaciones en las consultas, consiguiendo verdaderos informes (en especial si se combina con COMPUTE). Permite dividir la vista en secciones en base al valor de un campo al que se le pueden incluso quitar los duplicados. El comando: BREAK ON tipo;

Hace que las columnas con alias tipo no muestren los duplicados, mostrando una vez cada valor duplicado.

Page 92: Java y Oracle 11g

Oracle /92

Para el buen funcionamiento de la orden, el resultado debe de estar ordenado por esa columna. Se pueden hacer varios grupos a la vez: BREAK ON tipo ON modelo;

La orden CLEAR BREAK elimina todos los BREAK anteriormente colocados. 10.5.1. Parámetro «SKIP». A la instrucción anterior se le puede añadir la palabra SKIP seguida de un número. Ese número indica las líneas que se dejan tras el valor del grupo al imprimir. Si se indica SKIP PAGE, significa que se salta una página completa cada vez que cambie el valor del grupo. 10.5.2. Parámetro «ON REPORT». Permite (en unión con COMPUTE) realizar cálculos de totales sobre el informe completo. 10.5.3. Parámetros «DUPLICATES» y «NODUPLICATES». Permiten mostrar o no los duplicados de cada sección. La opción inicial es NODUPLICATES.

10.6. Comando «COMPUTE».

Permite en unión con BREAK realizar cálculos para las secciones de una consulta. Todo COMPUTE está asociado a un apartado ON de una instrucción BREAK previa. Sintaxis: COM[PUTE] [función [LAB[EL] texto] OF columna_o_Alias ON {columna_o_Alias|REPORT}

Donde: función, es el nombre de la función de cálculo que se usa (SUM, AVG, MIN, MAX, NUM, STD o VAR). LABEL, permite indicar un texto previo al resultado del cálculo, si no se utiliza se pone el nombre de la función utilizada. OF, indica el nombre de columna o alias utilizado en la instrucción SELECT a partir de la que se realiza el cálculo. ON, indica el nombre de columna o alias que define la sección sobre la que se realizará el cálculo. Este nombre debe haber sido indicado en un BREAK anterior. REPORT, indica que el cálculo se calculará para toda la consulta.

Por ejemplo: CLEAR BREAK;

COLUMN TIPO FORMAT A20;

BREAK ON tipo SKIP PAGE ON modelo ON REPORT;

COMPUTE SUM LABEL 'Total' MAX LABEL 'Máximo' OF cantidad ON tipo;

COMPUTE SUM LABEL 'Total' OF cantidad ON modelo;

COMPUTE SUM LABEL 'Total absoluto' OF cantidad ON REPORT;

SELECT tipo, modelo, n_almacen, cantidad FROM existencias

WHERE tipo='AR' OR tipo='TU' ORDER BY tipo, modelo;

Producirá el siguiente resultado:

Page 93: Java y Oracle 11g

Oracle /93

10.7. Guardar consultas en ficheros.

Podemos guardar consulta en un fichero de texto y después ejecutarlo desde SQL*Plus. Debemos guardar el archivo con extensión .sql para que sea reconocido sin problemas. Para ejecutar el archivo se utiliza el comando START o la sintaxis @archivo desde la línea de comandos de SQL*Plus. Como ejemplo, supongamos el archivo siguiente:

Archivo «comandos.sql» SET PAGESIZE 40

SET LINESIZE 80

SELECT IdObra, Nombre, Precio

FROM Obra

/

Ahora podemos ejecutarlo desde SQL*Plus mediante el comando: SQL> @comandos.sql

O bien: SQL> START comandos.sql

10.8. Redirigir la salida de SQL*Plus con «SPOOL».

Oracle permite redirigir la salida de los comandos en SQL*Plus a un fichero de texto. Esto se consigue mediante el comando SPOOL. SQL> SPOOL /tmp/mi_fichero_de_texto.lst

Deberemos tener en cuenta que: • Los datos de salida no se materializarán en el fichero de texto hasta que se ejecute el comando SPOOL

OFF. • Si vamos a redirigir muchos datos tendremos que modificar la variable de entorno SERVEROUTPUT para darle un tamaño mayor.

El siguiente sería un ejemplo de utilización de SPOOL donde se desea un listado en un fichero de texto con la ruta "d:\tmp\listado.lst": SQL> SET SERVEROUTPUT ON SIZE 10000

Page 94: Java y Oracle 11g

Oracle /94

SQL> SPOOL d:\tmp\listado.lst

SQL> PROMPT TEST

SQL> SPOOL OFF

Si ahora leemos el contenido del archivo listado.lst veremos que contiene lo mismo que se mostró por pantalla.

11. Consultas avanzadas

11.1. Consultas con «ROWNUM».

La pseudo-columna ROWNUM devuelve un número para cada fila de salida de un SELECT, que se corresponde con el orden en que son generadas las filas a partir de los registros de origen. Por ejemplo, la consulta siguiente: SELECT ROWNUM AS "Orden", nif, apellidos FROM Alumno WHERE localidad = 'Madrid';

Produce un resultado como éste:

ORDEN NIF APELLIDOS

1 44444444L López Martín

2 55555555V Senén Rivas

3 33333333H García Gómez

Si aplicamos una ordenación por nif: SELECT ROWNUM AS "Orden", nif, apellidos FROM Alumno WHERE localidad = 'Madrid' ORDER BY nif;

Se producirá el siguiente resultado:

ORDEN NIF APELLIDOS

3 33333333H García Gómez

1 44444444L López Martín

2 55555555V Senén Rivas

Como vemos, ROWNUM no se corresponde con la ordenación establecida, sino con el orden en que fueron generadas las filas antes de su ordenación. Si deseamos establecer una secuencia desde 1 sobre el resultado de la ordenación deberemos aplicar ROWNUM sobre una subconsulta: SELECT ROWNUM AS "Orden", nif, apellidos

FROM (SELECT nif, apellidos FROM Alumno WHERE localidad = 'Madrid' ORDER BY nif);

Puesto que la subconsulta obtiene una lista de los alumnos ordenada por nif, el SELECT superior obtendrá esa lista pero mostrando el orden de las filas en esa consulta. Eso permite hacer consultas del tipo top-n (los n más...). Por ejemplo, para sacar el top-10 de los alumnos con mejor nota media: SELECT nif AS "Alumno"

FROM (SELECT nif, AVG(valor) FROM Nota GROUP BY nif ORDER BY AVG(valor))

WHERE ROWNUM<=10;

Cuando se usa ROWNUM para filtrar registros hay que tener en cuenta las siguientes normas: - Se puede comparar por igualdad ROWNUM con el valor 1, pero no con cualquier otro valor. - Se puede comparar ROWNUM mediante el operador menor que (o menor o igual que) con un número, pero no se puede comparar con el operador mayor que (o mayor o igual que).

Por ejemplo, la siguiente consulta no retornaría ningún registro: SELECT * FROM Alumno WHERE ROWNUM > 1

Este comportamiento se produce por el siguiente motivo: el valor de ROWNUM es asignado a cada fila luego de que pasa la fase de predicado de la consulta pero antes que la consulta pase por algún ordenamiento o agregación; además, cada valor de ROWNUM es incrementado solamente luego de ser asignado, lo que explica por qué la consulta no devuelve ninguna fila. Debido a que ROWNUM > 1 no es verdadero para la primera fila, ROWNUM nunca avanza al valor 2.

11.2. Consultas con «ROWID».

El tipo de dato ROWID permite almacenar la dirección que tiene una fila dentro de su tabla. Cada vez que se inserta un registro en la base de datos Oracle genera un valor hexadecimal de tipo ROWID para el nuevo registro con el formato siguiente: OOOOOOFFFBBBBBBRRR

Donde:

Page 95: Java y Oracle 11g

Oracle /95

OOOOOO: es el segmento de la base de datos. Todos los objetos que estén en el mismo esquema y en el mismo segmento tendrán el mismo valor. FFF: es el número de fichero del tablespace relativo que contiene la fila. BBBBBB: es el bloque de datos que contiene a la fila. El número de bloque es relativo a su fichero de datos, no al tablespace. Por lo tanto, dos filas con números de bloque iguales podrían residir en diferentes ficheros de datos del mismo tablespace. RRR: es el número de fila en el bloque.

Por tanto no existen dos filas en la base de datos con el mismo ROWID. Este valor es sólo accesible mediante la pseudo-columna ROWID, por ejemplo: SELECT ROWID, nombre, apellidos FROM Alumno;

Siempre que queramos obtener una fila de la forma más rápida posible, debemos hacerlo a través de su ROWID. Un uso típico suele ser obtener un listado de ROWID's con un SELECT, y después acceder a cada una de las filas directamente con la condición del ROWID. Esta pseudo-columna también puede ser útil para consultar ciertos aspectos físicos sobre las tablas de nuestra base de datos. Por ejemplo, una forma de saber en cuántos ficheros de datos está alojada una tabla sería consultando la parte de ROWID correspondiente. SELECT COUNT(DISTINCT SUBSTR(ROWID,7,3)) "Número ficheros" FROM Alumno;

11.3. Consultas con «RANK».

Podemos crear vistas sobre consultas de agrupación para realizar informes de resumen complejos sobre los datos. Las vistas de una agrupación simplifican la representación de los datos para varios niveles de agrupación dentro de nuestra aplicación. Esto hace que sea más sencillo usar funcionalidades analíticas avanzadas. Consideremos la tabla NOTA para crear un vista que proporcione la nota media de cada alumno. CREATE VIEW NOTAS_AVG AS (nif, notaMedia)

SELECT nif, AVG(valor) FROM Nota GROUP BY nif;

Podemos obtener un listado de los alumnos, ordenados de mayor a menor, por los que tienen mejor nota media: SELECT * FROM NOTAS_AVG ORDER BY notaMedia DESC;

Siendo un posible resultado el siguiente: NIF NOTAMEDIA

---------------- -----------------

00000000A 9,8

22222222B 8

33333333C 8

44444444D 6,5

55555555E 5,3

Este resultado muestra la clasificación de los alumnos por su nota media; el alumno de nif '00000000A' es el primero en cuanto a mejores notas. Sin mostrar esta lista, es posible determinar dónde debería estar un valor determinado de nota media en esta clasificación. Para hacer esto se usa la función RANK; la cual toma un valor como entrada y usa cláusulas adicionales (WITHIN GROUP y ORDER BY) para decirle a Oracle cómo crear la clasificación. Por ejemplo, para saber en que posición de la clasificación debería estar la nota media 7 podemos efectuar la siguiente consulta: SELECT RANK(7) WITHIN GROUP (ORDER BY notaMedia DESC) "Ranking"

FROM NOTAS_AVG;

Siendo el resultado: RANKING

----------------

4

El valor 4 indica la posición en el resultado de la clasificación que debería ocupar un registro con el valor 7 en la columna NOTAMEDIA. Desde una perspectiva de porcentaje, podemos aplicar la función PERCENT_RANK para obtener la clasificación de la nota media 7: SELECT PERCENT_RANK(7) WITHIN GROUP (ORDER BY notaMedia DESC) "Ranking"

FROM NOTAS_AVG;

Siendo el resultado:

Page 96: Java y Oracle 11g

Oracle /96

RANKING

----------------

,6

Es decir, estaría en una posición casi intermedia (con el 60%). Con esta técnica de usar vistas de resumen y funciones analíticas podemos crear vistas e informes que incluyan medias ponderadas, producción eficaz, porcentajes de totales, porcentajes de subtotales, y muchos cálculos similares.

11.4. Consultas sobre estructuras jerárquicas.

Imaginemos una tabla de empleados definida por un código de empleado, nombre del mismo y el código del jefe: CREATE TABLE Empleado (

codigo NUMBER PRIMARY KEY,

nombre NVARCHAR2(100),

codigoJefe NUMBER REFERENCES Empleado(codigo));

E insertamos los siguientes registros: CODIGO NOMBRE CODIGOJEFE

------------ ------------ ------------------

1 Ángel

2 Eva 1

3 Andrés 2

4 Antonio 1

5 Carmen 2

6 Carmelo 3

Este último código está relacionado con el código de empleado que posee el jefe en cuestión. Así definido, una consulta que muestre el nombre de un empleado y el nombre de su jefe directo, sería: SELECT E.nombre AS empleado, J.nombre AS jefe

FROM Empleado EJOIN Empleado J ON (E.codigoJefe=J.codigo);

Obtendríamos por ejemplo: EMPLEADO JEFE

-------------- ----------

Antonio Ángel

Ángel

Eva Ángel

Carmen Eva

Andrés Eva

Carmelo Andrés

En el ejemplo se observa como un jefe puede tener otro jefe, generando una estructura jerárquica:

En este tipo de estructuras a veces se requieren consultas que muestren todos los empleados de un jefe, mostrando los mandos intermedios. Se trata de una consulta que recorre ese árbol. Este tipo de consultas posee esta sintaxis: SELECT [LEVEL,] listaDeColumnasYExpresiones

FROM tabla(s)...

[WHERE condiciones...]

[START WITH condiciones]

CONNECT BY [PRIOR] expresion1=[PRIOR] expresion2

El apartado CONNECT permite indicar qué relación hay que seguir para recorrer el árbol. La palabra PRIOR indica hacia dónde se dirige el recorrido. Finalmente el apartado START indica la condición de inicio del recorrido (normalmente la condición que permita buscar el nodo del árbol por el que comenzamos el recorrido); es decir, sirve para indicar desde dónde comenzamos.

Page 97: Java y Oracle 11g

Oracle /97

El siguiente ejemplo obtiene los jefes jerárquicos del empleado 'Andrés': SELECT nombre

FROM Empleado

START WITH nombre='Andrés'

CONNECT BY PRIOR codigoJefe = codigo;

Y el resultado es: NOMBRE

------------

Andrés

Eva

Ángel

Sin embargo, la siguiente consulta: SELECT nombre

FROM Empleado

START WITH nombre='Andrés'

CONNECT BY codigoJefe= PRIOR codigo;

Devuelve los subordinados de 'Andrés': NOMBRE

------------

Andrés

Carmelo

El modificador LEVEL permite mostrar el nivel en el árbol jerárquico de cada elemento: SELECT LEVEL, nombre

FROM Empleado

START WITH nombre='Ángel'

CONNECT BY codigoJefe= PRIOR codigo;

Y el resultado es: LEVEL NOMBRE

-------- ------------

1 Ángel

2 Antonio

2 Eva

3 Carmen

3 Andrés

4 Carmelo

Para eliminar recorridos se utilizan condiciones en WHERE o en el propio CONNECT. De modo que: SELECT LEVEL, nombre

FROM Empleado

WHERE nombre<>'Eva'

START WITH nombre='Ángel'

CONNECT BY codigoJefe= PRIOR codigo;

En este ejemplo, Eva no sale en los resultados. En este otro: SELECT LEVEL, nombre

FROM Empleado

START WITH nombre='Ángel'

CONNECT BY codigoJefe= PRIOR codigo AND nombre<>'Eva';

No sale ni Eva ni sus empleados (se corta la rama entera). Usar CONNECT BY y START WITH para crear informes no es difícil, pero deben seguirse ciertas reglas básicas:

• El orden de las cláusulas debe ser siempre como sigue: 1. SELECT 2. FROM 3. WHERE 4. START WITH 5. CONNECT BY 6. ORDER BY

• PRIOR fuerza que los informes vayan desde la raíz hacia las hojas (si la columna previa es el padre) o de una hoja hacia la raíz (si la columna previa es el hijo).

Page 98: Java y Oracle 11g

Oracle /98

• Una cláusula WHERE elimina nodos del árbol, pero no así a sus descendientes (o ancestros). • Una cualificación en CONNECT BY (particularmente un no igual) elimina nodos y sus descendientes (o ancestros). • CONNECT BY no puede ser usado con una tabla enlazada en la cláusula WHERE.

11.5. Consultas de agrupación avanzada.

La expresión ROLLUP en una consulta de agrupación (GROUP BY) permite obtener los resúmenes parciales o totales sobre las funciones de agregado utilizadas en las columnas de la consulta. Por ejemplo, la siguiente consulta obtiene la nota de cada alumno en cada módulo: SELECT N.nif, M.nombre, AVG(valor) AS "Nota"

FROM Nota N JOIN Modulo M ON N.idModulo=M.idModulo

GROUP BY N.nif, M.nombre;

Como cada alumno tiene una única nota en cada módulo, el resultado de la función AVG(valor) es el mismo que valor. Pero si ahora añadimos: SELECT N.nif, M.nombre, AVG(valor) AS "Nota media"

FROM Nota N JOIN Modulo M ON N.idModulo=M.idModulo

GROUP BY ROLLUP (N.nif, M.nombre);

Entonces nos añade un registro para cada alumno en el que aparece la media para ese alumno, y al final mostrará un registro con la media global de todos los alumnos. Es decir, el resultado de esa consulta podría ser algo como:

En ROLLUP se pueden unir varias columnas entre paréntesis para tratarlas como si fueran una unidad: SELECT N.nif, M.nombre, AVG(valor) AS "Nota media"

FROM Nota N JOIN Modulo M ON N.idModulo=M.idModulo

GROUP BY ROLLUP ( (N.nif, M.nombre) );

La diferencia respecto a la anterior es que sólo muestra un resumen global para alumno y módulo. Es decir, sólo una fila resumen donde todos los campos involucrados quedan a nulo.

NIF NOMBRE NOTA MEDIA

11111111A Modulo1 5

11111111A Modulo2 6

22222222B Modulo1 4

22222222B Modulo2 7

33333333C Modulo1 8

33333333C Modulo2 9

6,5

O se puede aplicar el ROLLUP sobre una única expresión del GROUP BY: SELECT N.nif, M.nombre, AVG(valor) AS "Nota media"

FROM Nota N JOIN Modulo M ON N.idModulo=M.idModulo

GROUP BY ROLLUP (N.nif), M.nombre;

Con lo cual se obtiene filas de resumen donde sólo los campos incluidos en ROLLUP estarán a nulo:

NIF NOMBRE NOTA MEDIA

11111111A Modulo1 5

22222222B Modulo1 4

33333333C Modulo1 8

Modulo1 5,6

NIF NOMBRE NOTA MEDIA

11111111A Modulo1 5

11111111A Modulo2 6

11111111A 5,5

22222222B Modulo1 4

22222222B Modulo2 7

22222222B 5,5

33333333C Modulo1 8

33333333C Modulo2 9

33333333C 8,5

6,5

Page 99: Java y Oracle 11g

Oracle /99

11111111A Modulo2 6

22222222B Modulo2 7

33333333C Modulo2 9

Modulo2 7,3

La expresión CUBE es muy similar a ROLLUP, sólo que ésta calcula todos los resúmenes relativos a los campos involucrados. Ejemplo: SELECT N.nif, M.nombre, AVG(valor) AS "Nota media"

FROM Nota N JOIN Modulo M ON N.idModulo=M.idModulo

GROUP BY CUBE (N.nif, M.nombre);

Y el resultado es:

NIF NOMBRE NOTA MEDIA

11111111A Modulo1 5

11111111A Modulo2 6

11111111A 5,5

22222222B Modulo1 4

22222222B Modulo2 7

22222222B 5,5

33333333C Modulo1 8

33333333C Modulo2 9

33333333C 8,5

Modulo1 5,6

Modulo2 7,3

6,5

Es decir, calcula medias por alumno, medias por módulo, y la media total. (Obsérvese que las filas de resumen se generan para cada combinación de campos incluidos en CUBE a nulo.) GROUPING es una función que se combina con ROLLUP o CUBE y que recibe uno o más campos, e indica si la fila muestra un resumen referido a los campos en cuestión (filas de resumen con esos campos a nulo). Si la fila es un resumen de esos campos lo marca con 1, sino lo marca con 0. Por ejemplo: SELECT N.nif, M.nombre, AVG(valor), GROUPING(N.nif) AS "SW_NIF", GROUPING(M.nombre) AS "SW_MOD"

FROM Nota N JOIN Modulo M ON N.idModulo=M.idModulo

GROUP BY CUBE (N.nif, M.nombre);

Genera el siguiente resultado:

NIF NOMBRE NOTA MEDIA SW_NIF SW_MOD

11111111A Modulo1 5 0 0

11111111A Modulo2 6 0 0

11111111A 5,5 0 1

22222222B Modulo1 4 0 0

22222222B Modulo2 7 0 0

22222222B 5,5 0 1

33333333C Modulo1 8 0 0

33333333C Modulo2 9 0 0

33333333C 8,5 0 1

Modulo1 5,6 1 0

Modulo2 7,3 1 0

6,5 1 1

Se utiliza sobre todo para preparar una consulta en la creación de informes. GROUPING SETS se trata de una mejora de Oracle 9i que permite realizar varias agrupaciones para la misma consulta. Sintaxis: SELECT...

...

GROUP BY GROUPING SETS (listaDeCampos1) [, (lista2)...]

Las listas indican los campos por los que se realiza la agrupación. Por ejemplo, para obtener resúmenes que cuenten las faltas por alumno, módulo y año: SELECT M.nif, F.idModulo, EXTRACT(year FROM fecha) AS "Año", COUNT(*) AS "Nº faltas"

FROM Matricula M JOIN Falta F ON M.idMatricula=F.idMatricula

Page 100: Java y Oracle 11g

Oracle /100

GROUP BY GROUPING SETS ((M.nif, F.idModulo), (EXTRACT(year FROM fecha)));

Se pueden combinar agrupaciones de diversas formas creando consultas como: SELECT M.nif, F.idModulo, EXTRACT(year FROM fecha) AS "Año", COUNT(*) AS "Nº faltas"

FROM Matricula M JOIN Falta F ON M.idMatricula=F.idMatricula

GROUP BY M.nif, ROLLUP(F.idModulo) , CUBE(EXTRACT(year FROM fecha)) ;

Que mostraría un informe espectacular sobre las tablas anteriores. Así como: SELECT M.nif, F.idModulo, EXTRACT(year FROM fecha) AS "Año", COUNT(*) AS "Nº faltas"

FROM Matricula M JOIN Falta F ON M.idMatricula=F.idMatricula

GROUP BY GROUPING SETS ( M.nif, EXTRACT(year FROM fecha) ) ,

GROUPING SETS ( F.idModulo, EXTRACT(year FROM fecha) );

Page 101: Java y Oracle 11g

Oracle /101

III. PL/SQL

PL/SQL amplia SQL con los elementos característicos de los lenguajes de programación: variables, sentencias de control de flujo, bucles, etc.

1. Estructura del lenguaje PL/SQL

Con PL/SQL vamos a poder programar las unidades procedimentales de la base de datos Oracle; éstas son: - Procedimientos almacenados - Funciones - Triggers - Scripts

Pero además, PL/SQL nos permite realizar programas sobre las siguientes herramientas de Oracle: - Oracle Forms - Oracle Reports - Oracle Graphics - Oracle Aplication Server

1.1. Fundamentos de PL/SQL.

Para programar en PL/SQL es necesario conocer sus fundamentos. PL/SQL no es "case-sensitive", es decir, no diferencia mayúsculas de minúsculas como otros lenguajes de programación como C o Java. Sin embargo, debemos recordar que Oracle es "case-sensitive" en la búsqueda de texto. 1.1.1. Tipos de datos. Los tipos de datos escalares que soporta PL/SQL se muestran a continuación:

BINARY_DOUBLE* FLOAT NUMBER SMALLINT

BINARY_FLOAT* INT NUMERIC STRING

BINARY_INTEGER INTEGER NVARCHAR2 TIMESTAMP

BOOLEAN INTERVAL DAY

TO SECOND

PLS_INTEGER TIMESTAMP WITH

LOCAL TIME ZONE

CHAR INTERVAL YEAR

TO MONTH

POSITIVE TIMESTAMP WITH

TIME ZONE

CHARACTER LONG POSITIVEN UROWID

DATE LONG RAW RAW VARCHAR

DEC NATURAL REAL VARCHAR2

DECIMAL NATURALN ROWID

DOUBLE PRECISION NCHAR SIGNTYPE

* Desde Oracle Database 10g, los nuevos tipos de datos BINARY_FLOAT y BINARY_DOUBLE pueden mejorar el rendimiento en aplicaciones que trabajan con número intensivamente, como en procesos de datos científicos.

Los tipos de datos compuestos que soporta PL/SQL se muestran a continuación:

RECORD TABLE VARRAY

Los tipos de datos por referencia que soporta PL/SQL se muestran a continuación:

REF CURSOR REF tipo_objeto

Los tipos de datos largos (LOB) que soporta PL/SQL se muestran a continuación:

BFILE BLOB CLOB** NCLOB**

** Podemos convertir implícitamente de CLOB a NCLOB o de NCLOB a CLOB. Sin embargo, ya que puede ser una operación expansiva, podemos ayudarnos de las funciones TO_CLOB y TO_NCLOB.

1.1.2. Comentarios. PL/SQL soporta varios estilos de comentarios, el de línea simple y de multilínea, para lo cual son empleados ciertos caracteres especiales como son: -- Línea simple

Page 102: Java y Oracle 11g

Oracle /102

REM Línea simple

/*

Conjunto de Líneas

*/

1.2. Estructuras de control en PL/SQL.

Dentro de las órdenes de un bloque PL/SQL también existen sentencias de control del flujo del programa. 1.2.1. Bloque condicional «IF». El mandato IF tiene la siguiente sintaxis: IF condición THEN

-- hacer algo;

[ELSE

-- hacer algo;]

ENDIF;

Si queremos encadenar varios bloques IF debemos hacerlo de la siguiente manera: IFV=1 THEN

-- hacer algo;

ELSE

IFV=2THEN

-- hacer algo;

ELSE

-- hacer algo;

END IF;

END IF;

1.2.2. Bloque «CASE». El mandato CASE permite seleccionar entre una secuencia de condiciones, y ejecutar una instrucción correspondiente. El comando CASE evalúa una expresión y la compara con varios valores, o evalúa varias expresiones lógicas y elige la primera verdadera. Un ejemplo del primer caso es el siguiente: n := 1;

CASE n

WHEN 1 THEN

-- hacer algo;

WHEN 2 THEN

-- hacer algo;

WHEN 3 THEN

-- hacer algo;

ELSE

-- hacer algo;

END CASE;

Un ejemplo del segunda caso es el siguiente: n := 1;

CASE

WHEN n=1 THEN

-- hacer algo;

WHEN n>=2 AND n<4 THEN

-- hacer algo;

WHEN n>=4 THEN

-- hacer algo;

END CASE;

1.2.3. Bucles. Otro tipo de sentencias de control del flujo de ejecución son los bucles, dentro de los cuales podemos encontrar bucles tales como LOOP, FOR y WHILE. La sintaxis de estos bloques de código es la siguiente: LOOP

sentencias;

...

EXIT [WHEN condición];

Page 103: Java y Oracle 11g

Oracle /103

END LOOP;

Y para la orden WHILE tenemos la siguiente sintaxis: WHILE condición LOOP

sentencias;

...

END LOOP;

Podemos finalizar cualquiera de los bucles con la instrucción EXIT o bien EXIT WHEN condición. 1.2.4. Bucle «FOR». El bucle FOR permite iterar sobre un rango de valores o sobre el resultado de una consulta. El siguiente ejemplo itera sobre los valores del 10 al 1 inclusive en orden decreciente: FOR contador IN REVERSE 1..10 LOOP

DBMS_OUTPUT.PUT_LINE( contador ); -- se imprime el valor

END LOOP;

El siguiente ejemplo accede al campo nombre de la tabla Producto y los imprime: FOR p IN (SELECT nombre FROM Producto) LOOP

DBMS_OUTPUT.PUT_LINE( p.nombre ); -- se imprime el nombre

END LOOP;

1.2.5. Instrucción «NULL». Algunas cláusulas de PL/SQL, como un bloque IF y otros, deben contener al menos una instrucción ejecutable. Se puede utilizar la instrucción NULL para completar estos bloques sin que hagan nada. Por ejemplo: IF unaCondición THEN

-- hacer algo;

ELSE

NULL; -- no se hace nada

END IF;

La instrucción NULL simplemente pasa el control a la siguiente instrucción.

2. Bloques PL/SQL.

2.1. Introducción.

Un programa de PL/SQL está compuesto por bloques, teniendo al menos que estar compuesto por un bloque. Los bloques de PL/SQL pueden ser de los siguientes tipos:

- Bloques anónimos: aquellos que no tienen un nombre. - Subprogramas: aquellos que se crean como funciones o procedimientos con un nombre.

2.2. Estructura de un Bloque.

Los bloques PL/SQL presentan una estructura específica compuesta de tres partes bien diferenciadas: - La sección declarativa, en donde se declaran todas los tipos de datos, constantes y variables que se van a utilizar en la ejecución del bloque. - La sección de ejecución, que incluye las instrucciones a ejecutar en el bloque PL/SQL. - La sección de excepciones, en donde se definen los manejadores de errores que soportará el bloque.

Cada una de las partes anteriores se delimita por una palabra reservada, de modo que un bloque PL/SQL se puede representar como sigue: DECLARE | IS | AS

/*Parte declarativa*/

BEGIN

/*Parte de ejecución*/

EXCEPTION

/*Parte de excepciones*/

END;

De las anteriores partes, únicamente la sección de ejecución es obligatoria, quedando delimitada entre las cláusulas BEGIN y END. Veamos un ejemplo de bloque PL/SQL muy genérico. Se trata de un bloque anónimo, es decir, no lo identifica ningún nombre. Los bloques anónimos identifican su parte declarativa con la palabra reservada DECLARE. DECLARE

Page 104: Java y Oracle 11g

Oracle /104

/*Parte declarativa*/

nombre_variable DATE;

BEGIN

/*Parte de ejecución

*Este código asigna la fecha actual a la variable identificada por "nombre_variable"

*/

SELECT SYSDATEINTO nombre_variable FROM DUAL;

EXCEPTION

/*Parte de excepciones*/

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE('Se ha producido un error');

END;

2.3. Sección de declaración de variables.

En esta parte se declaran las variables que va a necesitar nuestro programa. Una variable se declara asignándole un nombre o "identificador" seguido del tipo de valor que puede contener. También se declaran cursores, de gran utilidad para la consulta de datos, y excepciones definidas por el usuario. También podemos especificar si una variable será constante, si puede contener valores nulos y asignarle un valor inicial. La sintaxis genérica para la declaración de constantes y variables es: nombre_variable [CONSTANT] tipo_de_dato [NOT NULL] [:=valor_inicial]

Donde: tipo_de_dato es el tipo de dato que va a poder almacenar la variable, éste puede ser cualquiera de los tipos soportados por Oracle. CONSTANT indica la definición de una constante, cuyo valor no puede ser modificado. Se debe incluir la inicialización de la constante en su declaración. NOT NULL impide que a una variable se le asigne el valor nulo, y por tanto debe inicializarse a un valor diferente de NULL.

La inicialización puede incluir cualquier expresión legal de PL/SQL, que lógicamente debe corresponderse con el tipo del identificador definido. Los tipos escalares incluyen los definidos en SQL, más los tipos VARCHAR y BOOLEAN. Este último puede tomar los valores TRUE, FALSE y NULL, y se suele utilizar para almacenar el resultado de alguna operación lógica. También es posible definir el tipo de una variable o constante, dependiendo del tipo de otro identificador, mediante la utilización de las cláusulas %TYPE y %ROWTYPE. Mediante la primera opción se define una variable o constante escalar, y con la segunda se define una variable de tipo registro, donde el identificador puede ser otra variable de tipo registro o una tabla. Habitualmente se utiliza %TYPE para definir la variable del mismo tipo que tenga definido un campo en una tabla de la base de datos, mientras que %ROWTYPE se utiliza para declarar variables utilizando cursores. Ejemplos de estructura de un bloque anónimo. DECLARE

/* Se declara la variable de tipo VARCHAR2(15) identificada por v_location y se le asigna el valor "Granada"*/

v_location VARCHAR2(15) := 'Granada';

/*Se declara la constante de tipo NUMBER identificada por PI y se le asigna el valor 3,1416*/

PI CONSTANT NUMBER := 3.1416;

/*Se declara la variable del mismo tipo que tenga el campo nombre de la tabla tabla_empleados

* identificada por v_nombre y no se le asigna ningún valor */

v_nombre tabla_empleados.nombre%TYPE;

/*Se declara la variable del tipo registro correspondiente a un supuesto cursor, llamado

* micursor, identificada por reg_datos */

reg_datos micursor%ROWTYPE;

BEGIN

/*Parte de ejecución*/

EXCEPTION

/*Parte de excepciones*/

END;

Ejemplo de estructura de un subprograma: CREATE PROCEDURE simple_procedure IS

Page 105: Java y Oracle 11g

Oracle /105

/* Se declaran variables */

BEGIN

/*Parte de ejecución*/

EXCEPTION

/*Parte de excepciones*/

END;

2.4. El paquete «DBMS_OUTPUT».

El paquete DBMS_OUTPUT permite enviar mensajes desde un bloque PL (procedimiento, función, paquete o trigger) de la base de datos. Los procedimientos PUT y PUT_LINE de este paquete permiten colocar datos en un búfer que puede ser leído por otro bloque PL, que empleará el procedimiento GET_LINE para recuperar la información. Si no se gestiona la recuperación y presentación de los datos incluidos en el búfer y si la ejecución no se realiza bajo SQL*Plus, los datos son ignorados. El principal interés de este paquete es facilitar la depuración de los programas. Los procedimientos incluidos en este paquete son los siguientes: DBMS_OUTPUT.DISABLE

Desactiva DBMS_OUTPUT y reinicializa el tamaño del búfer al valor por defecto. DBMS_OUTPUT.ENABLE(buffer_size IN INTEGER DEFAULT 20000)

Activa DBMS_OUTPUT y asigna el tamaño del búfer, que puede ser entre 1 y 1000000. DBMS_OUTPUT.GET_LINE(line OUT VARCHAR2, status OUT INTEGER);

Retorna una única línea del búfer. El siguiente ejemplo muestra como usar este procedimiento: DECLARE

bufer VARCHAR2(100);

estado INTEGER;

BEGIN

DBMS_OUTPUT.PUT_LINE('Esto es');

DBMS_OUTPUT.PUT_LINE('una prueba.');

DBMS_OUTPUT.GET_LINE(bufer, estado);

DBMS_OUTPUT.PUT_LINE('Búfer: ' || bufer); -- Búfer: Esto es

DBMS_OUTPUT.PUT_LINE('Estado: ' || TO_CHAR(estado)); -- Estado: 0

END;

/

DBMS_OUTPUT.GET_LINES(lines OUT CHARARR, numlines IN OUT INTEGER);

Recupera un array de líneas desde el búfer. El siguiente ejemplo muestra cómo usar este procedimiento: DECLARE

outtab DBMS_OUTPUT.CHARARR; -- un array de líneas de texto

fetchln INTEGER := 15;

BEGIN

outtab(1) := 'Esto es una prueba';

outtab(12) := 'de DBMS_OUTPUT.GET_LINES';

DBMS_OUTPUT.PUT_LINE('A: ' || outtab(1));

DBMS_OUTPUT.PUT_LINE('A: ' || outtab(12));

DBMS_OUTPUT.GET_LINES(outtab, fetchln);

DBMS_OUTPUT.PUT_LINE(TO_CHAR(fetchln)); -- 2

FOR i IN 1 .. fetchln LOOP

DBMS_OUTPUT.PUT_LINE('B: ' || outtab(i)); -- B: A: Esto es una prueba

END LOOP; -- B: A: de DBMS_OUTPUT.GET_LINES

END;

/

DBMS_OUTPUT.GET_LINES( linesOUT DBMSOUTPUT_LINESARRAY,numlines IN OUT INTEGER);

Recupera un array de líneas desde el búfer. El siguiente ejemplo muestra cómo usar este procedimiento: DECLARE

lo DBMSOUTPUT_LINESARRAY := DBMSOUTPUT_LINESARRAY(10);

fetchln INTEGER := 15;

BEGIN

lo(1) := 'ABC';

lo.EXTEND;

Page 106: Java y Oracle 11g

Oracle /106

lo(2) := 'DEF';

lo.EXTEND;

lo(3) := 'GHI';

lo.EXTEND;

lo(4) := 'JKL';

lo.EXTEND;

lo(5) := 'MNO';

DBMS_OUTPUT.PUT_LINE('A: ' || lo(1));

DBMS_OUTPUT.PUT_LINE('A: ' || lo(2));

DBMS_OUTPUT.PUT_LINE('A: ' || lo(3));

DBMS_OUTPUT.PUT_LINE('A: ' || lo(4));

DBMS_OUTPUT.PUT_LINE('A: ' || lo(5));

DBMS_OUTPUT.GET_LINES(lo, fetchln);

DBMS_OUTPUT.PUT_LINE(TO_CHAR(fetchln)); -- 5

END;

/

DBMS_OUTPUT.NEW_LINE;

Inserta una marca de fin de línea en el búfer. DBMS_OUTPUT.PUT_LINE(a IN VARCHAR2);

Escribe una línea de texto al búfer. Con el procedimiento GET_LINE se puede leer una línea de datos del búfer y con el procedimiento GET_LINES se puede extraer una tabla de filas. Después de leer el búfer, todas las líneas que no se han leído al hacer otra llamada a los procedimientos PUT, PUT_LINE o NEW_LINE se eliminan, con el fin de evitar cualquier posible confusión acerca de los datos. También podemos generar un mensaje hacia consola mediante DBMS_OUTPUT. En consolas de SQL*Plus, previamente debemos activar el depurador activando la variable SERVEROUTPUT. Por ejemplo: SET SERVEROUTPUT ON;

BEGIN

DBMS_OUTPUT.PUT_LINE ('Hola');

END;

2.5. Asignación de variables.

Dentro de un bloque podemos asignar valores a las variables de varias formas: • Directamente mediante el operador de asignación (:=). Por ejemplo:

DECLARE

Valor NUMBER(38,2);

BEGIN

Valor := 213.23;

END;

• A través de una consulta mediante el operador INTO. Por ejemplo: DECLARE

Nota_minima NUMBER(2,0);

Nota_maxima NUMBER(2,0)

BEGIN

SELECT MIN(valor), MAX(valor) INTO Nota_minima, Nota_maxima FROM Nota;

END;

Debemos tener en cuenta que si la consulta no retorna registros o retorna más de un registro se generará una excepción.

• A través de una instrucción de borrado mediante RETURNING...INTO. Por ejemplo: DECLARE

nota_V NUMBER(2,0);

BEGIN

DELETE FROM Nota WHERE idNota=9

RETURNING valor INTO nota_V;

END;

Si queremos recuperar todos los valores de un registro podemos asignarlos a una variable de tipo registro de la siguiente forma:

DECLARE

Page 107: Java y Oracle 11g

Oracle /107

fila_notaNota%ROWTYPE;

BEGIN

SELECT * INTO fila_nota FROM Nota WHERE idNota = 34;

DBMS_OUTPUT.PUT_LINE (fila_nota.valor); -- Imprime el valor de nota del registro recuperado

END;

3. Excepciones en PL/SQL.

3.1. Manejo de excepciones.

En PL/SQL una advertencia o condición de error es llamada una excepción. Las excepciones se controlan dentro de su propio bloque. Cuando ocurre un error se ejecuta la porción del programa marcada por el bloque EXCEPTION, transfiriéndose el control a ese bloque de sentencias. El siguiente ejemplo muestra un bloque de excepciones que captura las excepciones NO_DATA_FOUND y ZERO_DIVIDE. Cualquier otra excepción será capturada en el bloque WHEN OTHERS THEN. DECLARE

-- Declaraciones

BEGIN

-- Ejecución

EXCEPTION

WHEN NO_DATA_FOUND THEN

-- Se ejecuta cuando ocurre una excepción de tipo NO_DATA_FOUND

WHEN ZERO_DIVIDE THEN

-- Se ejecuta cuando ocurre una excepción de tipo ZERO_DIVIDE

WHEN OTHERS THEN

-- Se ejecuta cuando ocurre una excepción de un tipo no tratado en los bloques anteriores

END;

Si existe un bloque de excepción apropiado para el tipo de excepción se ejecuta dicho bloque. Si no existe un bloque de control de excepciones adecuado al tipo de excepción se ejecutará el bloque de excepción WHEN

OTHERS THEN (¡si existe!). WHEN OTHERS debe ser el último manejador de excepciones. Las excepciones pueden ser definidas en forma interna o explícitamente por el usuario. Ejemplos de excepciones definidas en forma interna son la división por cero y la falta de memoria en tiempo de ejecución. Estas mismas condiciones excepcionales tienen sus propio tipos y pueden ser referenciadas por ellos: ZERO_DIVIDE y STORAGE_ERROR. Las excepciones definidas por el usuario deben ser lanzadas explícitamente utilizando la sentencia RAISE.

3.2. Excepciones predefinidas.

PL/SQL proporciona un gran número de excepciones predefinidas que permiten controlar las condiciones de error más habituales. Las excepciones predefinidas no necesitan ser declaradas. Simplemente se utilizan cuando éstas son lanzadas por algún error determinado. La siguiente es la lista de las excepciones predeterminadas por PL/SQL y una breve descripción de cuándo son accionadas:

Excepción Se ejecuta ... SQLCODE

ACCESS_INTO_NULL El programa intentó asignar valores a los atributos de un objeto no inicializado

-6530

COLLECTION_IS_NULL El programa intentó asignar valores a una tabla anidada aún no inicializada

-6531

CURSOR_ALREADY_OPEN El programa intentó abrir un cursor que ya se encontraba

abierto. Recuerde que un cursor de ciclo FOR automáticamente

lo abre y ello no se debe especificar con la sentencia OPEN

-6511

DUP_VAL_ON_INDEX El programa intentó almacenar valores duplicados en una columna que se mantiene con restricción de integridad de un índice de unicidad

-1

INVALID_CURSOR El programa intentó efectuar una operación no válida sobre un cursor

-1001

INVALID_NUMBER En una sentencia SQL, la conversión de una cadena de caracteres hacia un número falla cuando esa cadena no representa un número válido

-1722

Page 108: Java y Oracle 11g

Oracle /108

LOGIN_DENIED El programa intentó conectarse a Oracle con un nombre de usuario o contraseña inválido

-1017

NO_DATA_FOUND Una sentencia SELECT INTO no devolvió valores o el programa referenció un elemento no inicializado en una tabla indexada

100

NOT_LOGGED_ON El programa efectuó una llamada a Oracle sin estar conectado -1012

PROGRAM_ERROR PL/SQL tiene un problema interno -6501

ROWTYPE_MISMATCH Los elementos de una asignación (el valor a asignar y la variable que lo contendrá) tienen tipos incompatibles. También se presenta este error cuando un parámetro pasado a un subprograma no es del tipo esperado

-6504

SELF_IS_NULL El parámetro SELF (el primero que es pasado a un método MEMBER) es nulo

-30625

STORAGE_ERROR La memoria se terminó o está corrupta -6500

SUBSCRIPT_BEYOND_COUNT El programa está tratando de referenciar un elemento de un arreglo indexado que se encuentra en una posición más grande que el número real de elementos de la colección

-6533

SUBSCRIPT_OUTSIDE_LIMIT El programa está referenciando un elemento de un arreglo utilizando un número fuera del rango permitido (por ejemplo, el elemento "-1")

-6532

SYS_INVALID_ROWID La conversión de una cadena de caracteres hacia un tipo ROWID falló porque la cadena no representa un número

-1410

TIMEOUT_ON_RESOURCE Se excedió el tiempo máximo de espera por un recurso en Oracle

-51

TOO_MANY_ROWS Una sentencia SELECT INTO devuelve más de una fila -1422

VALUE_ERROR Ocurrió un error aritmético, de conversión o truncamiento. Por ejemplo, sucede cuando se intenta calzar un valor muy grande dentro de una variable más pequeña

-6502

ZERO_DIVIDE El programa intentó efectuar una división por cero -1476

3.3. Excepciones definidas por el usuario.

PL/SQL permite al usuario definir sus propias excepciones, las que deberán ser declaradas y lanzadas explícitamente utilizando la sentencia RAISE. Las excepciones deben ser declaradas en el segmento de declaración de un bloque, subprograma o paquete. Se declara una excepción como cualquier otra variable, asignándole el tipo EXCEPTION. Las mismas reglas de alcance que se aplican a las variables también se aplican sobre las excepciones. DECLARE

MyExcepcion EXCEPTION;

BEGIN

-- Algún código que lanza la excepción personalizada

EXCEPTION

WHEN MyExcepcion THEN

-- Se ha capturado la excepción personalizada

END;

3.3.1. Reglas de alcance. Una excepción es válida dentro de su ámbito de alcance, es decir, el bloque o programa donde ha sido declarada. Las excepciones predefinidas son siempre válidas. Como las variables, una excepción declarada en un bloque es local a ese bloque y global a todos los sub-bloques que comprende. 3.3.2. La sentencia «RAISE». La sentencia RAISE permite lanzar una excepción en forma explícita. Es posible utilizar esta sentencia en cualquier lugar que se encuentre dentro del alcance de la excepción. DECLARE

-- Declaramos una excepción identificada por VALOR_NEGATIVO

VALOR_NEGATIVO EXCEPTION;

valor NUMBER;

BEGIN

Page 109: Java y Oracle 11g

Oracle /109

valor := -1;

IF valor < 0 THEN

RAISE VALOR_NEGATIVO;

END IF;

EXCEPTION

WHEN VALOR_NEGATIVO THEN

DBMS_OUTPUT.PUT_LINE('El valor no puede ser negativo');

END;

Con la sentencia RAISE podemos lanzar una excepción definida por el usuario o predefinida, siendo el comportamiento habitual lanzar excepciones definidas por el usuario.

3.4. Uso de «SQLCODE» y «SQLERRM».

Al manejar una excepción es posible usar las funciones predefinidas SQLCODE y SQLERRM para aclarar al usuario la situación de error acontecida. SQLCODE devuelve el número del error de Oracle y un 0 (cero) en caso de éxito al ejecutarse una sentencia SQL. Por otra parte, SQLERRM devuelve el correspondiente mensaje de error. El siguiente ejemplo muestra el uso de estas dos funciones: DECLARE

err_num NUMBER;

err_msg VARCHAR2(255);

result NUMBER;

BEGIN

SELECT 1/0 INTO result FROM DUAL;

EXCEPTION

WHEN OTHERS THEN

err_num := SQLCODE;

err_msg := SQLERRM;

DBMS_OUTPUT.PUT_LINE('Error:'||TO_CHAR(err_num));

DBMS_OUTPUT.PUT_LINE(err_msg);

END;

También es posible entregarle a la función SQLERRM un número negativo que represente un error de Oracle y ésta devolverá el mensaje asociado. DECLARE

msg VARCHAR2(255);

BEGIN

msg := SQLERRM(-1403);

DBMS_OUTPUT.PUT_LINE(MSG);

END;

3.5. Excepciones personalizadas en PL/SQL.

En ocasiones queremos enviar un mensaje de error personalizado al producirse una excepción PL/SQL. Para ello es necesario utilizar la instrucción RAISE_APPLICATION_ERROR. La sintaxis general es la siguiente: RAISE_APPLICATION_ERROR ( error_num , mensaje );

Siendo error_num es un entero negativo comprendido entre -20001 y -20999, y mensaje la descripción del error.

3.6. Propagación de excepciones en PL/SQL.

Una de las características más interesantes de las excepciones es la propagación de las mismas. Cuando se lanza una excepción, el control se transfiere hasta la sección EXCEPTION del bloque donde se ha producido la excepción. Entonces se busca un manejador válido de la excepción (WHEN excepción THEN, WHEN OTHERS

THEN) dentro del bloque actual. En el caso de que no se encuentre ningún manejador válido el control del programa se desplaza hasta el bloque EXCEPTION del bloque que ha realizado la llamada PL/SQL.

4. Cursores

PL/SQL utiliza habitualmente cursores para gestionar las instrucciones SELECT. Un cursor es un objeto que gestiona el conjunto de registros afectados por una instrucción SQL. Técnicamente, los cursores son

Page 110: Java y Oracle 11g

Oracle /110

fragmentos de memoria reservados para procesar los resultados de una consulta SELECT. Podemos distinguir dos tipos de cursores:

• Cursores implícitos. Este tipo de cursores se utiliza para operaciones SELECT INTO. Normalmente se usan cuando la consulta devuelve un único registro. • Cursores explícitos. Son los cursores que son declarados y controlados por el programador. Se utilizan cuando la consulta devuelve un conjunto de registros. Ocasionalmente también se utilizan en consultas que devuelven un único registro por razones de eficiencia, ya que son más rápidos.

4.1. Cursores implícitos.

Los cursores implícitos se referencian con el identificador SQL, y poseen los siguientes atributos: • SQL%ROWCOUNT: cuenta el número de filas que fueron accedidas en la última operación. • SQL%FOUND: Posee el valor TRUE si el atributo SQL%ROWCOUNT es igual o mayor que uno; es decir, que se asigna a valor TRUE si se ha accedido a alguna fila. • SQL%NOTFOUND: Poseerá el valor TRUE si el atributo SQL%FOUND tiene el valor FALSE; es decir, este atributo toma el valor TRUE si en la última operación no se accedió a ninguna fila. • SQL%ISOPEN: este atributo indica si el cursor está abierto. Posee siempre el valor FALSE, porque PL/SQL cierra los cursores implícitos inmediatamente después de ser ejecutados.

Como ejemplo de uso de un cursor implícito, el siguiente boque PL/SQL imprime el número de columnas que han sido borradas: DECLARE

v_id NUMBER:=605;

BEGIN

DELETE FROM Nota WHERE idModulo =v_id;

DBMS_OUTPUT.PUT_LINE( SQL%ROWCOUNT );

END;

Para operaciones de tipo SELECT, los cursores implícitos sólo pueden devolver una fila, por lo que pueden producirse determinadas excepciones. Las más comunes que se pueden encontrar son NO_DATA_FOUND y TOO_MANY_ROWS.

4.2. Cursores explícitos.

Los cursores explícitos son definidos por el usuario, y a la zona de memoria del cursor se le asigna una sentencia SELECT. Para controlar un cursor explícito debemos seguir los siguientes pasos:

1) Declarar el cursor en el bloque declarativo. 2) Abrir el cursor con el comando OPEN. Con ello se cursor se sitúa sobre el primer resultado del comando asociado. 3) Leer la primera fila con el comando FETCH. 4) Comprobar si se ha leído algo, y si es necesario seguir leyendo con FETCH. 5) Cerrar el cursor con el comando CLOSE.

La sintaxis de declaración de un cursor, es la siguiente: CURSOR nombreCursor IS sentencia_Select;

Donde sentencia_Select es una sentencia SELECT cualquiera, que puede incluir cualquier tipo de restricción. La sintaxis más básica de los comandos OPEN y CLOSE es: OPEN nombreCursor;

CLOSE nombreCursor;

Cuando queramos recuperar información del cursor, deberemos utilizar el comando FETCH con la siguiente sintaxis: FETCH nombreCursor INTO variable1, variable2, ...;

Este comando devolverá la fila actual cada vez que sea ejecutado, y avanzará a la siguiente posición dentro del conjunto de filas que devuelve la consulta especificada en la declaración del cursor. El funcionamiento de los atributos de los cursores vistos anteriormente (%ROWCOUNT, %FOUND, %NOTFOUND, %ISOPEN) es similar en los cursores explícitos, solo que el valor de %ISOPEN puede ser TRUE en este caso si el cursor esta abierto. Existe un tipo de bucle específico para cursores, que ejecuta automáticamente las órdenes OPEN, CLOSE y FETCH. Se trata de una variante del bucle FOR, cuya sintaxis es la siguiente: FOR fila IN nombreCursor LOOP

-- La variable "fila" es definida automáticamente del tipo de registro del cursor

-- y contiene los datos del registro actual

Page 111: Java y Oracle 11g

Oracle /111

...

END LOOP;

En caso de usar otro tipo de bucle, se debería especificar la condición de salida del mismo (normalmente cuando el cursor haya recorrido todas las filas); en ese caso la condición de salida será: EXIT WHEN nombreCursor%NOTFOUND;

Por ejemplo, veamos dos formas de realizar la misma acción: una mediante el bucle FOR antes mencionado, y otra mediante la forma clásica. Así, mediante la forma clásica, podríamos crear un cursor para mostrar el nif y apellidos de todos los alumnos: DECLARE

CURSOR c1 ISSELECT nif, apellidos FROM Alumno;

c1_record c1%ROWTYPE;

BEGIN

OPEN c1;

LOOP

FETCH c1 INTO c1_record;

EXIT WHEN c1%NOTFOUND;

DBMS_OUTPUT.PUT_LINE( c1_record.nif );

DBMS_OUTPUT.PUT_LINE( c1_record.apellidos );

END LOOP;

CLOSE c1;

END;

Sin embargo, esto puede realizarse de forma mucho más sencilla, utilizando el bucle LOOP de la siguiente manera: DECLARE

CURSOR c1 ISSELECT nif, apellidos FROM Alumno;

BEGIN

FOR fila IN c1 LOOP

DBMS_OUTPUT.PUT_LINE( fila.nif );

DBMS_OUTPUT.PUT_LINE( fila.apellidos );

END LOOP;

END;

Esta misma operación puede realizarse mediante un cursor implícito: BEGIN

FOR fila IN (SELECT nif, apellidos FROM Alumno ) LOOP

DBMS_OUTPUT.PUT_LINE( fila.nif );

DBMS_OUTPUT.PUT_LINE( fila.apellidos );

END LOOP;

END;

4.3. Cursores con parámetros.

Los cursores explícitos pueden incluir parámetros para poder trabajar con datos que son establecidos después de la declaración del cursor. Por ejemplo, el siguiente código crea un cursor para recorrer los registros de alumnos de una localidad determinada: DECLARE

CURSOR c1 (p_localidadVARCHAR2) IS SELECT nif, apellidos FROM Alumno WHERE localidad=p_localidad;

BEGIN

FOR fila IN c1 ('Madrid')LOOP

DBMS_OUTPUT.PUT_LINE( fila.nif );

DBMS_OUTPUT.PUT_LINE( fila.apellidos );

END LOOP;

END;

El valor del parámetro es pasado en este caso al referenciar el cursor en la instrucción FOR. En otros casos se pasará el parámetro al abrir el cursor con la instrucción OPEN: OPEN c1('Madrid');

4.4. Cursores de actualización.

Se utilizan los cursores de actualización para determinar los registros afectados por una operación UPDATE. Para ello, tanto el cursor como el comando UPDATE deben utilizar la misma tabla base.

Page 112: Java y Oracle 11g

Oracle /112

Los cursores de actualización se declaran igual que los cursores explícitos, añadiendo FOR UPDATE al final de la sentencia SELECT. CURSOR nombre_cursor IS

instrucción_SELECT

FOR UPDATE

Para utilizar este tipo de cursor hay que ejecutar una sentencia UPDATE especificando la cláusula «WHERE

CURRENT OF nombre_cursor». La sintaxis es: UPDATE nombre_tabla SET

campo_1 = valor_1

[, campo_2 = valor_2]

WHERE CURRENT OF nombre_cursor

El siguiente ejemplo muestra el uso de un cursor de actualización para actualizar las notas de los alumnos de Madrid, incrementándolas en una unidad: DECLARE

CURSOR c1 IS SELECT idNota FROM Nota WHERE nif IN

(SELECT nif FROM Alumno WHERE localidad='Madrid') FOR UPDATE;

v_idNota.idNota%TYPE;

BEGIN

OPEN c1;

FETCH c1 INTO v_id;

WHILE c1%FOUND LOOP

UPDATE NotaSET valor = valor + 1 WHERE CURRENT OF c1;

FETCH c1 INTO v_id;

END LOOP;

CLOSE c1;

COMMIT;

END;

Cuando trabajamos con cursores de actualización debemos tener en cuenta que generan bloqueos en la base de datos.

5. Subprogramas en PL/SQL

Las reglas del negocio sofisticadas y la lógica de la aplicación pueden ser almacenadas como procedimientos y funciones dentro de Oracle. Los procedimientos almacenados (grupos de SQL, PL/SQL y comandos Java) permiten mover código que fuerza las reglas del negocio de nuestras aplicaciones a la base de datos. Como resultado, el código será guardado una vez para ser usado por varias aplicaciones. Con este soporte de subprogramas almacenados por parte de Oracle, el código dentro de nuestras aplicaciones puede ser más consistente y fácil de mantener. Además, podemos agrupar subprogramas, variables y tipos personalizados de PL/SQL dentro de paquetes. Podemos experimentar beneficios de rendimiento usando procedimientos, por dos razones:

• El procesamiento de reglas del negocio complejas puede ser realizadas dentro de la base de datos (y por tanto por el servidor). En el servidor cliente o en las aplicaciones de la capa intermedia, al mover procesamientos complejos desde la aplicación a la base de datos, se puede mejorar significativamente el rendimiento. • Ya que el código del procedimiento es almacenado dentro de la base de datos y es limpiamente estático, podemos beneficiarnos de reutilizar las mismas consultas dentro de la base de datos. El área compartida en el SGA almacenará las versiones parseadas de los comandos ejecutados. Por lo tanto, la segunda vez que un comando es ejecutado, se puede tomar la ventaja de que ha sido parseado previamente, aumentando así el rendimiento de la ejecución del procedimiento.

Además de estas dos ventajas, nuestro esfuerzo de desarrollo puede también beneficiarse. Las reglas del negocio consolidadas dentro de la base de datos no necesitan ser escritas en cada aplicación, con lo cual se ahorra tiempo en la creación de la aplicación y se simplifica su proceso de mantenimiento.

5.1. Permisos requeridos.

Para crear un objeto procedimental debemos tener el permiso de sistema CREATE PROCEDURE. Si el objeto procedimental es usado en otro esquema entonces debemos tener el permiso de sistema CREATE ANY

PROCEDURE.

Page 113: Java y Oracle 11g

Oracle /113

5.1.1. Ejecución de procedimientos. Una vez que el objeto procedimental ha sido creado podemos ejecutarlo. Cuando un procedimiento es ejecutado podemos confiar en los permisos sobre tablas de sus propietarios o podemos confiar en los permisos del usuario que lo está ejecutando. Cuando un procedimiento es creado usando los permisos de su definidor, un usuario que ejecute el procedimiento no necesita que le concedan permisos sobre las tablas a las que accede el procedimiento. Si un procedimiento confía en los derechos del invocador, el usuario debe tener acceso a todos los objetos accedidos por el procedimiento. Para permitir a otros usuarios ejecutar nuestros objetos procedimentales, debemos concederles el permiso EXECUTE sobre el objeto, tal como se muestra a continuación: GRANT EXECUTE ON un_precedimiento TO un_usuario;

Si no concedemos el permiso EXECUTE al usuario, debe tener el permiso de sistema EXECUTE ANY PROCEDURE para poder ejecutar el procedimiento. 5.1.2. Permisos requeridos sobre tablas. Los objetos procedimentales pueden referenciar tablas. Para que estos objetos se ejecuten apropiadamente, el propietario del procedimiento, paquete o función debe tener permisos sobre las tablas que usa. A menos que estemos usando derechos de invocador, el usuario que esté ejecutando el objeto procedimental no necesita permisos sobre las tablas subyacentes.

Nota. Los permisos necesarios para procedimientos, paquetes y funciones no pueden venir de roles; deben concederse directamente por el propietario del objeto.

5.2. Procedimientos, funciones y paquetes.

Los procedimientos no retornan un valor en su llamada. Las funciones pueden retornar un valor en su llamada y pueden ser usadas directamente en consultas. El valor de una función es retornado a través del uso de la palabra clave RETURN dentro de la función. Los paquetes son grupos de procedimientos, funciones, variables y comandos SQL agrupados dentro de una simple unidad. Para ejecutar un procedimiento dentro de un paquete debemos primero identificar el nombre del paquete y después el nombre del procedimiento, tal como se muestra a continuación: EXECUTE PAQUETE_LIBRO.NUEVO_LIBRO('Un libro');

Aquí, el procedimiento NUEVO_LIBRO dentro del paquete PAQUETE_LIBRO es ejecutado. Los paquetes permiten varios procedimientos que usan las mismas variables y cursores. Los procedimientos dentro de los paquetes pueden ser públicos o privados, en cuyo caso sólo son accesibles dentro del código del paquete.

5.3. Procedimientos almacenados.

Un procedimiento es un subprograma que ejecuta una acción específica y que no devuelve ningún valor en su llamada. Un procedimiento tiene un nombre, un conjunto de parámetros (opcional) y un bloque de código. La sintaxis de un procedimiento almacenado es la siguiente: CREATE [OR REPLACE]PROCEDURE

nombre_procedimiento [(parametro1 [IN|OUT|IN OUT] tipo, parametro2 [IN|OUT|IN OUT] tipo, ...)]

IS

-- Declaración de variables locales

BEGIN

-- Sentencias

[EXCEPTION]

-- Sentencias de control de excepción

END [nombre_procedimiento];

El uso de OR REPLACE permite sobrescribir un procedimiento existente. Si se omite, y el procedimiento existe, se producirá un error al ejecutar el comando CREATE PROCEDURE. La sintaxis es muy parecida a la de un bloque anónimo, salvo porque se reemplaza la sección DECLARE por la secuencia PROCEDURE ... IS en la especificación del procedimiento. Debemos especificar el tipo de datos de cada parámetro. Al especificar el tipo de dato del parámetro no debemos especificar la longitud del tipo. Los parámetros pueden ser de entrada (IN), de salida (OUT) o de entrada salida (IN OUT). El valor por defecto es IN, y se toma ese valor en caso de que no especifiquemos nada. Por ejemplo, el siguiente procedimiento actualiza una nota para un alumno y módulo determinados: CREATE OR REPLACEPROCEDURE

Page 114: Java y Oracle 11g

Oracle /114

Actualiza_Nota(pNif IN VARCHAR2, pIdModulo NUMBER, pNota NUMBER)

IS

-- Declaración de variables locales

BEGIN

UPDATE Nota SET valor = pNota WHERE nif=pNif AND idModulo=pIdModulo;

END Actualiza_Nota;

También podemos asignar un valor por defecto a los parámetros, utilizando la cláusula DEFAULT o el operador de asignación (:=) . CREATE PROCEDURE Actualiza_Nota(pNif IN VARCHAR2, pIdModulo NUMBER, pNota NUMBER DEFAULT 5)

. . .

Los parámetros con valores por defecto deben situarse al final de la lista de parámetros. Una vez creado y compilado el procedimiento almacenado podemos ejecutarlo. Si el sistema nos indica que el procedimiento se ha creado con errores de compilación podemos ver estos errores de compilación con la orden SHOW ERRORS en SQL *Plus. Existen dos formas de pasar argumentos a un procedimiento almacenado a la hora de ejecutarlo (en realidad es válido para cualquier subprograma). Éstas son:

▪ Notación posicional: Se pasan los valores de los parámetros en el mismo orden en que el PROCEDURE los define.

BEGIN

Actualiza_Nota('2222222B', 1,8);

COMMIT;

END;

▪ Notación nominal: Se pasan los valores en cualquier orden nombrando explícitamente el parámetro. BEGIN

Actualiza_Nota(pNif =>'2222222B',pIdModulo => 1, pNota =>8);

COMMIT;

END;

5.4. Funciones en PL/SQL.

Una función es un subprograma que devuelve un valor. La sintaxis para construir funciones es la siguiente: CREATE [OR REPLACE]FUNCTION

nombre_función [(parámetro1 IN tipo, parámetro2 IN tipo, ...)]

RETURN Tipo_de_retorno

IS

result Tipo_de_retorno;

BEGIN

RETURN result;

[EXCEPTION]

-- Sentencias de control de excepción

END [nombre_función];

El uso de OR REPLACE permite sobrescribir una función existente. Si se omite, y la función existe, se producirá un error el ejecutar el comando CREATE FUNCTION. La sintaxis de los parámetros es la misma que en los procedimientos almacenado. Como ejemplo, la siguiente función retorna la nota media de un alumno: CREATE OR REPLACEFUNCTION fn_NotaMedia (pNif VARCHAR2)RETURN NUMBER

IS

result NUMBER;

BEGIN

SELECT AVG(valor) INTO result FROM Nota WHERE nif = pNif;

RETURN result;

EXCEPTION

WHEN NO_DATA_FOUND THEN

RETURN 0;

END ;

Si el sistema nos indica que la función se ha creado con errores de compilación podemos ver estos errores de compilación con la orden SHOW ERRORS en SQL*Plus. Una vez creada y compilada la función podemos ejecutarla de la siguiente forma: DECLARE

Page 115: Java y Oracle 11g

Oracle /115

notaMedia NUMBER;

BEGIN

notaMedia := fn_NotaMedia ('2222222B');

END;

Las funciones pueden utilizarse en sentencias SQL de manipulación de datos (SELECT, UPDATE, INSERT y DELETE) siempre y cuando no realicen operaciones de actualización en su código: SELECT nif, nombre, apellidos, fn_NotaMedia( nif )FROM Alumno;

5.5. Subprogramas en bloques procedimentales.

Dentro de la sección declarativa de bloque anónimo, un procedimiento o una función almacenada podemos declarar subfunciones y subprocedimientos e invocarlos desde el bloque de ejecución del script. Este tipo de subprogramas son menos conocidos que los procedimientos almacenados, funciones y triggers, pero son enormemente útiles. El siguiente ejemplo declara y ejecuta utiliza una subfunción (fn_multiplica_x2) en un bloque anónimo DECLARE

idx NUMBER;

/* Se declara la subfunción */

FUNCTION fn_multiplica_x2(num NUMBER)

RETURN NUMBER

IS

result NUMBER;

BEGIN

result := num *2;

RETURN result;

END fn_multiplica_x2;

BEGIN

FOR idx IN 1..10

LOOP

DBMS_OUTPUT.PUT_LINE ('Llamada a la función ... '||TO_CHAR(fn_multiplica_x2(idx)));

END LOOP;

END;

Nótese que se utiliza la función TO_CHAR para convertir el resultado de la función fn_multiplica_x2 (numérico) en alfanumérico y poder mostrar el resultado por pantalla.

5.6. Depurando procedimientos.

El comando SHOW ERRORS de SQL*Plus muestra todos los errores asociados con la más reciente creación de objetos procedimentales. Este comando verifica la vista USER_ERRORS del diccionario de datos para mostrar los errores asociados con la compilación del procedimiento. SHOW ERRORS muestra la línea y número de columna de cada error, así como el texto del mensaje de error. Para ver errores asociados con procedimientos creados previamente podemos consultar USER_ERRORS directamente, tal como se muestra a continuación. Este ejemplo consulta USER_ERRORS por mensajes de error encontrados durante la creación de una función Gastos_atrasados. Si un error es encontrado, las líneas en el código que provocan condiciones de error son retornadas por la consulta. SELECT Line, /* Número de línea del error. */

Position, /* Número de columna del error dentro de la línea.*/

Text /* Texto del error.*/

FROM USER_ERRORS

WHERE Name = 'GASTOS_ATRASADOS'

AND TYPE = 'FUNCTION'

ORDER BY Sequence;

Valores válidos para la columna Type son VIEW, PROCEDURE, PACKAGE, FUNCTION, y PACKAGE BODY. Los otros dos niveles del diccionario de datos (ALL y DBA) pueden ser usados para recuperar información acerca de errores involucrados con los objetos procedimiento.

5.7. Paquetes en PL/SQL.

Un paquete es una estructura que agrupa objetos de PL/SQL compilados (procedimientos, funciones, variables, tipos, etc.) en la base de datos. Esto nos permite agrupar la funcionalidad de los procesos en programas.

Page 116: Java y Oracle 11g

Oracle /116

Lo primero que debemos tener en cuenta es que los paquetes están formados por dos partes: la especificación y el cuerpo. La especificación del un paquete y su cuerpo se crean por separado. 5.7.1. Especificación de un paquete. La especificación de un paquete es la interfaz pública del paquete que será usada por las aplicaciones. En ella es posible declarar los tipos, variables, constantes, excepciones, cursores y subprogramas disponibles para su uso posterior desde fuera del paquete. En la especificación del paquete sólo se declaran los objetos (procedimientos, funciones, variables, ...), no se implementa el código. Los objetos declarados en la especificación del paquete son accesibles desde fuera del paquete por otro script de PL/SQL o programa. Haciendo una analogía con el mundo de C, la especificación es como el archivo de cabecera de un programa en C. Para crear la especificación de un paquete la sintaxis general es la siguiente: CREATE [OR REPLACE] PACKAGE NombrePaquete

IS

-- Declaraciones de tipos y registros públicas

TYPE NombreTipo IS TipoDeDato;

-- Declaraciones de variables y constantes públicas

-- También podemos declarar cursores

NombreConstante CONSTANT TipoDato := valor;

NombreVariable TipoDato;

-- Declaraciones de procedimientos y funciones públicas

FUNCTION NombreFunción (Parámetro TipoDato , ...) RETURN TipoDato;

PROCEDURE NombreProcedimiento (Parámetro TipoDato , ...);

END NombrePaquete;

5.7.2. Cuerpo de un paquete. El cuerpo de un paquete es la implementación privada del paquete. El cuerpo del paquete debe implementar lo que se declaró inicialmente en la especificación. En el cuerpo de un paquete podemos declarar nuevos subprogramas y tipos, pero estos serán privados para el propio paquete. La sintaxis general para crear el cuerpo de un paquete es muy parecida al de la especificación, tan solo se añade la palabra clave BODY, y se implementa el código de los subprogramas. CREATE [OR REPLACE] PACKAGE BODY NombrePaquete

IS

-- Declaraciones de tipos y registros privados

TYPE NombreTipo IS TipoDato;

-- Declaraciones de variables y constantes privadas

-- También podemos declarar cursores

NombreConstante CONSTANT TipoDato := valor;

NombreVariable TipoDato;

-- Implementación de procedimientos y funciones

FUNCTION NombreFunción(Parámetro TipoDato , ...)RETURN TipoDato

IS

-- Variables locales de la función

BEGIN

-- Implementación de la función

END;

PROCEDURE NombreProcedimiento (Parámetro TipoDato , ...)

IS

-- Variables locales de la función

BEGIN

-- Implementación de procedimiento

END;

END pkgName;

El siguiente ejemplo crea un paquete llamado PKG_ACADEMIA que incluye funciones y procedimientos para procesar los datos de alumnos, matrículas, notas, etc. Para crear la especificación del paquete: CREATE OR REPLACE PACKAGE PKG_ACADEMIA

IS

-- Declaraciones de tipos y registros públicas

TYPE Reg_ResumenNotas IS RECORD (

Page 117: Java y Oracle 11g

Oracle /117

minima NUMBER(2,0),

maxima NUMBER(2,0),

media NUMBER(2,2)

);

-- Declaraciones de variables y constantes públicas

APROBADO CONSTANT INTEGER := 5;

SOBRESALIENTE CONSTANT INTEGER := 10;

ERROR_NO_EXISTE_ALUMNO EXCEPTION;

-- Declaraciones de procedimientos y funciones públicas

PROCEDURE MatriculaAlumno (pNif VARCHAR2, pNombre VARCHAR2, pApellidos VARCHAR2,

pLocalidad VARCHAR2) ;

FUNCTION fn_Obtener_Resumen_Notas (pNif VARCHAR2) RETURN Reg_ResumeNotas;

END PKG_ACADEMIA;

Aquí sólo hemos declarado las variables y constantes, y prototipado las funciones y procedimientos públicos. Es en el cuerpo del paquete donde debemos escribir el código de los subprogramas MatriculaAlumno y fn_Obtener_Resumen_Notas. CREATE PACKAGE BODY PKG_ACADEMIA IS

-- Función privada para saber si existe un alumno

FUNCTION fn_Existe_Alumno (pNif VARCHAR2) RETURN INTEGER

IS

n INTEGER;

BEGIN

SELECT COUNT(*) INTO n FROM Alumno WHERE nif = pNif;

RETURN n;

END;

-- Función pública

FUNCTION fn_Obtener_Resumen_Notas (pNif VARCHAR2) RETURN Reg_ResumeNotas

IS

result Reg_ResumenNotas;

BEGIN

IF fn_Existe_Alumno(pNif) THEN

SELECT MIN(valor), MAX(valor), AVG(valor) INTO result FROM Nota WHERE nif = pNif;

RETURN result;

ELSE

RAISE ERROR_NO_EXISTE_ALUMNO;

END IF;

END;

-- Procedimiento público

PROCEDURE MatriculaAlumno (pNif VARCHAR2, pNombre VARCHAR2, pApellidos VARCHAR2,

pLocalidad VARCHAR2)

IS

maxId INTEGER;

BEGIN

IF NOT fn_Existe_Alumno(pNif) THEN

INSERT INTO Alumno(nif,nombre,apellidos,localidad) VALUES (pNif,pNombre,pApellidos,pLocalidad);

END IF;

SELECT NVL(MAX(idMatricula, 0) INTO maxId FROM Matricula;

INSERT INTO Matricula(idMatricula,nif,año) VALUES (maxId+1,pNif,EXTRACT(year FROM SYSDATE));

END;

END PKG_ACADEMIA;

Es posible modificar el cuerpo de un paquete sin necesidad de alterar por ello la especificación del mismo. Los paquetes pueden llegar a ser programas muy complejos y suelen almacenar gran parte de la lógica de negocio. 5.7.3. Inicialización de paquetes. Los paquetes pueden incluir código que puede ser ejecutado la primera vez que un usuario ejecuta una función o procedimiento del paquete durante cada sesión. En el siguiente ejemplo, el paquete GESTOR_LIBRO incluye en su cuerpo un comando SQL que registra el nombre del usuario y el tiempo de la primera vez que ejecuta un componente del usuario dentro de la sesión. Se incluyen dos variables dentro del paquete para

Page 118: Java y Oracle 11g

Oracle /118

registrar estos valores. Al ser declaradas dentro del cuerpo del paquete no están disponibles para el público. El código de inicialización del paquete se muestra resaltado en el siguiente listado: CREATE OR REPLACE PACKAGE BODY GESTOR_LIBRO

AS

Nombre_usuario VARCHAR2(30);

Fecha_entrada DATE;

/*

Definición de otros componentes del paquete

*/

BEGIN

SELECT USER, SYSDATE INTO Nombre_Usuario, Fecha_Entrada

FROM DUAL;

END GESTOR_LIBRO;

/

Nota. El código que será ejecutado la primera vez que un componente del paquete es ejecutado es almacenado en su propio bloque PL/SQL al final del cuerpo del paquete. No tiene su propia cláusula END, sino que usa la cláusula END del paquete.

5.8. Viendo el código fuente de objetos procedimentales.

El código fuente de procedimientos, funciones y paquetes, y cuerpos de paquete puede ser consultado con las siguientes vistas del diccionario de datos:

USER_SOURCE Para objetos propiedad del usuario. ALL_SOURCE Para objetos propiedad del usuario o a los cuales el usuario tiene permisos de acceso. DBA_SOURCE Para todos los objetos de la base de datos.

Se selecciona información desde la vista USER_SOURCE mediante una consulta similar a la mostrada a continuación. En este ejemplo se selecciona la columna Text y se ordena por el número de línea. El nombre y tipo del objeto se utilizan para indicar qué código fuente será mostrado. El siguiente ejemplo usa un procedimiento llamado Nuevo_Libro. SELECT Text

FROM USER_SOURCE

WHERE Name = 'NUEVO_LIBRO' AND Type = 'PROCEDURE'

ORDER BY Line;

TEXT

-------------------------------------------------------------------------------------------

PROCEDURE NUEVO_LIBRO (PTitulo IN VARCHAR2, PEditor IN VARCHAR2,

PCategoria IN VARCHAR2)

AS

BEGIN

INSERT INTO BOOKSHELF (Titulo, Editor, Categoria)

VALUES (PTitulo, PEditor, PCategoria);

DELETE FROM PEDIDO_LIBRO

WHERE Titulo = PTitulo;

END;

Como se ve en este ejemplo, la vista USER_SOURCE contiene un registro por cada línea del procedimiento NUEVO_LIBRO. La secuencia de la línea es mantenida por la columna Line; por lo tanto, la columna Line debería ser usada para ordenar el resultado. Valores válidos para la columna Type son PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY, JAVA SOURCE, TYPE, y TYPE BODY.

5.9. Compilando procedimientos, funciones y paquetes.

Oracle compila los objetos procedimentales cuando son creados. Sin embargo, los objetos procedimentales pueden ser inválidos si los objetos de la base de datos que referencian cambian. La siguiente vez que los objetos procedimentales sean ejecutados serán recompilados por la base de datos. Podemos evitar esta compilación en tiempo de ejecución (y la degradación de rendimiento que esto puede causar) recompilando explícitamente los procedimientos, funciones y paquetes. Para recompilar un procedimiento se usa el comando ALTER PROCEDURE, tal como se muestra a continuación. La cláusula

Page 119: Java y Oracle 11g

Oracle /119

COMPILE es una opción sólo válida par este comando. ALTER PROCEDURE Nuevo_Libro COMPILE;

Para recompilar un procedimiento debemos ser el propietario o tener el permiso de sistema ALTER ANY

PROCEDURE. Para recompilar una función se usa el comando ALTER FUNCTION con la cláusula COMPILE: ALTER FUNCTION Libros_Cambiados COMPILE;

Para recompilar una función debemos ser el propietario o tener el permiso de sistema ALTER ANY PROCEDURE. Cuando recompilamos paquetes podemos recompilar tanto la especificación del paquete como su cuerpo, o bien sólo el cuerpo del paquete. Por defecto se recompilan tanto la especificación del paquete como su cuerpo. No podemos usar los comandos ALTER FUNCTION o ALTER PROCEDURE para recompilar las funciones y procedimientos dentro del paquete. Si sólo ha cambiado el código de alguna función o procedimiento dentro del paquete sólo es necesario recompilar el cuerpo del paquete. Para ello se utiliza la siguiente sintaxis: ALTER PACKAGE [usuario.]nombre_del_paquete COMPILE [DEBUG] [PACKAGE | BODY | SPECIFICATION];

Para recompilar un paquete se usa el comando precedente con la cláusula COMPILE, tal como sigue: ALTER PACKAGE GESTOR_LIBRO COMPILE;

Para recompilar un paquete debemos ser su propietario o debemos tener el permiso de sistema ALTER ANY

PROCEDURE. Ya que no se especificó PACKAGE ni BODY en el ejemplo precedente, se usó por defecto PACKAGE, con lo cual se recompiló tanto la especificación como el cuerpo del paquete.

6. Transacciones

No todas las operaciones SQL son transaccionales. Sólo son transaccionales las operaciones correspondientes al DML, es decir, sentencias SELECT, INSERT, UPDATE y DELETE. Una transacción comienza con la primera instrucción DML que se ejecute y finaliza con alguna de estas circunstancias:

- Una operación COMMIT o ROLLBACK. - Una instrucción DDL (como ALTER TABLE, por ejemplo). - Una instrucción DCL (como GRANT). - El usuario abandona la sesión. - Se produce una caída del sistema.

Hay que tener en cuenta que cualquier instrucción DDL o DCL da lugar a un COMMIT implícito; es decir, todas las instrucciones DML ejecutadas hasta ese instante pasan a ser definitivas.

6.1. Estado de los datos durante la transacción.

Si se inicia una transacción usando comandos DML hay que tener en cuenta que: - Se puede volver a la instrucción anterior a la transacción cuando se desee. - Las instrucciones de consulta SELECT realizadas por el usuario que inició la transacción muestran los datos ya modificados por las instrucciones DML. - El resto de usuarios ven los datos tal cual estaban antes de la transacción; de hecho, los registros afectados por la transacción aparecen bloqueados hasta que la transacción finalice. Esos usuarios no podrán modificar los valores de dichos registros.

Tras la transacción todos los usuarios ven los datos tal cual quedan tras el fin de la transacción. Los bloqueos son liberados y los puntos de ruptura borrados.

6.2. Control de transacciones en PL/SQL.

Para confirmar una transacción se utiliza la sentencia COMMIT. Cuando realizamos un COMMIT los cambios se escriben en la base de datos. Para deshacer una transacción se utiliza la sentencia ROLLBACK. Cuando realizamos un ROLLBACK se deshacen todas las modificaciones realizadas por la transacción en la base de datos que todavía no han sido confirmadas, quedando la base de datos en el mismo estado que antes de iniciarse la transacción. En una transacción los datos modificados no son visibles por el resto de usuarios hasta que se confirme la transacción. Un abandono de sesión incorrecto o un problema de comunicación o de caída del sistema dan lugar a un ROLLBACK implícito. El siguiente ejemplo muestra una supuesta transacción bancaria: DECLARE

importe NUMBER;

ctaOrigen VARCHAR2(23);

Page 120: Java y Oracle 11g

Oracle /120

ctaDestino VARCHAR2(23);

BEGIN

importe := 100;

ctaOrigen:= '2530 10 2000 1234567890';

ctaDestino := '2532 10 2010 0987654321';

UPDATE CUENTAS SET SALDO = SALDO – importe WHERE CUENTA = ctaOrigen;

UPDATE CUENTAS SET SALDO = SALDO + importe WHERE CUENTA = ctaDestino;

INSERT INTO MOVIMIENTOS (CUENTA_ORIGEN, CUENTA_DESTINO,IMPORTE, FECHA_MOVIMIENTO)

VALUES (ctaOrigen, ctaDestino, importe*(-1), SYSDATE);

INSERT INTO MOVIMIENTOS (CUENTA_ORIGEN, CUENTA_DESTINO,IMPORTE, FECHA_MOVIMIENTO)

VALUES (ctaDestino,ctaOrigen, importe, SYSDATE);

COMMIT;

EXCEPTION

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE('Error en la transacción:'||SQLERRM);

DBMS_OUTPUT.PUT_LINE('Se deshacen las modificaciones);

ROLLBACK;

END;

Si alguna de las tablas afectadas por la transacción tiene triggers, las operaciones que realiza el trigger están dentro del ámbito de la transacción, y son confirmadas o deshechas conjuntamente con la transacción. Durante la ejecución de una transacción, una segunda transacción no podrá ver los cambios realizados por la primera transacción hasta que éstos se confirmen. Oracle es completamente transaccional. Siempre debemos especificar si queremos deshacer o confirmar la transacción.

6.3. Puntos de ruptura.

La instrucción SAVEPOINT permite establecer un punto de ruptura en una transacción. El problema de la combinación ROLLBACK/COMMIT es que un COMMIT acepta todo y un ROLLBACK anula todo. SAVEPOINT permite señalar un punto intermedio entre el inicio de la transacción y la situación actual. Su sintaxis es: ...instrucciones DML...

SAVEPOINT nombreSavepoint;

....instrucciones DML...

Para regresar a un punto de ruptura concreto se utiliza el comando: ROLLBACK TO SAVEPOINT nombreSavepoint;

Cuando se vuelve a un punto marcado, las instrucciones que siguieron a esa marca se anulan definitivamente. Debemos tener en cuenta que si realizamos un ROOLBACK TO SAVEPOINT a un punto de ruptura al cual se ha aplicado un COMMIT, se producirá una excepción. Por ejemplo, el siguiente código producirá una excepción: BEGIN

SAVEPOINT SP1;

DELETE FROM Alumno;

COMMIT;

ROLLBACK TO SAVEPOINT SP1; -- Se lanza una excepción.

END;

6.4. Transacciones autónomas

En ocasiones es necesario que los datos escritos por parte de una transacción sean persistentes, pero sin afectar a la persistencia del resto de la transacción. PL/SQL permite marcar un bloque con PRAGMA AUTONOMOUS_TRANSACTION. Con esta directiva marcamos un subprograma para que se comporte como una transacción diferente a la del proceso principal, llevando el control de COMMIT o ROLLBACK independientemente. Obsérvese el siguiente ejemplo. Primero creamos un procedimiento y lo marcamos con PRAGMA

AUTONOMOUS_TRANSACTION. CREATE OR REPLACE PROCEDURE Grabar_Log(descripcion VARCHAR2)

IS

PRAGMA AUTONOMOUS_TRANSACTION;

BEGIN

INSERT INTO LOG_APLICACION (CO_ERROR, DESCRIPICION, FX_ERROR)

VALUES (SQ_ERROR.NEXTVAL, descripcion, SYSDATE);

Page 121: Java y Oracle 11g

Oracle /121

COMMIT; -- Este commit solo afecta a la transacción autónoma

END ;

Cualquier COMMIT o ROLLBACK realizado dentro un subprograma autónomo sólo afectará a las operaciones realizadas dentro del subrpograma, y nunca a cualquier operación previa pendiente de confirmar o rechazar. A continuación utilizamos el procedimiento desde un bloque de PL/SQL: DECLARE

v_idModulo.idModulo%TYPE;

BEGIN

v_id := 99;

INSERT INTO Modulo (idModulo, nombre) VALUES (v_id, 'Modulo ' || v_id);

COMMIT;

EXCEPTION

WHEN OTHERS THEN

Grabar_Log(SQLERRM);

ROLLBACK;

/* Los datos grabados por "Grabar_Log" se escriben en la base de datos a pesar del ROLLBACK,

* ya que el procedimiento autónomo contiene un COMMIT.

* Sin embargo, dicho COMMIT no confirma la operación INSERT previa.

*/

END;

Es muy común que, por ejemplo, en caso de que se produzca algún tipo de error queramos insertar un registro en una tabla de log con el error que se ha producido y hacer ROLLBACK de la transacción. Pero si hacemos ROLLBACK de la transacción también lo hacemos de la inserción del log.

7. Triggers

Un trigger define una acción que la base de datos debe realizar cuando ocurre algún evento. Se pueden usar los triggers para aplicar integridad referencial adicional, forzar reglas del negocio complejas, o auditar cambios en los datos. El código dentro de un trigger, llamado el cuerpo del trigger, se escribe con un bloque PL/SQL. La ejecución del trigger es transparente para el usuario. Los triggers son ejecutados por la base de datos cuando determinados tipos de comandos se ejecutan desde aplicaciones clientes. Estos comandos pueden incluir inserciones, actualizaciones y borrados, operaciones DDL y operaciones DCL. La actualización de columnas específicas puede también ser usada como eventos para lanzar triggers, así como inicios de sesión y paradas en la base de datos.

7.1. Permisos requeridos.

Para crear un trigger sobre una tabla debemos ser capaces de modificar dicha tabla. Por lo tanto, debemos o bien ser propietarios de la tabla o tener el permiso ALTER para la tabla, o tener el permiso de sistema ALTER

ANY TABLE. Además, debemos tener el permiso de sistema CREATE TRIGGER; para crear triggers en otros esquemas debemos tener el permiso de sistema CREATE ANY TRIGGER. El permiso CREATE TRIGGER es parte del rol RESOURCE proporcionado con Oracle. Para modificar un trigger debemos ser propietarios del mismo o tener el permiso de sistema ALTER ANY

TRIGGER. Podemos también habilitar o deshabilitar triggers modificando las tablas en las que está basado, lo cual requiere que tengamos el permiso ALTER para la tabla o el permiso de sistema ALTER ANY TABLE. Para crear un trigger sobre un evento del nivel de base de datos debemos tener el permiso de sistema ADMINISTER DATABASETRIGGER. Los triggers pueden referenciar otras tablas además de la que produce el evento. Por ejemplo, si usamos triggers para auditar cambios de datos en la tabla LIBRO, entonces podemos insertar un registro dentro de una tabla diferente (como LIBRO_AUDIT) cada vez que un registro es cambiado en LIBRO. Para hacer esto necesitamos tener el permiso para insertar dentro de LIBRO_AUDIT.

Nota. Los permisos necesarios para desencadenar transacciones no pueden venir de roles; deben concederse directamente por el creador del trigger.

7.2. Tipos de triggers.

Se determina el tipo de un trigger por el tipo de transacción desencadenada y por el nivel en el cual el trigger es ejecutado.

Page 122: Java y Oracle 11g

Oracle /122

7.2.1. Triggers al nivel de fila. Los triggers al nivel de fila se ejecutan para cada fila afectada por un comando DML. Para el ejemplo de auditoria de la tabla LIBRO, cada fila que es cambiada en la tabla LIBRO puede ser procesada por el trigger. Los triggers a nivel de fila son los más comunes; se usan normalmente en aplicaciones de auditoría de datos. También son normalmente usados para sincronización de datos. Las vistas materializadas usan internamente triggers a nivel de fila para estos propósitos. Los triggers a nivel de fila se crean usando la cláusula FOR EACH ROW en el comando CREATE TRIGGER. 7.2.2. Triggers a nivel de comando. Los triggers a nivel de comando se ejecutan una vez por cada comando DML. Por ejemplo, si un único comando INSERT inserta 500 filas dentro de la tabla LIBRO, se lanza un trigger a nivel de fila por cada registro insertado, pero un único trigger a nivel de comando para toda la tabla. Por lo tanto, los triggers a nivel de comando no son normalmente usados para actividades relativas a los datos; son normalmente usados para forzar medidas adicionales de seguridad sobre los tipos de acciones que podemos realizar sobre una tabla. Los triggers a nivel de comando son el tipo de trigger por defecto creados mediante el comando CREATE

TRIGGER. 7.2.3. Triggers «BEFORE» y «AFTER». Ya que los triggers son ejecutados por eventos, pueden ocurrir inmediatamente antes o después de estos eventos. Ya que los eventos que ejecutan los triggers incluyen comandos DML de base de datos, pueden ser ejecutados inmediatamente antes o después de inserciones, actualizaciones o borrados. Para eventos al nivel de base de datos, podemos aplicar restricciones adicionales; no podemos desencadenar un evento para que ocurra antes de un inicio de sesión o un inicio de la base de datos. Dentro del trigger podemos referenciar los antiguos y nuevos valores involucrados en un comando DML. El acceso requerido por los datos antiguos y nuevos puede determinar qué tipo de trigger necesitamos. Si necesitamos asignar un valor de columna en un registro insertado mediante nuestro trigger, entonces podemos necesitar usar un trigger BEFORE INSERT para acceder a los nuevos valores. Usando un trigger AFTER INSERT no podemos modificar los valores a insertar, ya que la fila ha sido realmente insertada dentro de la tabla. Los triggers AFTER del nivel de fila son usados frecuentemente en aplicaciones de auditoría, ya que no se lanzan hasta que la fila ha sido modificada. Las modificaciones sucedidas en la fila implican que han pasado las restricciones de integridad referencial definidas para la tabla. 7.2.4. Triggers de sustitución (INSTEAD OF). Podemos usar triggers INSTEAD OF para decirle a Oracle qué hacer en vez de las acciones que invoca el trigger. Por ejemplo, podemos usar un trigger INSTEAD OF sobre una vista para redirigir inserciones dentro de una tabla o para actualizar varias tablas que son parte de una vista. Podemos usar triggers INSTEAD OF sobre vistas de objetos o vistas relacionales. Por ejemplo, si una vista involucra un join entre dos tablas, nuestra habilidad para usar el comando UPDATE sobre los registros en la vista es limitada. Sin embargo, si usamos un trigger INSTEAD OF, podemos decirle a Oracle cómo actualizar, borrar o insertar registros en las tablas subyacentes de la vista cuando un usuario intente cambiar valores a través de la vista. El código en el trigger INSTEAD OF es ejecutado en lugar de la inserción, actualización o borrado que entremos.

Nota. Podemos acceder o cambiar datos LOB dentro de triggers BEFORE e INSTEAD OF.

7.2.5. Triggers de esquema. Podemos crear triggers sobre operaciones a nivel de esquema como un CREATE TABLE, ALTER TABLE, DROP

TABLE, AUDIT, RENAME, TRUNCATE, y REVOKE. Aún podemos crear un trigger BEFORE DDL. Principalmente, los triggers de nivel de esquema proporcionan dos capacidades: la prevención de operaciones DDL y proporcionar supervisión de seguridad adicional con operaciones DDL. 7.2.6. Triggers al nivel de base de datos. Podemos crear triggers que sean lanzados por eventos de base de datos, incluyendo errores, registros, desconexiones, paradas e inicios de base de datos. Podemos usar este tipo de triggers para automatizar el mantenimiento de base de datos o acciones de auditoría. Las bases de datos virtuales privadas ejecutan triggers a nivel de base de datos para establecer valores de variables del contexto de sesión.

7.3. Triggers asociados a tablas.

Normalmente se habla de un trigger asociado a una tabla como un bloque PL/SQL que se ejecuta como

Page 123: Java y Oracle 11g

Oracle /123

consecuencia de una determinada instrucción SQL (INSERT, UPDATE o DELETE) sobre dicha tabla. 7.3.1. Declaración de los triggers. La sintaxis para crear estos triggers es la siguiente: CREATE [OR REPLACE] TRIGGER nombre_trigger

{BEFORE|AFTER|INSTEAD OF}

{DELETE|INSERT|UPDATE [OF col1, col2, ..., colN]

[OR {DELETE|INSERT|UPDATE [OF col1, col2, ..., colN]...]}

ON nombre_tabla

[FOR EACH ROW [WHEN (<condición>)]]

DECLARE

-- variables locales

BEGIN

-- Sentencias

[EXCEPTION]

-- Sentencias de control de excepción

END nombre_trigger;

El uso de OR REPLACE permite sobrescribir un trigger existente. Si se omite, y el trigger existe, se producirá un error al ejecutar el comando CREATE TRIGGER. Los triggers pueden definirse para las operaciones INSERT, UPDATE o DELETE, y pueden ejecutarse antes o después de la operación. Los modificadores BEFORE y AFTER indican que el trigger se lanzará antes o después de ejecutarse la sentencia SQL. Si incluimos el modificador OF, el trigger solo se ejecutará cuando la sentencia SQL afecte a los campos incluidos en la lista. El alcance de estos triggers puede ser a nivel de fila o de comando. El modificador FOR EACH ROW indica que el trigger se disparará cada vez que se realizan operaciones sobre una fila de la tabla. Si se acompaña del modificador WHEN, se puede establece una restricción; el trigger solo actuará sobre las filas que satisfagan la restricción. (La cláusula WHEN sólo es válida para los triggers con nivel de fila.) 7.3.2. Orden de ejecución de los triggers. Una misma tabla puede tener varios triggers asociados. En tal caso es necesario conocer el orden en el que se van a ejecutar. Los triggers se activan al ejecutarse la sentencia SQL:

• Si existe, se ejecuta el disparador de tipo BEFORE (disparador previo) con nivel de comando. • Para cada fila a la que afecte el comando:

- Se ejecuta si existe, el disparador de tipo BEFORE con nivel de fila. - Se ejecuta el propio comando. - Se ejecuta si existe, el disparador de tipo AFTER (disparador posterior) con nivel de fila.

• Se ejecuta, si existe, el disparador de tipo AFTER con nivel de comando. 7.3.3. Restricciones de los triggers. El cuerpo de un trigger es un bloque PL/SQL. Cualquier comando que sea legal en un bloque PL/SQL, es legal en el cuerpo de un trigger, con las siguientes restricciones:

- Un trigger no puede emitir ninguna orden de control de transacciones: COMMIT, ROLLBACK o SAVEPOINT. El trigger se activa como parte de la ejecución del comando que provocó el disparo, y forma parte de la misma transacción que dicho comando. Cuando el comando que provoca el disparo es confirmado o cancelado, se confirma o cancela también el trabajo realizado por el trigger. - Por razones idénticas, ningún procedimiento o función llamado por el trigger puede emitir órdenes de control de transacciones. - El cuerpo del trigger no puede contener ninguna declaración de variables LONG o LONG RAW.

7.3.4. Utilización de las variables globales «OLD» y «NEW». Dentro del ámbito de un trigger disponemos de las variables globales OLD y NEW. Estas variables se utilizan del mismo modo que cualquier otra variable PL/SQL, con la salvedad de que no es necesario declararlas; son del tipo %ROWTYPE de la tabla asociada al trigger y contienen una copia del registro antes (OLD) y después (NEW) de la acción SQL que ha disparado el trigger. Utilizando estas variables podemos acceder a los datos que se están insertando, actualizando o borrando. La siguiente tabla muestra los valores de OLD y NEW según el comando que dispara el trigger.

Acción SQL OLD NEW

INSERT No definido; todos los campos toman Valores que serán insertados cuando se complete la

Page 124: Java y Oracle 11g

Oracle /124

valor NULL. orden.

UPDATE Valores originales de la fila, antes de la actualización.

Nuevos valores que serán escritos cuando se complete la orden.

DELETE Valores, antes del borrado de la fila. No definidos; todos los campos toman el valor NULL.

Nota. Los registros OLD y NEW son sólo válidos dentro de los triggers con nivel de fila (con la especificación FOR EACH ROW).

Por ejemplo, si queremos lanzar un trigger antes de que se actualice una nota, pero sólo si el nuevo valor es mayor que el antiguo valor, podríamos utilizar el siguiente código: CREATE OR REPLACE TRIGGER TR_Nota_01

BEFORE UPDATE ON Nota

FOR EACH ROW WHEN (NEW.valor > OLD.valor)

BEGIN

-- Código del Trigger

END ;

El siguiente ejemplo muestra un trigger que inserta automáticamente un registro de matrícula con el año actual cada vez que insertamos un nuevo alumno en la base de datos (en este ejemplo se presupone creado un objeto secuencia denominado SQ_IDMatricula): CREATE OR REPLACE TRIGGER TR_Matricula_01

AFTER INSERT ON Alumno

FOR EACH ROW

BEGIN

INSERT INTO Matricula (idMatricula, nif, año)

VALUES (SQ_IDMatricula.NEXT, :NEW.nif, EXTRACT(YEAR FROM SYSDATE));

END ;

El trigger se ejecutará automáticamente cuando sobre la tabla Alumno se ejecute una sentencia INSERT como: INSERT INTO Alumno (nif, nombre, apellidos, localidad)

VALUES ('66666666H', 'Juan' , 'Salgado Rey', 'Madrid');

Nota. Cuando se usa NEW y OLD en el cuerpo del trigger deben ir precedidos de dos punto (:); sin embargo, cuando se usan en la cláusula WHEN de FOR EACH ROW no deben ir precedidos de dos puntos.

7.3.5. Utilización de las funciones «INSERTING», «UPDATING» y «DELETING». Dentro de un trigger en el que se disparan distintos tipos de órdenes DML, hay tres funciones booleanas que pueden emplearse para determinar de qué operación se trata. Estas funciones son INSERTING, UPDATING y DELETING. Su comportamiento es el siguiente:

Función Retorna

INSERTING TRUE si el comando de disparo es INSERT; FALSE en otro caso.

UPDATING TRUE si el comando de disparo es UPDATE; FALSE en otro caso.

DELETING TRUE si el comando de disparo es DELETE; FALSE en otro caso.

Como ejemplo, el siguiente trigger normaliza el precio de un libro cuando se realizan inserciones o actualizaciones en la tabla LIBRO. Si se inserta un nuevo registro redondea el precio, y se actualiza el precio conserva el antiguo valor si es mayor que el nuevo. CREATE OR REPLACE TRIGGER TR_Normaliza_Titulo

BEFORE INSERT OR UPDATE OF precio ON Libro

FOR EACH ROW

DECLARE

BEGIN

IF INSERTING THEN -- se está realizando una inserción

:NEW.precio := ROUND(:NEW.precio, 2);

END IF;

IF UPDATING AND :NEW.precio<ODL.precio THEN -- se está realizando una actualización

:NEW.precio := :OLD.precio;

END IF;

END;

Page 125: Java y Oracle 11g

Oracle /125

7.3.6. Uso de triggers para asignar claves automáticamente. Oracle no permite crear claves autonuméricas, pero mediante el uso de secuenciadores y triggers podemos simular esta funcionalidad. Por ejemplo, si hemos creado la tabla Matricula con un campo clave idMatricula de tipo INTEGER, podemos omitir la asignación de este campo cada vez que hagamos una inserción de producto de la siguiente manera:

1) Creamos un secuenciador con el comando: CREATE SEQUENCE SQ_IDMatricula;

2) Creamos un trigger como el siguiente: CREATE TRIGGER TR_Pon_IDMatricula

BEFORE INSERT

ON Matricula

FOR EACH ROW

BEGIN

SELECT SQ_IDMatricula.NEXTVAL INTO :NEW.idMatricula FROM DUAL;

END;

Ahora, cada vez que se ejecute una instrucción INSERT sobre la tabla Matricula, tanto si pasamos un valor para idMatricula como si no, se lanzará un trigger que asigne automáticamente el valor.

7.4. Triggers para eventos DDL.

Podemos crear triggers que son ejecutados cuando ocurre un evento DDL. Si estamos planificando usar esta funcionalidad para propósitos de seguridad, deberíamos investigar usar el comando AUDIT en su lugar. Por ejemplo, podemos usar un trigger DDL que se lance al ejecutar comandos CREATE, ALTER, y DROP sobre un clúster (cubo), función, índice, paquete, procedimiento, rol, esquema, sinónimo, tabla, tablespace, trigger, tipo, usuario o vista. Si usamos la cláusula ON SCHEMA, el trigger se ejecutará para cada nuevo objeto del diccionario de datos creado sobre nuestro esquema. El siguiente ejemplo ejecutará un procedimiento llamado INSERT_AUDIT_RECORD si son creados objetos dentro de nuestro esquema: CREATE OR REPLACE TRIGGER CREATE_DB_OBJECT_AUDIT

AFTER CREATE ON SCHEMA

BEGIN

CALL INSERT_AUDIT_RECORD (ORA_DICT_OBJ_NAME);

END;

/

Como muestra este ejemplo, podemos referenciar atributos del sistema (en el ejemplo, ORA_DICT_OBJ_NAME, recupera el nombre del objeto creado). Los atributos disponibles se listan en la tabla al final de esta sección. Para proteger los objetos dentro de un esquema podemos crear un trigger que sea ejecutado para cada intento del comando DROP TABLE. Este trigger tendrá que ser un trigger BEFORE DROP: CREATE OR REPLACE TRIGGER PREVENT_DROP

BEFORE DROP ON Empleado.SCHEMA

BEGIN

IF ORA_DICT_OBJ_OWNER = 'Empleado' AND ORA_DICT_OBJ_NAME LIKE 'LIB%'

AND ORA_DICT_OBJ_TYPE = 'TABLE' THEN

RAISE_APPLICATION_ERROR (-20002, 'Operación no permitida.');

END IF;

END;

/

Nótese que este trigger referencia los atributos de evento dentro cuerpo. Intentar borrar una tabla dentro del esquema Empleado cuyo nombre comience por LIB resultará en lo siguiente: DROP TABLE LIBRO_AUDIT_DUP;

DROP TABLE LIBRO_AUDIT_DUP

*

ERROR en línea 1:

ORA-00604: error occurred at recursive SQL level 1

ORA-20002: Operación no permitida.

ORA-06512: at line 6

Podemos usar el procedimiento RAISE_APPLICATION_ERROR para personalizar un mensaje de usuario que queramos mostrar al usuario.

Page 126: Java y Oracle 11g

Oracle /126

La siguiente tabla describe los atributos de sistema que podemos usar en este tipo de triggers.

Atributo Tipo Descripción y ejemplo

ora_client_ip_address VARCHAR2 Retorna la dirección IP del cliente en un evento LOGON cuando el protocolo subyacente es TCP/IP.

if (ora_sysevent='LOGON') then

addr:=ora_client_ip_address;

end if;

ora_database_name VARCHAR2(50) Nombre de la base de datos.

Declare

db_name VARCHAR2(50);

begin

db_name:=ora_database_name;

end;

ora_des_encrypted_password VARCHAR2 La contraseña con encriptación DES del usuario que está siendo creado o modificado.

if (ora_dict_obj_type='USER') then

insert into

event_table(ora_des_encrypted_password);

end if;

ora_dict_obj_name VARCHAR(30) Nombre del objeto en el diccionario sobre el cual ocurre la operación DDL.

insert into event_table (

'El objeto cambiado es ' ||

ora_dict_obj_name');

ora_dict_obj_name_list (name_list

OUT ora_name_list_t)

BINARY_INTEGER Retorna la lista de nombres de objetos que están siendo modificados en el evento.

if (ora_sysevent='associate statistics') then

number_modified :=

ora_dict_obj_name_list (name_list);

end if;

ora_dict_obj_owner VARCHAR(30) Propietario del objeto sobre el cual ocurre la operación.

insert into event_table ('el propietario del

objeto es ' || ora_dict_obj_owner');

ora_dict_obj_owner_list (owner_list

OUT ora_name_list_t)

BINARY_INTEGER Retorna la lista de propietarios de los objetos modificados en el evento.

if (ora_sysevent='associate statistics') then

number_of_modified_objects :=

ora_dict_obj_owner_list(owner_list);

end if;

ora_dict_obj_type VARCHAR(20) Tipo de objeto sobre el cual ocurre la operación DDL.

insert into event_table ('Este objeto es

un ' || ora_dict_obj_type);

ora_grantee (user_list OUT

ora_name_list_t)

BINARY_INTEGER Retorna los permisos de un evento GRANT en el parámetro de salida; retorna el número de concesiones.

if (ora_sysevent='GRANT') then

number_of_users:=ora_grantee(user_list);

end if;

ora_instance_num NUMBER Número de instancia.

if (ora_instance_num=1) then

insert into event_table('1');

end if;

Page 127: Java y Oracle 11g

Oracle /127

ora_is_alter_column (column_name

IN VARCHAR2)

BOOLEAN Retorna TRUE si la columna especificada es modificada.

if (ora_sysevent='ALTER' and

ora_dict_obj_type='TABLE') then

alter_column:=ora_is_alter_column('FOO');

end if;

ora_is_creating_nested_table BOOLEAN Retorna TRUE si el evento actual es creando una tabla anidada.

if (ora_sysevent='CREATE' and

ora_dict_obj_type='TABLE' and

ora_is_creating_nested_table) then

insert into event_tab

values ('Se creó una tabla anidada');

end if;

ora_is_drop_column (column_name

IN VARCHAR2)

BOOLEAN Retorna TRUE si la columna especificada es borrada.

if (ora_sysevent='ALTER' and

ora_dict_obj_type='TABLE') then

drop_column:=ora_is_drop_column('FOO');

end if;

ora_is_servererror BOOLEAN Retorna TRUE si el error dado es sobre un error

de pila, FALSE en otro caso.

if (ora_is_servererror(error_number)) then

insert into event_table('¡Error de servidor!');

end if;

ora_login_user VARCHAR2(30) Nombre de usuario en un registro.

select ora_login_user from dual;

ora_partition_pos BINARY_INTEGER En un trigger INSTEAD OF para CREATE TABLE, la posición dentro del texto SQL donde

podríamos insertar una cláusula PARTITION.

-- Recupera ora_sql_txt dentro de la

-- variable sql_text primero.

n:=ora_partition_pos;

new_stmt:=substr(sql_text, 1, n-1) ||

'' || my_partition_clause ||

'' || substr(sql_text, n));

ora_privilege_list (privilege_list

OUT ora_name_list_t)

BINARY_INTEGER Retorna la lista de permisos que están siendo

concedidos por el comando GRANT o la lista de

permisos revocados por el comando REVOKE, en el parámetro de salida; retorna el número de permisos.

if (ora_sysevent='GRANT' or

ora_sysevent = 'REVOKE') then

number_of_privileges:=

ora_privilege_list(priv_list);

end if;

ora_revokee (user_list OUT

ora_name_list_t)

BINARY_INTEGER Retorna las revocaciones de un evento REVOKE en el parámetro de salida; retorna el número de revocaciones.

if (ora_sysevent='REVOKE') then

number_of_users := ora_revokee(user_list);

end if;

ora_server_error NUMBER Dada una posición (1 para el tope de la pila) retorna el número de error en la pila de errores.

insert into event_table ('error en la pila ' ||

ora_server_error(1));

Page 128: Java y Oracle 11g

Oracle /128

ora_server_error_depth BINARY_INTEGER Retorna el número total de mensajes de error en la pila de errores.

n := ora_server_error_depth;

ora_server_error_msg (position in

binary_integer)

VARCHAR2 Dada una posición (1 para el tope de la pila), retorna el mensaje de error en la pila de errores.

insert into event_table ('Mensaje de la pila: ' ||

ora_server_error_msg(1));

ora_server_error_num_params

(position in binary_integer)

BINARY_INTEGER Dada una posición (1 para el tope de la pila), retorna el número de strings que han sido sustituidos dentro del mensaje de error usando un formato como "%".

n:=ora_server_error_num_params(1);

ora_server_error_param (position in

binary_integer, param in

binary_integer)

VARCHAR2 Dada una posición (1 para el tope de la pila) y un número de parámetro, retorna la coincidencia "%s", "%d" y demás en valores de sustitución en el mensaje de error.

-- E.j. el 2º %s en un mensaje como

-- "Esperado %s, encontrado %s"

param:=ora_server_error_param(1,2);

ora_sql_txt (sql_text out

ora_name_list_t)

BINARY_INTEGER Retorna el texto SQL del comando dentro del parámetro de salida. Si el comando es largo, se rompe en varios elementos. La función retorna un valor que especifica cuántos elementos hay.

sql_text ora_name_list_t;

stmt VARCHAR2(2000);

. . .

n := ora_sql_txt(sql_text);

for i in 1..n loop

stmt := stmt || sql_text(i);

end loop;

insert into event_table ('texto del comando: '

|| stmt);

ora_sysevent VARCHAR2(20) Evento del sistema que lanza el trigger. El nombre del evento es el mismo que está en la sintaxis.

insert into event_table (ora_sysevent);

ora_with_grant_option BOOLEAN Retorna TRUE si los permisos son concedidos

con la opción GRANT.

if (ora_sysevent='GRANT' and

ora_with_grant_option=TRUE) then

insert into event_table('with grant option');

end if;

space_error_info (error_number

OUT NUMBER, error_type OUT

VARCHAR2, object_owner OUT

VARCHAR2, table_space_name OUT

VARCHAR2, object_name OUT

VARCHAR2, sub_object_name OUT

VARCHAR2)

BOOLEAN Retorna TRUE si el error es relativo a una condición fuera-de-espacio, y rellena los parámetros de salida con información acerca del objeto que causó el error.

if (space_error_info(eno, typ,

owner, ts, obj, subobj) = TRUE) then

dbms_output.put_line('El objeto se ' ||

'ejecuta fuera de espacio.');

dbms_output.put_line('El objeto es ' ||

'propiedad de ' || owner);

end if;

7.5. Triggers para eventos del sistema.

Un trigger del sistema se dispara cuando se arranca o para la base de datos, un usuario inicia o para una sesión, cuando se crea, modifica o elimina un objeto, etc. Cuando ocurre un evento de base de datos podemos ejecutar un trigger que referencie los atributos del evento (como con los eventos DDL). Podemos

Page 129: Java y Oracle 11g

Oracle /129

usar un trigger de sistema para realizar funciones de mantenimiento del sistema inmediatamente después de cada inicio de la base de datos. La sintaxis para este tipo de trigger es el siguiente: CREATE [OR REPLACE] TRIGGER nombre_trigger

{ BEFORE|AFTER } { <lista eventos de definición> | <lista eventos del sistema>}

ON { DATABASE | SCHEMA} [WHEN (condición)]

<cuerpo del trigger (bloque PL/SQL)>

Donde la lista de eventos de definición puede tener uno o más eventos DDL separados por OR y la lista de eventos del sistema igualmente separados por OR. La siguiente tabla describe los diversos disparadores de sistema.

Evento Momento Se disparan:

STARTUP AFTER Después de arrancar la instancia.

SHUTDOWN BEFORE Antes de apagar la instancia.

LOGON AFTER Después de que el usuario se conecte a la base de datos.

LOGOFF BEFORE Antes de la desconexión de un usuario.

SERVERERROR AFTER Cuando ocurre un error en el servidor.

CREATE BEFORE|AFTER Antes o después de crear un objeto en el esquema.

DROP BEFORE|AFTER Antes o después de borrar un objeto en el esquema.

ALTER BEFORE|AFTER Antes o después de cambiar un objeto en el esquema.

TRUNCATE BEFORE|AFTER Antes o después de ejecutar un comando TRUNCATE.

GRANT BEFORE|AFTER Antes o después de ejecutar un comando GRANT.

REVOKE BEFORE|AFTER Antes o después de ejecutar un comando REVOKE.

DLL BEFORE|AFTER Antes o después de ejecutar cualquier comando de definición de datos.

Por ejemplo, el siguiente trigger fija paquetes en cada arranque de la base de datos. Fijar paquetes en un modo efectivo de guardar objetos PL/SQL grandes en la memoria compartida, mejorando el rendimiento y la escalabilidad de la base de datos. Este trigger, PIN_ON_STARTUP, se ejecutará cada vez que arranque la base de datos. Debemos crear este trigger mientras nos conectamos como un usuario con permisos ADMINISTER

DATABASE TRIGGER. -- Mientras nos conectamos como un usuario de tipo administrador:

CREATE OR REPLACE TRIGGER PIN_ON_STARTUP

AFTER STARTUP ON DATABASE

BEGIN

DBMS_SHARED_POOL.KEEP ('SYS.STANDARD', 'P');

END;

/

Este ejemplo muestra un simple trigger que será ejecutado inmediatamente después de que arranque la base de datos. Podemos modificar la lista de paquetes en el cuerpo del trigger para incluir los más usados por nuestra aplicación. Al arrancar y parar, los triggers pueden acceder a los atributos ora_instance_num, ora_database_name, ora_login_user, y ora_sysevent.

7.6. Triggers de sustitución.

Si creamos una vista podemos usar un trigger INSTEAD OF para decirle a Oracle cómo actualizar las tablas subyacentes que son parte de la vista. Podemos usar trigger INSTEAD OF sobre vistas de objeto o sobre vistas relacionales estándar. Por ejemplo, si una vista involucra un join entre dos tablas, nuestra habilidad para actualizar registros en la vista es limitada. Sin embargo, usando un trigger INSTEAD OF podemos decirle a Oracle cómo actualizar, borrar o insertar registros en tablas cuando un usuario intenta cambiar valores a través de la vista. El código del trigger INSTEAD OF se ejecuta en lugar del comando INSERT, UPDATE o DELETE. Por ejemplo, podemos tener una vista que combine la tabla LIBRO y AUTOR: CREATE OR REPLACE VIEW AUTOR_LIBRO AS

SELECT A.NombreAutor, L.Titulo

FROM AUTOR A INNER JOIN LIBRO L USING (idAutor);

Podemos seleccionar valores de esta vista. Consideremos los siguientes registros: SELECT NombreAutor, Titulo

FROM AUTOR_LIBRO

Page 130: Java y Oracle 11g

Oracle /130

WHERE NombreAutor = 'Emilio Salgari';

NombreAutor Titulo

------------------------- ---------------------------

Emilio Salgari Sandokán

Emilio Salgari El Corsario Negro

Si intentamos actualizar el valor del nombre del autor, la actualización fallará: UPDATE AUTOR_LIBRO

SET NombreAutor = 'Emil Salgari'

WHERE NombreAutor = 'Emilio Salgari';

El problema es que Oracle no puede determinar qué registro de AUTOR se intenta actualizar en la vista, al no disponer de la clave de AUTOR (IdAutor). Para realizar la actualización mediante la vista necesitamos usar un trigger INSTEAD OF. En el siguiente ejemplo se crea un trigger de tipo INSTEAD OF para la vista AUTOR_LIBRO: CREATE OR REPLACE TRIGGER AUTOR_LIBRO_UPDATE

INSTEAD OF UPDATE ON AUTOR_LIBRO

FOR EACH ROW

BEGIN

IF :OLD.NombreAutor <> :NEW.NombreAutor THEN

UPDATE AUTOR SET NombreAutor = :NEW.NombreAutor

WHERE NombreAutor = :OLD.NombreAutor;

END IF;

IF :OLD.Titulo <> :NEW.Titulo THEN

UPDATE LIBRO SET Titulo = :NEW.Titulo

WHERE Titulo = :OLD.Titulo;

END IF;

END;

/

En la primera parte de este trigger se nombra el trigger, y su propósito es descrito en la cláusula INSTEAD OF. Se crea el trigger al nivel de registro (FOR EACH ROW), de forma que cada cambio del registro será procesado. La siguiente sección del trigger le dice a Oracle cómo procesar la actualización. Si cambia el valor del nombre de autor se actualiza la tabla AUTOR para reflejar ese cambio, y si cambia el valor del título de libro se actualiza la tabla LIBRO para reflejar ese cambio Así, la vista confía en dos tablas (LIBRO y AUTOR), y una actualización sobre la vista puede actualizar ambas tablas. Ahora podemos actualizar la vista AUTOR_LIBRO directamente y tenemos un trigger que actualizará apropiadamente ambas tablas subyacentes. Por ejemplo, el siguiente comando actualizará la tabla AUTOR: UPDATE AUTOR_LIBRO

SET NombreAutor = 'Emil Salgari'

WHERE NombreAutor = 'Emilio Salgari';

2 filas actualizadas.

Los triggers INSTEAD OF son muy potentes. Como muestra el ejemplo anterior podemos usarlos para realizar operaciones a través de diferentes tablas de la base de datos, usando una lógica de control de flujo. En relación con vistas de objetos, podemos usar triggers INSTEAD OF para redirigir DML sobre la vista de objeto a la tablas base de la vista.

7.7. Activar y desactivar triggers.

La sintaxis para desactivar un trigger es: ALTER TRIGGER nombre_del_trigger DISABLE;

La sintaxis para desactivar todos los triggers sobre una tabla es: ALTER TABLE nombre_de_tabla DISABLE ALL TRIGGERS;

La sintaxis para volver a activar un trigger es: ALTER TRIGGER nombre_del_trigger ENABLE;

La sintaxis para volver a activar todos los triggers sobre una tabla es: ALTER TABLE nombre_de_tabla ENABLE ALL TRIGGERS;

Page 131: Java y Oracle 11g

Oracle /131

8. Tipos de datos complejos y operaciones masivas.

Existen tres tipos de datos personalizados en PL/SQL: ▪ RECORD (Registro): Es un tipo de datos definido por el usuario que contiene varios atributos en una sola estructura. ▪ TABLE (Array asociativo): Es una colección de elementos del mismo tipo que no tienen limitación en cuanto al número de elementos. ▪ VARRAY (Array variable): Es una colección de elementos del mismo tipo que tienen una limitación en cuanto al número de elementos.

Pueden utilizarse estos tipos de datos personalizados para realizar operaciones masivas de recuperación de datos mediante el comando BULK. Para trabajar con grandes cantidades de información, tanto en formato binario como en formato de texto, se utilizan los tipos de datos LOB predefinidos.

8.1. Registros (RECORD).

Un registro es una estructura de datos de PL/SQL, almacenados en campos, cada uno de los cuales tiene su propio nombre y tipo y que se tratan como una sola unidad lógica. Los campos de un registro pueden ser inicializados y pueden ser definidos como NOT NULL. Aquellos campos que no sean inicializados explícitamente, se inicializarán a NULL. 8.1.1. Declaración de registros. La sintaxis para definir un tipo de registro personalizado es: TYPE Nombre_del_tipo IS RECORD

(nombreVariable1 tipoDeDato,

[nombreVariable2 tipoDeDato, ...]);

Y para declarar una variable del tipo de registro personalizado: nombreRecord Nombre_del_tipo;

En ese momento ya podemos utilizar el RECORD mediante la inclusión de un punto, seguido del nombre de la variable a la que deseamos acceder: nombreRecord.nombreVariable1 := valor;

Los registros pueden estar anidados. Es decir, un campo de un registro puede ser de un tipo de dato de otro registro. Sólo pueden crearse tipos de registros personalizados en la parte declarativa de un objeto procedimental, nunca como objetos de la base de datos. Pueden asignarse todos los campos de un registro utilizando una sentencia SELECT. En este caso hay que tener cuidado en especificar las columnas en el orden conveniente según la declaración de los campos del registro. Para este tipo de asignación es muy frecuente el uso del atributo %ROWTYPE que veremos más adelante. DECLARE

TYPE RegAlumno IS RECORD ( nombre VARCHAR2(100), apellidos VARCHAR2(100) );

Alum1 RegAlumno;

BEGIN

SELECT nombre, apellidos INTO Alum1 FROM Alumno WHERE nif = '3333333D';

END;

Puede asignarse un registro a otro cuando sean del mismo tipo: DECLARE

TYPE RegAlumno IS RECORD ( nombre VARCHAR2(100), apellidos VARCHAR2(100) );

Alum1 RegAlumno;

Alumn2 RegAlumno;

BEGIN

Alum1.nombre := 'Juan';

Alum1.apellidos := 'Senén Rey';

Alum2 := Alum1;

END;

8.1.2. Declaración de registros con el atributo «%ROWTYPE». Se puede declarar un registro basándose en una colección de columnas de una tabla, vista o cursor de la base de datos mediante el atributo %ROWTYPE. Se puede declarar una variable del tipo de registros de la tabla Alumno con Alumno%ROWTYPE: DECLARE

Page 132: Java y Oracle 11g

Oracle /132

Alum1Alumno%ROWTYPE;

BEGIN

Alumn1.nif := '2222222B';

END;

Lo cual significa que el registro alum1 tendrá la siguiente estructura: ( nif VARCHAR2(9), nombre VARCHAR2(100),

apellidos VARCHAR2(50), localidad VARCHAR2(50) ). De esta forma se crea el registro de forma dinámica y se podrán asignar valores a los campos de un registro a través de un SELECT sobre la tabla, vista o cursor a partir de la cual se creó el registro.

8.2. Arrays asociativos (TABLE).

Los arrays asociativos o tablas anidadas o tablas de PL/SQL son tipos de datos que nos permiten almacenar varios valores del mismo tipo de datos. Una tabla PL/SQL:

- Es similar a un array sin límite de elementos. - Consta de dos componentes: un índice del tipo BINARY_INTEGER o VARCHAR2 que permite acceder a los elementos en la tabla y una columna de escalares, registros u objetos que contiene los valores de la tabla. - Puede incrementar su tamaño dinámicamente.

8.2.1. Declaración de tablas de PL/SQL. La sintaxis general para declarar una tabla de PL dentro de un bloque es la siguiente: TYPE nombreTabla IS TABLE OF tipoDato [INDEX BY tipoIndice];

TYPE nombreTabla IS TABLE OF tipoRegistro [INDEX BY tipoIndice];

Si definimos la colección sin la cláusula INDEX BY deberemos inicializar la colección antes de poder acceder a sus elementos, y en ese caso los índices serán de tipo entero comenzando por 1. Si se usa esta cláusula, la colección se comporta como un array asociativo y queda inicializada al rango de índices establecido según el tipo BINARY_INTEGER o VARCHAR2. Una vez que hemos definido la tabla, podemos declarar variables del tipo y asignarle valores. DECLARE

-- Definimos el tipo Valores_Table como tabla PL/SQL

TYPE Valores_Table IS TABLE OF NUMBER;

-- Declaramos una variable del tipo Valores_Table con tres elementos

valores Valores_Table := Valores_Table(0, 0, 0);

BEGIN

valores (1) := 1;

valores (2) := 2;

valores (3) := 3;

END;

Hay que fijarse que, en la declaración de la variable valores, debemos inicializar la tabla a un número de elementos dado. El primer elemento está asociado al índice 1. Posteriormente podrá ampliarse la tabla con más elementos mediante la función EXTEND(). . . .

valores.EXTEND();

valores (4) := 4;

. . .

Si definimos la colección con INDEX BY no será necesario inicializarla: DECLARE

-- Definimos el tipo Valores_Table como tabla PL/SQL

TYPE Valores_Table IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;

-- Declaramos una variable del tipo Valores_Table

valores Valores_Table;

BEGIN

valores (-100) := 1;

valores (-4) := 2;

valores (12) := 3;

END;

Así tenemos un array al cual podemos acceder mediante un índice entero (positivo o negativo) que se pone entre paréntesis. También podemos definir índices de tipo VARCHAR2: DECLARE

-- Definimos el tipo Ordinal_Meses para almacenar los ordinales de mes,

Page 133: Java y Oracle 11g

Oracle /133

-- usando como índices los nombres cortos de mes.

TYPE Ordinal_Meses IS TABLE OF NUMBER(1) INDEX BY VARCHAR2(3);

-- Declaramos una variable del tipo Ordinal_Meses

meses Ordinal_Meses;

BEGIN

meses ('ENE') := 1;

meses ('FEB') := 2;

meses ('MAR') := 3;

END;

También se puede almacenar un tipo de tabla en la base de datos mediante la instrucción CREATE TYPE: CREATE OR REPLACE TYPE Valores_Table AS TABLE OF NUMBER;

8.2.2. Tablas PL/SQL de registros. Es posible declarar elementos de una tabla PL/SQL como de tipo registro. DECLARE

TYPE RegAlumno IS RECORD ( nombre VARCHAR2(100), apellidos VARCHAR2(100) );

TYPE TAlumnos IS TABLE OF RegAlumno ;

ta TAlumnos := TAlumnos(NULL); -- Se crea la tabla con un elemento

BEGIN

ta(1).nombre := 'Juan';

ta(1).apellidos := 'Senén Rey';

END;

Si definimos la tabla con el comando CREATE TYPE no es posible que sus elementos sean de tipo RECORD. En este caso debemos sustituir el tipo RECORD por un tipo OBJECT. CREATE TYPE ObjAlumno AS OBJECT ( nombre VARCHAR2(100), apellidos VARCHAR2(100) );

CREATE TYPE TAlumnos AS TABLE OF ObjAlumno ;

8.2.3. Funciones para el manejo de tablas PL/SQL. También tenemos una serie de métodos asociados a los tipos TABLE, que nos permiten realizar acciones como:

• EXISTS(n): devuelve verdadero si el valor del índice que contenga no es nulo; es decir, tiene asignado algún valor. • COUNT: devuelve el número de elementos que posee la TABLE. • FIRST y LAST: Devuelven el índice inicial y final, respectivamente. • PRIOR(índice): Devuelve índice anterior del indicado. • NEXT(índice): Devuelve el siguiente índice del indicado. • EXTEND(): agrega un nuevo elemento con valor NULL a la colección. • EXTEND(n): agrega n elementos con valor NULL a la colección. • EXTEND(n, i): agrega n copias del elemento i-ésimo a la colección. • TRIM(n): Elimina los n últimos elementos. • DELETE[(n, m)]: Si no lleva índices, vacía la TABLE entera; si lleva un solo índice borra el elemento indicado; y si lleva los dos índices, borra desde el índice n hasta el índice m inclusive.

El siguiente ejemplo muestra el uso de FIRST y LAST: DECLARE

TYPE Arr_Ciudades IS TABLE OF VARCHAR2(50);

misCiudades ARR_CIUDADES := ARR_CIUDADES(NULL, NULL, NULL);

BEGIN

misCiudades(1) := 'MADRID';

misCiudades(2) := 'BILBAO';

misCiudades(3) := 'MALAGA';

FOR i IN misCiudades.FIRST..misCiudades.LAST LOOP

DBMS_OUTPUT.PUT_LINE(misCiudades(i));

END LOOP;

END;

8.3. Arrays variables (VARRAY).

Un VARRAY, o array variable, se manipula de forma muy similar a las tablas de PL, pero se implementa de forma diferente. Los elementos en el VARRAY se almacenan comenzando en el índice 1 hasta la longitud máxima declarada en el tipo VARRAY.

Page 134: Java y Oracle 11g

Oracle /134

8.3.1. Declaración de arrays variables. La sintaxis general para declarar un array variable en un bloque es la siguiente: TYPE nombreTipo IS VARRAY (tamañoMaximo) OF tipoElementos;

Una consideración a tener en cuenta es que en la declaración de un VARRAY el tipo de datos no puede ser de los siguientes tipos: BOOLEAN, NCHAR, NCLOB, NVARCHAR(n), REF CURSOR, TABLE, VARRAY. Sin embargo se puede especificar el tipo utilizando los atributos %TYPE y %ROWTYPE. Los VARRAY deben estar inicializados antes de poder utilizarse. Para inicializar un VARRAY se utiliza un constructor (podemos inicializar el VARRAY en la sección DECLARE o bien dentro del cuerpo del bloque): DECLARE

-- Declaramos el tipo VARRAY de cinco elementos VARCHAR2

TYPE T_Cadena IS VARRAY(5) OF VARCHAR2(50);

-- Asignamos los valores con un constructor

v_lista T_Cadena:= T_Cadena('Aitor', 'Alicia', 'Pedro', NULL);

BEGIN

v_lista(3) := 'Tita';

v_lista(4) := 'Ainhoa';

END;

También se puede almacenar en la base de datos mediante la instrucción CREATE TYPE: CREATE OR REPLACE TYPE T_Cadena AS VARRAY(5) OF VARCHAR2(50);

En este caso, el tipo de los elementos no puede ser RECORD, ni se puede utilizar los atributos %TYPE y %ROWTYPE. En este caso debemos sustituir el tipo RECORD por un tipo OBJECT. El tamaño de un VARRAY se establece mediante el número de argumentos utilizados en el constructor, si declaramos un VARRAY de cinco elementos pero al inicializarlo pasamos sólo tres parámetros al constructor, el tamaño del VARRAY será tres. Si se hacen asignaciones a elementos que queden fuera del rango se producirá un error. El tamaño de un VARRAY podrá aumentarse utilizando la función EXTEND, pero nunca con mayor dimensión que la definida en la declaración del tipo. Por ejemplo, la variable v_lista anterior, que sólo tiene 4 valores definidos, se podría ampliar hasta cinco elementos pero no más allá. Un VARRAY comparte varias de las funciones válidas para tablas, añadiendo alguna más:

• EXISTS(n): devuelve verdadero si el valor del índice que contenga no es nulo; es decir, tiene asignado algún valor. • COUNT: devuelve el número de elementos que posee el VARRAY. • FIRST y LAST: Devuelven el índice inicial y final, respectivamente. • PRIOR(índice): Devuelve índice anterior del indicado. • NEXT(índice): Devuelve el siguiente índice del indicado. • LIMIT, devuelve el número máximo de elementos que admite el VARRAY. • EXTEND, añade un elemento al VARRAY. • EXTEND(n), añade (n) elementos al VARRAY. • TRIM(n): Elimina los n últimos valores. • DELETE, elimina todos los elementos de la colección. El VARRAY se queda vacío, pero es distinto de NULL.

8.3.2. Arrays variables en la base de datos. Los VARRAY pueden almacenarse en columnas de tablas relacionales. Sin embargo, como campo, un VARRAY sólo puede manipularse en su integridad, no pudiendo modificarse directamente su contenido. Para poder crear tablas con campos de tipo VARRAY debemos crearlo como un objeto de la base de datos. La sintaxis general es: CREATE [OR REPLACE]

TYPE nombre_tipo AS VARRAY (tamaño_maximo) OF tipo_elementos;

Una vez que hayamos creado el tipo sobre la base de datos, podremos utilizarlo como un tipo de datos más en la creación de tablas, declaración de variables, ... Véase el siguiente ejemplo: CREATE OR REPLACETYPE Pack_Productos AS VARRAY(10) OF VARCHAR2(60);

/

CREATE TABLE Ofertas (

co_oferta NUMBER,

productos Pack_Productos,

precio NUMBER

Page 135: Java y Oracle 11g

Oracle /135

);

Para modificar un VARRAY almacenado, primero hay que seleccionarlo en una variable PL/SQL. Luego se modifica la variable y se vuelve a almacenar en la tabla. DECLARE

pack Pack_Productos;

BEGIN

-- Se recupera el campo de uno de los registros

SELECT productos INTO pack FROM Ofertas WHERE co_oferta = 2;

-- Se añade un nuevo elemento a la colección

pack.EXTEND(1);

pack(pack.LAST) := 'nuevo producto';

-- Se actualiza el campo

UPDATE Oferta SET productos = pack WHERE co_oferta = 2;

END;

8.4. Acceso masivo a los datos (BULK COLLECT).

PL/SQL nos permite leer varios registros en una colección de PL con un único acceso a través de la instrucción BULK COLLECT. Esto nos permitirá reducir el número de accesos a disco, por lo que optimizaremos el rendimiento de nuestras aplicaciones. Como contrapartida el consumo de memoria será mayor. Su uso más básico es el recuperar registros desde un cursor explícito mediante la instrucción FETCH: DECLARE

TYPE T_Apellidos IS TABLE OF Alumno.apellidos%TYPE;

v_apellidos T_Apellidos;

CURSOR c1 IS SELECT apellidos FROM Alumno;

BEGIN

OPEN c1;

FETCH c1 BULK COLLECT INTO v_apellidos LIMIT 10; -- Lee como máximo 10 registros

CLOSE c1;

-- se procesa la colección 'v_apellidos'

END;

Su uso más general es con cursores implícitos. El siguiente ejemplo recupera registros desde un SELECT: DECLARE

TYPE T_Apellidos IS TABLE OF Alumno.apellidos%TYPE;

TYPE T_Localidades IS TABLE OF Alumno.localidad%TYPE;

v_apellidos T_Apellidos;

v_localidades T_Localidades;

BEGIN

SELECT apellidos, localidad BULK COLLECT INTO v_apellidos, v_localidades FROM Alumno;

FOR i IN v_apellidos.FIRST .. v_apellidos.LAST LOOP

DBMS_OUTPUT.PUT_LINE(v_apellidos(i) || ',' || v_localidades(i));

END LOOP;

END;

Podemos utilizar BULK COLLECT con arrays de registros de PL, pero no así con arrays de objetos. DECLARE

TYPE T_Alumnos IS TABLE OF Alumno%ROWTYPE;

v_alumnos T_Alumnos;

BEGIN

SELECT* BULK COLLECT INTO v_alumnos FROM Alumno;

FOR i IN v_alumnos.FIRST .. v_alumnos.LAST LOOP

DBMS_OUTPUT.PUT_LINE(v_alumnos(i).apellidos || ', ' || v_alumnos(i).nombre);

END LOOP;

END;

También podemos aplicarlo en una instrucción de borrado para acumular los datos de los registros borrados: DECLARE

TYPE T_Alumnos IS TABLE OF Alumno%ROWTYPE;

v_alumnos T_Alumnos;

BEGIN

Page 136: Java y Oracle 11g

Oracle /136

DELETE FROM Alumno

RETURNING nif, nombre, apellidos, localidad BULK COLLECT INTO v_alumnos;

END;

8.5. Funciones en línea.

Una función en línea es aquella que retorna una tabla como valor, y que puede ser utilizada en la parte FROM de una consulta. Oracle proporciona dos formas para que una función retorne un conjunto de registros: funciones que retornan un array de tipo TABLE, y funciones pipeline. 8.5.1. Funciones que retornan una tabla anidada. Para que una función en línea retorne un array deben cumplirse dos condiciones:

- El array (VARRAY o TABLE) debe ser definido con el comando CREATE TYPE. - Los elementos del array deben pertenecer a un tipo base (como NUMBER, VARCHAR2, etc.) o a un tipo OBJECT personalizado. (No funciona con tipos RECORD.)

Ilustraremos este capítulo con un ejemplo. Crearemos la función OBTEN_ALUMNOS, que retornará un array con el nif y nombre de los alumnos almacenados en la tabla Alumno. Previamente se crea un tipo para contener los campos nif y nombre: CREATE TYPE OAlumno AS OBJECT (nif VARCHAR2(9), nombre VARCHAR2(60));

Y ahora creamos el tipo del array: CREATE TYPE TARR_Alumnos AS TABLE OF OAlumno;

Por último, la función puede crearse dentro de un paquete o con CREATE FUNCTION: CREATE OR REPLACE FUNCTION OBTEN_ALUMNOS RETURN TARR_Alumnos

IS

Tarr TARR_Alumnos := TARR_Alumnos();

BEGIN

FOR fila IN (SELECT nif, nombre FROM Alumno) LOOP

Tarr.Extend();

Tarr( Tarr.Last ) := OAlumno( fila.nif, fila.nombre );

END LOOP;

RETURN Tarr;

END;

Ahora podemos usar esta función con la cláusula FROM aplicando la función TABLE de la siguiente forma: SELECT * FROM TABLE( OBTEN_ALUMNOS() );

Un resultado posible de esta consulta puede ser el siguiente:

NIF NOMBRE

----------------- --------------------------

11111111A Juan

22222222B Marian

A veces puede ser necesario forzar el tipo devuelto por la función en línea: SELECT * FROM TABLE( CAST (OBTEN_ALUMNOS() AS TARR_Alumnos );

Si el tipo de array devuelve elementos de un tipo simple, la consulta retornará una única columna con el nombre COLUMN_VALUE. Por ejemplo, si hemos definido el siguiente tipo: CREATE TYPE VARR_Ordinales AS VARRAY(10) OF NUMBER;

Y ejecutamos la siguiente consulta: SELECT * FROM TABLE (VARR_Ordinales(3, 4, 7));

El resultado será el siguiente:

COLUMN_VALUE

---------------------

3

4

7

8.5.2. Funciones pipeline de tabla. Para que mejorar los tiempos de acceso a los registros devueltos por una función en línea se debe utilizar la técnica de pipeline. Esta técnica requiere que previamente creemos un tipo para la tabla que debe retornarse y un tipo para los registros de dicha tabla. Por ejemplo, si queremos crear una función que retorne los alumnos de una localidad determinada, primero debemos crear un objeto del tipo del registro:

Page 137: Java y Oracle 11g

Oracle /137

CREATE OR REPLACE TYPE Reg_Alumno AS OBJECT (nif VARCHAR2(9), apellidos VARCHAR2(100));

Ahora podemos crear el tipo de la tabla a retornar: CREATE OR REPLACE TYPE Tabla_Alumnos AS TABLE OF Reg_Alumno;

Y ahora ya podemos crear la función: CREATE OR REPLACE fn_AlumnosDeLocalidad (pLocalidad VARCHAR2)

RETURN Tabla_Alumnos PIPELINED

IS

CURSOR c1 IS (SELECT nif, apellidos FORM Alumno WHERE localidad = pLocalidad);

BEGIN

FOR fila IN c1 LOOP

PIPE ROW ( Reg_Alumno(fila.nif, fila.apellidos));

END LOOP;

RETURN;

END;

Al usar la palabra clave PIPELINED en la cabecera se mejoran los tiempos de respuesta de estas funciones, ya que los datos son devueltos conforme se ejecuta la función. Cada llamada al comando PIPE ROW retorna una registro del tipo de la tabla que queremos retornar, y Reg_Alumno() crea un objeto del tipo de registro. Podemos usar la función de la siguiente manera: SELECT * FROM TABLE ( fn_AlumnosDeLocalidad('Madrid') );

Este tipo de funciones no pueden usarse directamente en código PL/SQL puesto que realmente no retornan un valor. Por tanto, el siguiente código sería erróneo: DECLARE

vTabla Tabla_Alumnos;

BEGIN

vTabla := AlumnosDeLocalidad('Madrid'); -- Sintaxis errónea

END;

8.6. Instrucción «FORALL».

La instrucción FORALL permite realizar una serie de comandos INSERT, UPDATE, o DELETE de forma masiva normalmente más rápidamente que el bucle FOR equivalente. Este comando admite varias sintaxis: FORALL índice IN rango [SAVE EXCEPTIONS] Comando_SQL;

FORALL índice INDICES OF colección [BETWEEN inicio AND fin] [SAVE EXCEPTIONS] Comando_SQL;

FORALL índice VALUES IF colección_de_índices[SAVE EXCEPTIONS] Comando_SQL;

La cláusula opcional SAVE EXCEPTIONS provoca que si falla una de las operaciones no se produzca una excepción inmediata, sino que se continúa la ejecución hasta que finaliza el bucle y entonces se genera una única excepción. Los detalles de los errores están disponibles después del bucle en el atributo SQL%BULK_EXCEPTIONS. El siguiente código muestra cómo acceder a los errores en el boque EXCEPTIONS de un bloque anónimo: DECLARE

Errores NUMBER;

BEGIN

-- Una instrucción FORALL que provoca excepciones

EXCEPTION

WHEN OTHERS THEN

errores := SQL%BULK_EXCEPTIONS.COUNT;

DBMS_OUTPUT.PUT_LINE('Número de instrucciones que han fallado: ' || errores);

FOR i IN 1..errores LOOP

DBMS_OUTPUT.PUT_LINE('Error #' || i || ' ocurrió durante la iteración # '||

SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);

DBMS_OUTPUT.PUT_LINE('Mensaje de error: ' ||

SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));

END LOOP;

END;

Como ejemplo de uso del comando FORALL podemos eliminar sucesivamente registros cuyos id's estén asignados en una colección: DECLARE

TYPE T_Lista_ids IS VARRAY(5) OF INTEGER; -- El tipo de una colección para 5 elementos enteros

Page 138: Java y Oracle 11g

Oracle /138

lista T_Lista_ids := T_Lista_ids(1,3,6,34,40); -- Una variable del tipo de colección con valores iniciales

BEGIN

FORALL i IN 1..5

DELETE FROM UnaTabla WHERE id = lista(i); -- Se toma el valor de la lista en el índice 'i'

-- o de esta forma:

FORALL i IN INDICES OF lista BETWEEN lista.FIRST AND lista.LAST

DELETE FROM UnaTabla WHERE id = lista(i); -- Se toma el valor de la lista en el índice 'i'

END;

Las siguientes restricciones se aplican al comando FORALL: • No podemos iterar a través de elementos de un array asociativo que tenga un tipo string como clave. • Dentro del bucle FORALL, no podemos referenciar la misma colección en las cláusulas SET y WHERE de un comando UPDATE simultáneamente. Puede ser necesario hacer una copia de la colección. • Podemos usar comandos FORALL sólo en programas del lado servidor. • Los comandos INSERT, UPDATE o DELETE deben referenciar al menos una colección. Por ejemplo, un comando FORALL que inserta un conjunto de valores constantes lanza una excepción. • Cuando especificamos un rango explícito, todos los elementos de la colección en el rango deben existir. Si un elemento falta o fue borrado se obtendrá un error. • Cuando se usa las cláusulas INDICES OF o VALUES OF, todas las colecciones referenciadas en el comando DDL deben ser accedidas usando los valores de la variable índice. • No se puede referenciar un campo de registro individual dentro del comando DML llamado por el comando FORALL. En vez de eso hay que especificar todo el registro con la cláusula SET ROW en el comando UPDATE, o la cláusula VALUES en el comando INSERT.

8.7. Objetos grandes (LOB).

Los objetos grandes (Large objects) son los nuevos tipos de datos nativos que Oracle soporta para almacenar cantidades de datos muy grandes, de hasta 4 Gb. Existen cuatro tipos:

• CLOB: Objeto grande de caracteres ASCII o código ASCII extendido. • BLOB: Objeto grande de contenido binario. • BFILE: No es un objeto grande propiamente dicho, es un puntero a un elemento en el sistema de ficheros. Su tamaño máximo dependerá de las características del sistema de ficheros. El borrado del tipo BFILE no implica el borrado del fichero sino de la referencia. • NCLOB: Objeto grande de caracteres multibyte.

Estos tipos permiten almacenar y manipular tipos de datos que requieren un gran volumen de almacenamiento, como el contenido multimedia, por ejemplo. El acceso a este tipo de datos es similar al del resto de los tipos nativos, pero el sistema permite algunas bibliotecas adicionales para la manipulación especial de estos datos, DBMS_LOB o las de manipulación de cadenas. Su inicialización puede ser a NULL o bien haciendo uso de las funciones EMPTY_BLOB, EMPTY_CLOB y BFILENAME. 8.7.1. Paquete «DBMS_LOB». El paquete DBMS_LOB proporciona funciones y procedimientos para manipular datos de tipo LOB. Todos los subprogramas que usan DBMS_LOB trabajan sobre localizadores LOB. Un localizador LOB representa un LOB que existe en una base de datos o en un archivo externo. Un LOB interno existe como un campo de una tabla de la base de datos, un LOB externo existe en un archivo físico externo, un LOB temporal existe como una variable obtenida programáticamente. A continuación se hace un resumen de las funciones y procedimientos de este paquete:

• El procedimiento APPEND añade el contenido de un origen LOB(src_lob) a un destino LOB (dest_lob). DBMS_LOB.APPEND (dest_lob IN OUT BLOB, src_lob IN BLOB);

DBMS_LOB.APPEND (dest_lob IN OUT CLOB,src_lob IN CLOB);

• El procedimiento CLOSE cierra un LOB abierto previamente. DBMS_LOB.CLOSE (lob_locIN OUT BLOB);

DBMS_LOB.CLOSE (lob_loc IN OUT CLOB);

DBMS_LOB.CLOSE (lob_loc IN OUT BFILE);

• Las funciones COMPARE comparan dos LOB's enteros o parte de ellos. Retornan cero si son iguales y no cero si son distintos.

DBMS_LOB.COMPARE (lob_1 IN BLOB, lob_2 IN BLOB, amount IN INTEGER:= 4294967295,

offset_1 IN INTEGER:=1, offset_2 IN INTEGER:=1) RETURN INTEGER;

Page 139: Java y Oracle 11g

Oracle /139

DBMS_LOB.COMPARE (lob_1 IN CLOB, lob_2 IN CLOB, amount IN INTEGER:= 4294967295,

offset_1 INTEGER:=1, offset_2 IN INTEGER:=1) RETURN INTEGER;

DBMS_LOB.COMPARE (lob_1 IN BFILE, lob_2 INBFILE, amount IN INTEGER,

offset_1 INTEGER:=1,offset_2 IN INTEGER:=1) RETURN INTEGER;

• El procedimiento CONVERTTOBLOB lee caracteres desde un origen CLOB o NCLOB, los convierte y los escribe a un BLOB destino en formato binario, retornando el nuevo desplazamiento.

DBMS_LOB.CONVERTTOBLOB(dest_lob IN OUT BLOB, src_lob IN CLOB, amount IN INTEGER,

dest_offset IN OUT INTEGER, src_offset IN OUT INTEGER, blob_csid IN NUMBER,

lang_ctx IN OUT INTEGER, warning OUT INTEGER);

• El procedimiento CONVERTTOCLOB toma como origen un BLOB, convierte los datos binarios en caracteres y escribe el resultado en un CLOB o NCLOB destino, y retorna el nuevo desplazamiento.

DBMS_LOB.CONVERTTOCLOB(dest_lob IN OUT CLOB, src_lobIN BLOB, amount IN INTEGER,

dest_offset IN OUT INTEGER, src_offset IN OUT INTEGER, blob_csid IN NUMBER,

lang_ctx IN OUT INTEGER, warning OUT INTEGER);

• Los procedimientos COPY copian todo, o parte, de un origen LOB en un destino LOB. DBMS_LOB.COPY (dest_lob IN OUT BLOB, src_lob IN BLOB, amount IN INTEGER,

dest_offset IN INTEGER:=1, src_offset IN INTEGER:=1);

DBMS_LOB.COPY (dest_lob IN OUT CLOB, src_lob IN CLOB, amount IN INTEGER,

dest_offset IN INTEGER:=1, src_offset IN INTEGER:=1);

• El procedimiento CREATETEMPORARY crea un BLOB o CLOB temporal y su correspondiente índice en el espacio de tablas temporal por defecto.

DBMS_LOB.CREATETEMPORARY (lob_loc IN OUT BLOB, cache IN BOOLEAN, dur IN PLS_INTEGER := 10);

DBMS_LOB.CREATETEMPORARY (lob_loc IN OUT CLOB, cache BOOLEAN, dur PLS_INTEGER := 10);

El parámetro dur puede tomar los valores SESSION o CALL. • El procedimiento ERASE borra todo o parte de un LOB.

DBMS_LOB.ERASE (lob_loc IN OUT BLOB, amount IN OUT INTEGER, offset IN INTEGER:=1);

DBMS_LOB.ERASE (lob_loc IN OUT CLOB, amount IN OUT INTEGER, offset IN INTEGER:=1);

• El procedimiento FILECLOSE cierra un archivo previamente abierto. DBMS_LOB.FILECLOSE (file_loc IN OUT BFILE);

• El procedimiento FILECLOSEALL cierra todos los archivos previamente abiertos. DBMS_LOB.FILECLOSEALL;

• La función FILEEXISTS indica si existe un archivo en el servidor. Retorna 1 si existe y cero si no existe. DBMS_LOB.FILEEXISTS (file_loc IN BFILE) RETURN INTEGER;

• El procedimiento FILEGETNAME obtiene el nombre de objeto directorio y el nombre de archivo. DBMS_LOB.FILEGETNAME (file_loc IN BFILE, dir_alias OUTVARCHAR2, filename OUT VARCHAR2);

• La función FILEISOPEN indica si un archivo, asociado a un BFILE, está abierto. Retorna cero si no está abierto y 1 si lo está.

DBMS_LOB.FILEISOPEN (file_loc INBFILE) RETURN INTEGER;

• El procedimiento FILEOPEN abre un archivo en un modo de apertura. DBMS_LOB.FILEOPEN (file_loc IN OUT BFILE,open_mode IN BINARY_INTEGER := file_readonly);

El modo de apertura puede ser: file_readonly:=0, lob_readonly:=0, lob_readwrite:=1. • El procedimiento FREETEMPORARY libera los BLOB o CLOB temporales del espacio de tablas temporal por defecto del usuario.

DBMS_LOB.FREETEMPORARY (lob_loc IN OUT BLOB);

DBMS_LOB.FREETEMPORARY (lob_loc IN OUT CLOB);

• Las funciones GETCHUNKSIZE retornan la cantidad de espacio usado en un trozo LOB para guardar el valor LOB.

DBMS_LOB.GETCHUNKSIZE (lob_loc IN BLOB) RETURN INTEGER;

DBMS_LOB.GETCHUNKSIZE (lob_loc IN CLOB) RETURN INTEGER;

• Las funciones GETLENGTH retornan la longitud de un valor LOB. DBMS_LOB.GETLENGTH (lob_loc IN BLOB) RETURN INTEGER;

DBMS_LOB.GETLENGTH (lob_loc IN CLOB) RETURN INTEGER;

DBMS_LOB.GETLENGTH (lob_loc IN BFILE) RETURN INTEGER;

• La función GET_STORAGE_LIMIT retorna el límite del almacén para guardar valores LOB's. DBMS_LOB.GET_STORAGE_LIMIT;

• Las funciones INSTR retornan la posición de una ocurrencia de un patrón dentro de un dato LOB. DBMS_LOB.INSTR (lob_loc IN BLOB, pattern IN RAW, offset IN INTEGER:=1,

Page 140: Java y Oracle 11g

Oracle /140

nth IN INTEGER:=1) RETURN INTEGER;

DBMS_LOB.INSTR (lob_loc IN CLOB, pattern IN VARCHAR2, offset IN INTEGER:=1,

nth IN INTEGER:=1) RETURN INTEGER;

DBMS_LOB.INSTR (lob_loc IN BFILE, pattern IN RAW, offset IN INTEGER:=1,

nth IN INTEGER:=1) RETURN INTEGER;

• Las funciones ISOPEN indican si un LOB fue abierto usando un localizador. DBMS_LOB.ISOPEN (lob_loc IN BLOB) RETURN INTEGER;

DBMS_LOB.ISOPEN (lob_loc IN CLOB) RETURN INTEGER;

DBMS_LOB.ISOPEN (lob_loc IN BFILE) RETURN INTEGER;

• Las funciones ISTEMPORARY indican si un localizador apunta a un LOB temporal. DBMS_LOB.ISTEMPORARY (lob_loc IN BLOB) RETURN INTEGER;

DBMS_LOB.ISTEMPORARY (lob_loc IN CLOB) RETURN INTEGER;

• El procedimiento LOADBLOBFROMFILE lee un BFILE dentro de un BLOB interno. DBMS_LOB.LOADBLOBFROMFILE (dest_lob IN OUT BLOB, src_lobIN BFILE, amount IN INTEGER,

dest_offset IN OUT INTEGER, src_offset IN OUT INTEGER);

• El procedimiento LOADCLOBFROMFILE lee un BFILE dentro de un CLOB interno. DBMS_LOB.LOADCLOBFROMFILE (dest_lob IN OUT CLOB, src_lob IN BFILE, amount IN INTEGER,

dest_offset IN OUT INTEGER, src_offset IN OUT INTEGER, bfile_csid NUMBER,

lang_ctx IN OUT INTEGER, warning OUT INTEGER);

• El procedimiento LOADFROMFILE lee un BFILE dentro de un LOB interno. DBMS_LOB.LOADFROMFILE (dest_lob IN OUT BLOB, src_lobIN BFILE, amount IN INTEGER,

dest_offset IN INTEGER:=1, src_offset IN INTEGER:=1);

DBMS_LOB.LOADFROMFILE (dest_lob IN OUT CLOB, src_lob IN BFILE, amount IN INTEGER,

dest_offset IN INTEGER:=1, src_offset IN INTEGER:=1);

• El procedimiento OPEN abre un LOB (interno, externo o temporal) en el modo indicado. DBMS_LOB.OPEN (lob_loc IN OUT BLOB, open_mode BINARY_INTEGER);

DBMS_LOB.OPEN (lob_loc IN OUT CLOB, open_mode BINARY_INTEGER);

DBMS_LOB.OPEN (lob_loc IN OUT BFILE, open_mode BINARY_INTEGER:=file_readonly);

• El procedimiento READ lee datos de un LOB empezando en un desplazamiento especificado. DBMS_LOB.READ (lob_loc IN BLOB, amount IN OUT BINARY_INTEGER,

offset IN INTEGER, buffer OUT RAW);

DBMS_LOB.READ (lob_loc IN CLOB, amount IN OUT BINARY_INTEGER,

offset IN INTEGER, buffer OUT VARCHAR2);

DBMS_LOB.READ (lob_loc IN BFILE, amount IN OUT BINARY_INTEGER,

offset IN INTEGER, buffer OUT RAW);

• Las funciones SUBSTR retornan parte de un valor LOB. DBMS_LOB.SUBSTR (lob_loc IN BLOB, amount IN INTEGER:=32767, offset IN INTEGER:=1)

RETURN RAW;

DBMS_LOB.SUBSTR (lob_loc IN CLOB, amount IN INTEGER:=32767, offset IN INTEGER:=1)

RETURN VARCHAR2;

DBMS_LOB.SUBSTR (lob_loc IN BFILE, amount IN INTEGER:=32767, offset IN INTEGER:=1)

RETURN RAW;

• El procedimiento TRIM corta el contenido a un nuevo tamaño. DBMS_LOB.TRIM (lob_loc IN OUT BLOB, newlen INTEGER);

DBMS_LOB.TRIM (lob_loc IN OUT CLOB, newlen INTEGER);

• El procedimiento WRITE escribe datos a un LOB a partir de un desplazamiento especificado. DBMS_LOB.WRITE (lob_loc IN OUT BLOB, amount IN BINARY_INTEGER, offset IN INTEGER,

buffer IN RAW);

DBMS_LOB.WRITE (lob_loc IN OUT CLOB, amount IN BINARY_INTEGER, offset IN INTEGER,

buffer IN VARCHAR2);

• El procedimiento WRITEAPPEND escribe datos al final de un LOB. DBMS_LOB.WRITEAPPEND (lob_loc IN OUT BLOB, amount IN BINARY_INTEGER, buffer IN RAW);

DBMS_LOB.WRITEAPPEND (lob_loc IN OUT CLOB, amount IN BINARY_INTEGER, buffer IN VARCHAR2);

8.7.2. Cómo guardar un archivo binario en un campo BLOB. Aunque casi siempre es preferible guardar la ruta de un archivo en la base de datos en lugar del propio archivo en modo binario, existen ciertas circunstancias en las que no nos queda otra solución. Veremos cómo cargar un fichero existente en el servidor en un campo BLOB de una tabla.

Page 141: Java y Oracle 11g

Oracle /141

Si los ficheros se encuentran en el servidor de Oracle, lo primero que debemos hacer es crear un objeto directorio, y debemos permitir explícitamente el acceso al directorio en cuestión al usuario que ejecutará el código PL de inserción. El siguiente script SQL crea el directorio virtual asignándole el nombre lógico DirImagenes. Para poder crear el directorio debemos haber iniciado sesión como DBA. CREATE OR REPLACE DIRECTORY DirImagenes AS 'C:\Oracle\Blob\Imagenes';

Como se ha dicho, para crear objetos directorio debemos haber iniciado la sesión con permisos de DBA, y para leer el contenido de un directorio debemos asignar permisos a los usuarios que lo necesiten. -- Se concede el permiso se acceso a un usuario llamado APP1

GRANT READ ON DIRECTORY DirImagenes TO APP1;

-- Se concede el permiso a todo el mundo

GRANT READ ON DIRECTORY DirImagenes TO PUBLIC;

Lo siguiente que vamos a necesitar es una tabla con un campo BLOB para almacenar la imagen. En este caso vamos a llamar a la tabla "NIF", y su estructura es la siguiente: CREATE TABLE NIF (

Id INT PRIMARY KEY,

Nif NVARCHAR2(9) NOT NULL UNIQUE,

Foto BLOB NULL) ;

El siguiente bloque de código PL nos va a permitir cargar una foto, llamada "imagen.gif" en la tabla. Es importante tener claro que el archivo "imagen.gif" debe existir físicamente en el directorio «C:\Oracle\Blob\Imagenes». DECLARE

v_bfile BFILE;

v_blob BLOB;

BEGIN

-- Para insertar un nuevo registro con la foto

INSERT INTO NIF (Id, Nif, Foto)

VALUES(1, '1111111A', EMPTY_BLOB()) -- se asigna un valor vacío en el campo Foto

RETURNING Foto INTO v_blob; -- se guarda la referencia del campo Foto

-- O bien para actualizar un registro existente con la foto

UPDATE NIF SET Foto = EMPTY_BLOB() WHERE Id=1

RETURNING Foto INTO v_blob; -- se guarda la referencia del campo Foto

-- El código para insertar o actualizar es el mismo

v_bfile := BFILENAME('DIRIMAGENES', 'imagen.gif'); -- lee la ruta del archivo físico

DBMS_LOB.FILEOPEN(v_bfile, DBMS_LOB.FILE_READONLY); -- abre el archivo

DBMS_LOB.LOADFROMFILE(v_blob, v_bfile, DBMS_LOB.GETLENGTH(v_bfile)); -- lee el contenido

DBMS_LOB.FILECLOSE(v_bfile); -- cierra el archivo

COMMIT;

EXCEPTION

WHEN OTHERS THEN

ROLLBACK;

RAISE;

END;

Hay tres aspectos a comentar de este código: • El uso de RETURNING en la sentencia INSERT y UPDATE. Nos permite establecer una referencia al campo Foto que es insertada en la variable v_blob. • La función EMPTY_BLOB. Nos permite insertar un valor vacío en un campo BLOB. • La función BFILENAME. Esta función devuelve un objeto BFILE que representa la ruta del fichero "imagen.gif" que queremos almacenar en la tabla. • El uso del paquete predefinido de Oracle DBMS_LOB. Es el paquete que proporciona Oracle para trabajar con tipos largos. Utilizamos las siguientes funciones:

- FILEOPEN: Abre el archivo definido por BFILE (v_bfile) en el modo indicado (en nuestro caso solo lectura). - LOADFROMFILE: Lee un determinado número de bytes (en nuestro caso todos) del fichero definido por BFILE(v_bfile) en un objeto de tipo BLOB (v_blob). - GETLENGTH: Devuelve el tamaño del archivo en bytes. - FILECLOSE: Cierra el archivo

Page 142: Java y Oracle 11g

Oracle /142

El resultado de todo esto es que hemos conseguido almacenar la imagen en la base de datos. De forma análoga se puede cargar el contenido de un archivo de texto en un campo de tipo CLOB. 8.7.3. Cómo usar CLOB para campos de texto de más de 4000 caracteres. El tipo de dato CLOB permite crear campos en tablas de Oracle de tipo texto que contenga más de los 4000 caracteres permitidos por VARCHAR2. Como CLOB no es un tipo básico y se comporta como un objeto, para acceder a él se tienen que utilizar las funciones especificadas en DBMS_LOB. Como ejemplo, la tabla "Documento" incluye un campo CLOB: CREATE TABLE Documento (

Id INTEGER PRIMARY KEY,

Titulo NVARCHAR2(100),

Contenido CLOB);

Para insertar un contenido corto podemos utilizar la instrucción INSERT habitual: INSERT INTO Documento VALUES (1, 'Primer documento', 'Documento de ejemplo');

Algunas de las funciones del paquete DBMS_LOB que podemos usar para manipular campos CLOB son: • SUBSTR(campo, longitud, posicionInicial): retorna una subcadena de tipo VARCHAR2 de un campo CLOB dado. • GETLENGTH(campo): retorna el tamaño de un campo CLOB dado.

Por ejemplo, si queremos seleccionar registros por parte del contenido podemos realizar una consulta como la que sigue: SELECT * FROM Documento WHERE DBMS_LOB.SUBSTR(Contenido, 4000, 1) LIKE '%loquesea%';

Si el contenido es mayor de 4000 caracteres deberemos realizar consultas sucesivas modificando la posición inicial. Si queremos modificar el campo Contenido en un registro podemos hacerlo de la siguiente manera: DECLARE

c CLOB;

texto VARCHAR2(100);

BEGIN

SELECT Contenido INTO c FROM Documento WHERE Id=1 FOR UPDATE;

texto := 'Un texto al inicio del contenido';

DBMS_LOB.WRITE (c, LENGTH(texto), 1, texto);

COMMIT;

END;

El código PL precedente modifica el campo Contenido del registro de Id 1 añadiendo un texto al principio del contenido actual. Nótese el uso de la cláusula FOR UPDATE en la consulta que recupera la referencia del campo CLOB. Este cláusula es necesaria para bloquear el acceso al registro. Oracle no permite actualizar un campo CLOB si no se aplica un bloqueo. 8.7.4. Cómo leer un documento de texto en una variable de tipo CLOB. Supongamos creado un objeto DIRECTORY denominado DOCS que contiene el documento que queremos leer. Un bloque de código para copiar el contenido de un documento de texto dentro de una variable de tipo CLOB sería el siguiente: DECLARE

v_bfile BFILE;

v_clob CLOB;

BEGIN

DBMS_LOB.CREATETEMPORARY(v_clob,TRUE); -- se inicializa el CLOB en el espacio de tablas temporal

v_bfile := BFILENAME('DOC', 'texto.txt'); -- lee la ruta del archivo físico

DBMS_LOB.FILEOPEN(v_bfile, DBMS_LOB.FILE_READONLY);

DBMS_LOB.LOADFROMFILE(v_clob, v_bfile, DBMS_LOB.GETLENGTH(v_bfile));

DBMS_LOB.FILECLOSE(v_bfile);

-- Código para procesar el contenido de la variable "v_clob"

--

END;

9. SQL Dinámico

Se habla de ejecución dinámica cuando la sentencia SQL que va a ejecutar nuestro servidor no está escrita en su totalidad previamente, sino que se crea cada vez que tenemos que ejecutarla. Frente a esta forma de trabajar tenemos el SQL "normal", donde la sentencia SQL esta previamente escrita y lo único que hay que

Page 143: Java y Oracle 11g

Oracle /143

hacer es decidir cuándo la ejecutamos.

9.1. Sentencias DML con SQL dinámico.

PL/SQL ofrece la posibilidad de ejecutar sentencias SQL a partir de cadenas de caracteres. Para ello debemos emplear la instrucción EXECUTE IMMEDIATE. Podemos obtener información acerca del número de filas afectadas por la instrucción ejecutada por EXECUTE

IMMEDIATE utilizando SQL%ROWCOUNT. El siguiente ejemplo muestra la ejecución de un comando SQL dinámico. DECLARE

ret NUMBER;

FUNCTION fn_Ejecuta RETURN NUMBER IS

sql_str VARCHAR2(1000);

BEGIN

sql_str := 'UPDATE Alumno SET nombre = ''Nuevo nombre'' WHERE nif=''22222222B''';

EXECUTE IMMEDIATE sql_str;

RETURN SQL%ROWCOUNT;

END fn_Ejecuta ;

BEGIN

ret := fn_Ejecuta;

DBMS_OUTPUT.PUT_LINE(TO_CHAR(ret));

END;

Podemos además parametrizar nuestras consultas a través de variables host. Una variable host es una variable que pertenece al programa que está ejecutando la sentencia SQL dinámica y que podemos asignar en el interior de la sentencia SQL con la palabra clave USING. Las variables host van precedidas de dos puntos (:). El siguiente ejemplo muestra el uso de variables host para parametrizar una sentencia SQL dinámica. DECLARE

ret NUMBER;

FUNCTION fn_Ejecuta (pNombre VARCHAR2, pNif VARCHAR2) RETURN NUMBER

IS

sql_str VARCHAR2(1000);

BEGIN

sql_str := 'UPDATE Alumno SET nombre = :new_nombre WHERE nif = :nif';

EXECUTE IMMEDIATE sql_str USING pNombre, pNif;

RETURN SQL%ROWCOUNT;

END fn_Ejecuta ;

BEGIN

ret := fn_Ejecuta ('Nuevo nombre', '2222222B');

DBMS_OUTPUT.PUT_LINE(TO_CHAR(ret));

END;

9.2. Cursores con SQL dinámico.

Con SQL dinámico también podemos utilizar cursores dinámicos. 9.2.1. Cursores dinámicos implícitos. Para utilizar un cursor implícito solo debemos construir nuestra sentencia SELECT en una variable de tipo caracter y ejecutarla con EXECUTE IMMEDIATE utilizando la cláusula INTO o BULK COLLECT INTO para recuperar el resultado de la sentencia. El siguiente ejemplo ejecuta dinámicamente una consulta que retorna el número de alumnos y asigna el valor de retorno en la variable l_cnt. DECLARE

str_sql VARCHAR2(255);

l_cntVARCHAR2(20);

BEGIN

str_sql := 'SELECT COUNT(*) FROM Alumno';

EXECUTE IMMEDIATE str_sql INTO l_cnt;

DBMS_OUTPUT.PUT_LINE('El número de alumnos es ' || l_cnt);

END;

Estos cursores dinámicos implícitos también admiten variables de host, como en el siguiente ejemplo, donde se obtiene el número de alumnos de una localidad determinada (en este caso Madrid):

Page 144: Java y Oracle 11g

Oracle /144

DECLARE

str_sql VARCHAR2(255);

l_cntVARCHAR2(20);

BEGIN

str_sql := 'SELECT COUNT(*) FROM Alumno WHERE localidad = :local';

EXECUTE IMMEDIATE str_sql USING 'Madrid' INTO l_cnt;

DBMS_OUTPUT.PUT_LINE('El número de alumnos de Madrid es ' || l_cnt);

END;

Sólo los comandos INSERT, UPDATE y DELETE pueden tener variables enlazadas de salida. Estas variables pueden enlazarse con la cláusula RETURNING BULK COLLECT INTO del comando EXECUTE IMMEDIATE. En el siguiente ejemplo se muestra esto mediante una consulta de actualización dinámica que modifica las notas de algunos alumnos y recupera los apellidos de los mismos. DECLARE

TYPE ListaNifs IS TABLE OF VARCHAR2(9);

nifs ListaNifs;

nota_corte NUMBER := 5;

consulta VARCHAR(200);

BEGIN

consulta := 'UPDATE Nota SET valor = valor+1 WHERE valor < :1 RETURNING nif INTO :2';

EXECUTE IMMEDIATE consulta USING nota_corte RETURNING BULK COLLECT INTO nifs;

FOR nif IN 1..nifs.LAST LOOP -- Se muestran los nifs de alumnos

DBMS_OUTPUT.PUT_LINE(nif);

END LOOP;

END;

Para enlazar varias variables de entrada de un comando SQL, podemos usar el comando FORALL y la cláusula USING, tal como se muestra en el siguiente ejemplo. El comando SQL no puede ser una consulta. DECLARE

TYPE ListaIds IS TABLE OF NUMBER(38);

TYPE ListaNifs IS TABLE OF VARCHAR2(9);

idModulos ListaIds;

nifs ListaNifs;

BEGIN

idModulos := ListaIds(1, 3, 8, 10);

FORALL i IN 1..4 EXECUTE IMMEDIATE

'UPDATE Nota SET valor = valor+1 WHERE idModulo = :1 RETURNING nif INTO :2'

USING idModulos(i) RETURNING BULK COLLECT INTO nifs;

END;

En este ejemplo, mediante el comando FORALL se actualiza la nota de alumnos en cada uno de los módulos establecidos en la colección idModulos, y se recupera el nif de los alumnos de los cuales se actualizó la nota dentro de la colección nifs. 9.2.2. Cursores dinámicos explícitos. Trabajar con cursores explícitos es también muy fácil. Únicamente destacar el uso de REF CURSOR para declarar un nuevo tipo del cursor generado con SQL dinámico. DECLARE

TYPE CURSOR_DINAMICO IS REF CURSOR; -- Se crea un tipo de cursor dinámico

c_cursor CURSOR_DINAMICO; -- Se declara una variable del nuevo tipo

fila Alumno%ROWTYPE;

v_queryVARCHAR2(255);

BEGIN

v_query := 'SELECT * FROM Alumno';

OPEN c_cursor FOR v_query; -- Se abre el cursor dinámico

LOOP

FETCH c_cursor INTO fila;

EXIT WHEN c_cursor%NOTFOUND;

DBMS_OUTPUT.PUT_LINE(fila.nif);

END LOOP;

CLOSE c_cursor;

END;

Page 145: Java y Oracle 11g

Oracle /145

Al contrario que los cursores estáticos, los cursores dinámicos no pueden ser utilizados con el bucle FOR. Las variables host también se pueden utilizar en los cursores mediante la palabra USING. DECLARE

TYPE CURSOR_DINAMICO IS REF CURSOR; -- Se crea un tipo de cursor dinámico

c_cursor CURSOR_DINAMICO; -- Se declara una variable del nuevo tipo

v_queryVARCHAR2(255);

BEGIN

v_query := 'SELECT * FROM Alumno WHERE localidad = :1';

OPEN c_cursor FOR v_query USING 'Madrid'; -- Se abre el cursor dinámico

. . .

CLOSE c_cursor;

END;

9.2.3. El paquete «DBMS_SQL». Antes de Oracle9i el SQL dinámico requería del uso del paquete DBMS_SQL. Todavía podemos seguir usando las funciones y procedimientos de este paquete. Usando DBMS_SQL podemos tener un mayor control sobre el flujo de proceso del SQL dinámico, pero es generalmente más complejo de escribir que el SQL dinámico nativo visto previamente. El paquete DBMS_SQL incorpora las siguientes funciones para gestionar cursores dinámicos:

• DBMS_SQL.OPEN_CURSOR, crea un cursor dinámico y retorna un valor entero que lo identifica. • DBMS_SQL.PARSE, asigna una consulta a un cursor y lo ejecuta. Tiene tres parámetros: el identificador del cursor devuelto por DBMS_SQL.OPEN_CURSOR, el texto de la consulta, y la constante DBMS_SQL.NATIVE. • DBMS_SQL.CLOSE_CURSOR, cierra un cursor dinámico. Tiene un único parámetro: el identificador del cursor devuelto por DBMS_SQL.OPEN_CURSOR.

Antes de nada, como usuario SYS, necesitamos darle privilegios de ejecución sobre DBMS_SQL al usuario que va a ejecutar el procedimiento: GRANT EXECUTE ON DBMS_SQL TO usuario;

Un ejemplo sencillo sería el siguiente: CREATE OR REPLACE PROCEDURE DBMS_EJEMPLO

AS

ID INTEGER;

BEGIN

ID := DBMS_SQL.OPEN_CURSOR;

DBMS_SQL.PARSE(ID, 'CREATE TABLE Prueba (c1 NUMBER(10)) ', DBMS_SQL.NATIVE);

DBMS_SQL.CLOSE_CURSOR(ID);

END;

Para que este procedimiento pueda crear la tabla, previamente necesitaremos darle al usuario permisos para crear dicha tabla o crear cualquier tabla: DBMS_SQL.PARSE(ID, 'GRANT CREATE ANY TABLE TO ' || USER );

También podemos usar variables host del siguiente modo: DECLARE

idc INTEGER;

v_id INTEGER;

v_nif VARCHAR2(9);

tmp INTEGER;

BEGIN

idc := DBMS_SQL.OPEN_CURSOR;

DBMS_SQL.PARSE (idc,'SELECT idMatricula, nifFROM Matricula WHERE año= :anho',DBMS_SQL.NATIVE);

-- Llenamos el parámetro que enviamos

DBMS_SQL.BIND_VARIABLE(idc, ':anho', 2008);

-- Definimos las columnas que recibiremos del Select. Declaramos que el tipo de dato de

-- las columnas deberá ser del mismo tipo de dato que las variables resultado.

DBMS_SQL.DEFINE_COLUMN(idc, 1, v_id);

DBMS_SQL.DEFINE_COLUMN(idc, 2, v_nif, 9);

-- Ejecutamos el cursor.

TMP := DBMS_SQL.EXECUTE(idc);

-- La función FETCH_ROWS recupera filas y retorna el número de filas que quedan

WHILE DBMS_SQL.FETCH_ROWS(idc) > 0 LOOP

Page 146: Java y Oracle 11g

Oracle /146

-- Tomamos el valor de las columnas 1 y 2

DBMS_SQL.COLUMN_VALUE(idc, 1, v_id);

DBMS_SQL.COLUMN_VALUE(idc, 2, v_nif);

. . .

END LOOP;

-- Cerramos el cursor

DBMS_SQL.CLOSE_CURSOR(idc);

END;

9.3. Un ejemplo de cómo usar y cómo no usar SQL dinámico.

Para concluir vamos a ver un ejemplo donde podemos usar SQL dinámico. A veces tenemos que crear una sentencia que ordene una tabla según una columna que dependerá de algún parámetro que nos pasa el usuario. Eso lo podemos resolver utilizando una función almacenada y una sentencia como: CREATE PROCEDURE OrdenarPor (pColumna VARCHAR2)

IS

v_SQL varchar2(50);

BEGIN

v_SQL := 'INSERT INTO UnaTabla SELECT * FROM OtraTabla ORDER BY ' || pColumna;

EXECUTE IMMEDIATE v_SQL;

END;

Esto está en la línea de todo lo que hemos estado viendo. Es una solución sencilla pero plantea varios problemas de seguridad y rendimiento. Por ejemplo, si se invoca el procedimiento de la siguiente forma: OrdenarPor('nombre; DROP TABLE OtraTabla;');

Estamos permitiendo inyección de código perjudicial. Así que intentaremos crear un procedimiento almacenado con código normal que haga lo mismo, pero con más seguridad: CREATE PROCEDURE OrdenarPor (pColumna VARCHAR2)

IS

V_SQL varchar2(50);

BEGIN

INSERT INTO UnaTabla

SELECT * FROM OtraTabla ORDER BY

CASE pColumna

WHEN 'columna1' THEN TO_CHAR(columna1)

WHEN 'columna2' THEN TO_CHAR(columna2)

ELSE TO_CHAR(columna3)

END;

END;

Cierto que es un poco más complicado (se utiliza la función CASE), pero no hay inyección de código posible y una vez calculado el plan de ejecución se utilizará sin recompilar una y otra vez. Como conclusión nos queda que el SQL dinámico es una herramienta potente pero peligrosa si no se trata con cuidado, y que la mayoría de las veces no necesitamos recurrir a ella porque siempre podemos encontrar otra solución.

10. PL/SQL y Java

Otra de las virtudes de PL/SQL es que permite trabajar conjuntamente con Java.PL/SQL es un excelente lenguaje para la gestión de información, pero en ocasiones podemos necesitar de un lenguaje de programación más potente. Por ejemplo, podríamos necesitar consumir un servicio Web, conectar a otro servidor, trabajar con sockets, etc. Para estos casos podemos trabajar conjuntamente con PL/SQL y Java. Para poder trabajar con Java y PL/SQL debemos realizar los siguientes pasos:

- Crear el programa Java y cargarlo en la base de datos. - Crear un programa de recubrimiento (Wrapper) de PL/SQL.

10.1. Creación de Objetos Java en la base de datos ORACLE.

Oracle incorpora su propia versión de la máquina virtual Java y del JRE. Esta versión de Java se instala conjuntamente con Oracle. Para crear objetos Java en la base de datos podemos utilizar la utilidad LoadJava de Oracle desde la línea de

Page 147: Java y Oracle 11g

Oracle /147

comandos o bien crear objetos JAVA SOURCE en la propia base de datos. La sintaxis para la creación de objetos JAVA SOURCE en Oracle es la siguiente. CREATE [OR REPLACE] AND COMPILE JAVA SOURCE

NAMED mombreOrigenJava

AS

public class NombreDeClase {

...

};

El siguiente ejemplo crea y compila una clase Java OracleJavaClass en el interior del objeto JAVA SOURCE

FuentesJava. Un aspecto muy a tener en cuenta es que los métodos de la clase Java que queramos invocar desde PL/SQL deben ser estáticos. CREATE OR REPLACE AND COMPILE JAVA SOURCE

NAMED FuentesJava

AS

public class OracleJavaClass {

public static String saluda(String nombre) {

return ("Hola desde Java " + nombre);

}

};

Un mismo objeto JAVA SOURCE puede contener varias clases de Java. CREATE OR REPLACE AND COMPILE JAVA SOURCE

NAMED FuentesJava

AS

public class OracleJavaClass {

public static String saluda(String nombre) {

return ("Hola desde Java" + nombre);

}

}

public class OracleJavaMejorada{

public static String saludoMejorado(String nombre) {

return ("Saludo mejorado desde Java para " + nombre);

}

};

La otra opción sería guardar nuestro código java en el archivo OracleJavaClass.java, compilarlo y cargarlo en Oracle con LoadJava. A continuación se muestran ejemplos del uso de la utilidad LoadJava. loadJava -help

loadJava -userusuario/password@basedatos -v -f -r OracleJavaClass.class

loadJava -userusuario/password@basedatos -v -f -r OracleJavaClass.java

También se proporciona el comando DropJava para descargar clases previamente cargadas.

10.2. Ejecución de programas Java con PL/SQL

Una vez que tenemos listo el programa de Java debemos integrarlo con PL/SQL. Esto se realiza a través de subprogramas de recubrimiento llamados wrappers. No podemos crear un wrapper en un bloque anónimo. La sintaxis general es la siguiente: CREATE [OR REPLACE] FUNCTION|PROCEDURE nombre [(parámetros)]

[RETURN tipo]

IS|AS

LANGUAGE JAVA NAME 'clase.metodo [ return tipo]' ;

El siguiente ejemplo muestra el wrapper para nuestra función Saludo. CREATE OR REPLACE FUNCTION Saluda_wrap (nombre VARCHAR2)

RETURN VARCHAR2

AS

LANGUAGE JAVA NAME

'OracleJavaClass.saluda(java.lang.String) return java.lang.String';

Una vez creado el wrapper, podremos ejecutarlo como cualquier otra función o procedimiento de PL/SQL. Debemos crear un wrapper por cada función java que queramos ejecutar desde PL/SQL. Cuando ejecutemos el wrapper, es decir, la función "Saluda_wrap", internamente se ejecutará la clase java y se

Page 148: Java y Oracle 11g

Oracle /148

invocará el método estático "OracleJavaClass.saluda". Un aspecto a tener en cuenta es que es necesario proporcionar el nombre del tipo java completo, es decir, debemos especificar java.lang.String en lugar de únicamente String. La ejecución de este ejemplo en SQL*Plus: SQL> SELECT SALUDA_WRAP('DEVJOKER') FROM DUAL;

Genera la siguiente salida:

SALUDA_WRAP('DEVJOKER')

--------------------------------------

Hola desde Java DEVJOKER

Una recomendación de diseño sería agrupar todos los wrapper en un mismo paquete PL. En el caso de que nuestro programa Java necesitase de paquetes Java adicionales, deberíamos cargarlos en la base de datos con la utilidad LoadJava.

10.3. Correspondencia de tipos entre Java y Oracle.

Cuando creamos un wrapper entre un procedimiento de Oracle y un método de Java, que incluya parámetros, debemos especificar un tipo de dato de Java que sea compatible con el tipo de dato de Oracle. La siguiente tabla muestra la correspondencia de conversión de los tipos Java más comunes a tipos Oracle:

Tipos de Java Tipos de Oracle

byte, short, int, long, float, double Number

Char, String Char, Varchar2, Nchar, Nvarchar2

java.sql.Date Date

java.sql.ResultSet Ref Cursor

La siguiente tabla muestra la correspondencia de conversión de los tipos Oracle a tipos Java:

Tipos de Oracle Tipos de Java

Number java.math.BigDecimal

Char, Varchar2, Nchar, Nvarchar2 String

Date oracle.sql.TIMESTAMP

Blob oracle.sql.BLOB

Clob oracle.sql.CLOB

VARRAY o TABLE oracle.sql.ARRAY

Tipo de objeto de Oracle oracle.sql.STRUCT

10.4. Paso de cursores Oracle a métodos de Java.

Como ejemplo, veremos cómo utilizar un cursor de Oracle dentro del código Java. Para ello se crea en Java un método llamado procesaConsulta que recibe como parámetro un objeto java.sql.ResultSet. CREATE OR REPLACE AND COMPILE JAVA SOURCE

NAMED MiClaseJava AS

public class MiClase {

public static void procesaConsulta (java.sql.ResulSet rs) {

while (rs.next) {

// procesamos los registros

}

}

};

Para poder invocar el código de método Java se crea un wrapper dentro de un paquete, como el siguiente: CREATE OR REPLACE PACKAGE MI_PAQUETE IS

TYPE Mi_Cursor IS REF CURSOR; -- Declaramos el tipo de cursor dinámico

PROCEDURE Procesa_Consulta ( mc Mi_Cursor) IS

LANGUAGE JAVA NAME 'MiClase.procesaConsulta(java.sql.ResultSet)' ;

END MI_PAQUETE;

Ahora podemos invocar el procedimiento, por ejemplo desde el siguiente bloque: DECLARE

mc MI_PAQUETE.Mi_Cursor;

BEGIN

OPEN mc FOR 'SELECT ''Ejemplo de cursor'' AS texto FROM DUAL';

MI_PAQUETE.Procesa_Consulta(mc);

Page 149: Java y Oracle 11g

Oracle /149

CLOSE mc;

END;

10.5. Paso de objetos Oracle a métodos de Java.

Como se verá en un capítulo posterior, el modelo objeto-relacional de Oracle permite crear tipos de objetos dentro de una base de datos. Por ejemplo, un objeto Oracle que almacene la información de un cliente se crearía con la siguiente instrucción: CREATE OR REPLACE TYPE Cliente_t AS OBJECT (

codigo NUMBER(6,0); -- campo 0

nombre NVCHAR2(100); -- campo 1

apellidos NVACHAR2(200); -- campo 2

);

Los objetos Oracle pueden ser pasados a métodos de Java encapsulados con el tipo oracle.sql.STRUCT. (Este tipo está incluido con la librería del driver JDBC de Oracle para Java.) Y ahora, creamos un método de Java que reciba como parámetro un objeto Client_t y retorne el nombre completo con el formato "apellidos, nombre": CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED MiClaseJava AS

public class MiClase {

public static String nombreCompleto (oracle.sql.STRUCT c) throws java.sql.SQLException {

// Se obtienen los campos de cliente como un array de objetos

Object[] campos = c.getAttributes();

// Para acceder a los campos debemos moldearlos al tipo apropiado

String nombre = (String) (campo[1]); // campo[1] == nombre

String apel = (String) (campo[2]); // campo[2] == apellidos

// Se retorna el valor

return apel + ', ' + nombre;

}

};

La función de Oracle que mapeará la función de Java es: CREATE OR REPLACE FUNCTION NombreCompleto(c Cliente_t)

RETURN NVARCHAR2 AS LANGUAGE JAVA NAME

"MiClase.nombreCompleto(oracle.sql.STRUCT) return java.lang.String";

10.5.1. Funcionalidades de la clase «STRUCT». Si compilamos código con el estándar JDBC 2.0, entonces podemos crear instancias de java.sql.Struct, y usar sus métodos estándar siguientes:

• getAttributes(map): Recupera el valor de los atributos, usando entradas en el mapa especificado para determinar las clases Java que usar y materializar cualquier atributos perteneciente a la estructura del tipo de objeto Oracle. Los tipos Java para cualquier otro valor de atributo debería ser el mismo que para las llamadas al método getObject() del tipo SQL subyacente. • getAttributes(): Similar al método getAttributes(map), excepto que usa los tipos por defecto para los atributos. • getSQLTypeName(): Retorna un string Java que representa el nombre completo cualificado (esquema.nombreDelTipoSql) del tipo de objeto Oracle que representa esta estructura.

Si queremos tomar las ventajas de las funcionalidades extendidas ofrecidas por los método definidos de Oracle, entonces podemos crear instancias de oracle.sql.STRUCT. La clase oracle.sql.STRUCT implementa la interfaz java.sql.Struct y proporciona funcionalidades más allá del estándar JDBC 2.0. Los métodos adicionales de esta clase son los siguientes:

• getOracleAttributes(): Recupera los valores del array de valores como objetos oracle.sql.*. • getDescriptor(): Retorna el objeto StructDescriptor para el tipo SQL que se corresponde con el objeto STRUCT. • getJavaSQLConnection(): Retorna la conexión actual (java.sql.Connection). • toJdbc(): Consulta el mapa de tipos por defecto de la conexión para determinar qué clases mapean, y entonces usa toClass(). • toJdbc(map): Consulta el mapa de tipos especificado para determinar que clases mapean, y entonces usa toClass().

Page 150: Java y Oracle 11g

Oracle /150

10.5.2. Descriptores STRUCT. Crear y usar un objeto STRUCT requiere un descriptor (una instancia de la clase oracle.sql.StructDescriptor) que defina la correspondencia entre el tipo SQL (como Cliente_t) y su correspondiente objeto STRUCT. Antes de crear un objeto STRUCT debe existir primero un StructDescriptor para el tipo de objeto Oracle. Si no existe, podemos crearlo mediante el método estático StructDescriptor.createDescriptor(). Este método requiere que pasemos el nombre del tipo SQL y un objeto de conexión: StructDescriptor structdesc = StructDescriptor.createDescriptor(nombre_tipo_sql, conexion);

Una vez que tenemos el objeto StructDescriptor para el tipo de objeto de Oracle, podemos crear el objeto STRUCT. Para hacer esto necesitamos pasar en su constructor el StructDescriptor, el objeto de conexión y un array de objetos Java que contenga los valores para los atributos. STRUCT struct = new STRUCT(structdesc, conexion, atributos);

Donde atributos es un array del tipo java.lang.Object[]. Un StructDescriptor puede pasar a través de un tipo de objeto. Esto significa que contiene información sobre el tipo de objeto, incluyendo el código de tipo, el nombre del tipo, y cómo convertirlo. La clase StructDescriptor incluye los siguientes métodos:

• getName(): Retorna el nombre completo cualificado del tipo SQL (esto es, esquema y nombre del tipo Oracle, como Empleado.Cliente_t). • getLength(): Retorna el número de campos del tipo de objeto. • getMetaData(): Retorna los metadatos del tipo (de forma similar al método getMetaData() del un objeto ResultSet). El objeto ResultSetMetaData retornado contiene el nombre de atributos, código del tipo de atributo, e información de precisión del tipo de atributos.

10.5.3. Funcionalidad de buffering automático para los atributos. El driver JDBC de Oracle proporciona métodos públicos para habilitar y deshabilitar el "buffering" de los atributos de una estructura. Los siguientes métodos se incluyen dentro de la clase oracle.sql.STRUCT: public void setAutoBuffering(boolean enable)

public boolean getAutoBuffering()

El método setAutoBuffering(boolean) habilita o deshabilita el auto-buffering. El método getAutoBuffering() retorna el modo actual. Por defecto el auto-buffering está deshabilitado. El auto-buffering es útil en aplicaciones JDBC cuando los atributos de STRUCT son accedidos más de una vez mediante los métodos getAttributes() y getArray().

Nota. El buffering puede causar que las aplicaciones JDBC consuman una significativa cantidad de memoria.

10.6. Paso de arrays desde un programa Java a un procedimiento almacenado de Oracle.

En este apartado se explicará cómo pasar un array a un procedimiento de Oracle desde un programa de Java. Previamente se crea en la base de datos un tipo de VARRAY y un procedimiento almacenado que recibe como parámetro un VARRAY y lo retorna modificado: -- Se crea el tipo de VARRAY de como máximo 10 números

CREATE TYPE Tipo_Varray IS VARRAY(10) OF NUMBER;

-- Un procedimiento que incrementa en una unidad los elementos del VARRAY

CREATE OR REPLACE PROCEDURE INCREMENTAR(V IN OUT TIPO_VARRAY)

IS

BEGIN

FOR i IN V.FIRST..V.LAST LOOP

V(i) := V(i) + 1;

END LOOP;

END;

En el siguiente ejemplo se muestra cómo pasar un array de Java al procedimiento almacenado INCREMENTAR de Oracle: import java.math.BigDecimal;

import java.sql.*;

import oracle.jdbc.OracleCallableStatement;

import oracle.jdbc.OracleTypes;

import oracle.sql.*;

public class MiClase {

Page 151: Java y Oracle 11g

Oracle /151

public static void main(String[] args) throws Exception {

Connection conn = null;

OracleCallableStatement comando = null;

// Se carga el driver de Oracle. Previamente se debe añadir la librería correspondiente al classpath

Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();

// Se realiza la conexión y se crea el comando de invocación del procedimiento

conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:BD", "propietario", "password");

comando = (OracleCallableStatement) conn.prepareCall("{call INCREMENTAR(?,?)}");

// Se inicializan las variables y parámetros de tipo array

ArrayDescriptor ad = new ArrayDescriptor("TIPO_VARRAY", conn);

double [] numeros = {1.1, 2.3, 4.45, 6.8};

ARRAY unArray = new ARRAY(ad, conn, numeros);

// Se pasa el parámetro al comando y se registra como parámetro de salida

comando.setARRAY(1, unArray);

comando.registerOutParameter(1, OracleTypes.ARRAY, "TIPO_VARRAY");

// Se ejecuta el comando y se recupera el parámetro

comando.execute();

BigDecimal[] nos = (BigDecimal []) comando.getARRAY(1).getArray();

. . .

// Se cierra la conexión

conn.close();

}

}

Hay que resaltar dos cuestiones: - El nombre del tipo array debe ser pasado en mayúsculas ("TIPO_ARRAY") porque así es como lo guarda Oracle. Para más seguridad podemos indicar el propietario del tipo, también en mayúsculas: "PROPIETARIO.TIPO_ARRAY". - Al recuperar el parámetro debemos moldearlo como un array de objetos java.math.BigDecimal y no como objetos Double. Como alternativa se puede usar el método comando.getARRAY(1).getDoubleArray(), el cual devuelva ya un array de tipo double. Análogamente, el tipo ARRAY posee los métodos getFloatArray(), getIntArray(), getLongArray() y getShortArray().

Nota. Sólo es posible pasar arrays de Java a arrays de Oracle creados con el comando CREATE TYPE.

10.7. Cómo pasar y retornar un array de objetos a través de un procedimiento almacenado.

Este último ejemplo combina el uso de objetos y arrays. Supongamos creado en Oracle los siguientes tipos y función: -- Se crea un tipo de objeto

CREATE TYPE Venta_t AS OBJECT (

producto VARCHAR2(20); -- campo 0

precio NUMBER(14,2); -- campo 1

);

-- Se crea un tipo de array anidado

CREATE TYPE Ventas_TA AS TABLE OF Venta_t;

-- Una función que recibe un array de ventas y lo retorna modificado aplicando un IVA.

CREATE OR REPLACE PROCEDURE ActualizarVentas(v Ventas_TA, iva NUMBER) RETURN Ventas_TA

IS

BEGIN

FOR I IN v.FIRST..v.LAST LOOP

v(I).precio := v(I).precio *(1+iva);

END LOOP;

RETURN v;

END;

El código siguiente invoca la función almacenada, para ello crea un array de objetos de venta y lo pasa como parámetro a la función. Una vez ejecutada la función se recupera el array modificado: import java.math.BigDecimal;

import java.sql.*;

import oracle.jdbc.OracleCallableStatement;

Page 152: Java y Oracle 11g

Oracle /152

import oracle.jdbc.OracleTypes;

import oracle.sql.*;

public class MiClase {

public static void main(String[] args) throws Exception {

Connection conn = null;

OracleCallableStatement comando = null;

// Se realiza la conexión y se crea el comando de invocación del procedimiento

conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:BD", "propietario", "password");

comando = (OracleCallableStatement) conn.prepareCall("{? = call ActualizarVentas( ?,?)}");

// Se crea un elemento de tipo venta como un objeto STRUCT

STRUCT [] items = new STRUCT[2]; // este array contendrá las ventas

StructDescriptor sd = new StructDescriptor("VENTA_T", conn);

Object [] campos1 = {"producto 1", 12.4};// este array representa los datos de una venta

items[0] = new STRUCT(sd, conn, campos1); // añado la primera venta

Object [] campos2 = {"producto 2", 10.24}; // este array representa los datos de otra venta

items[1] = new STRUCT(sd, conn, campos2); // añado la segunda venta

// Se crea el array que pasaremos por parámetro del tipo ARRRAY

ArrayDescriptor ad = new ArrayDescriptor("VENTAS_TA", conn);

ARRAY lasVentas = new ARRAY(ad, conn, items);

// Se registra el primer parámetro como de salida, y se pasa valor a los otros parámetros

comando.registerOutParameter(1, OracleTypes.ARRAY, "VENTA_TA");

comando.setARRAY(2, lasVentas);

comando.setARRAY(3, 16.00);

// Se ejecuta el comando y se recupera el parámetro

comando.execute();

Object [] resultado = (Object []) comando.getARRAY(1).getArray();

// Se recupera la primera venta

STRUCT venta1 = (STRUCT) resultado[0];

// Se recuperan los valores de los campos de venta como un array de objetos

Object [] campo = venta1. getAttributes();

// Se imprime el precio de la primera venta:

System.out.println("Precio con iva = " + campo[1]);

. . .

// Se cierra la conexión

conn.close();

}

}

10.8. Paquete «DBMS_JAVA».

El paquete DBMS_JAVA incluye funciones y procedimientos que proporcionan un punto de entrada para acceder a las funcionalidades del sistema de gestión de bases de datos relacionales (RDBMS) mediante Java. A continuación se describen brevemente las funciones y procedimientos que incluye:

• FUNCTION longname (shortname VARCHAR2) RETURN VARCHAR2 Retorna el nombre completo cualificado de un objeto de Java especificado. Debe tenerse en cuenta que las clases y métodos de Java pueden tener un nombre excesivamente largo para un identificador SQL, de forma que la Máquina Virtual Java de Oracle usa abreviaturas internas para el acceso a SQL.

• FUNCTION shortname (longname VARCHAR2) RETURN VARCHAR2 Esta función es la inversa a longname().

• FUNCTION get_compiler_option(name VARCHAR2, optionName VARCHAR2) RETURN VARCHAR2 Retorna el valor de la opción especificada en el segundo parámetro para el compilador Java o SQLJ proporcionados con Oracle. El primer parámetro es un nombre de paquete Java, un nombre de clase completamente cualificado o un string vacío.

• PROCEDURE set_compiler_option(name VARCHAR2, optionName VARCHAR2, value VARCHAR2) Asigna el valor de la opción especificada en el segundo parámetro para el compilador Java o SQLJ proporcionados con Oracle. El primer parámetro es un nombre de paquete Java, un nombre de clase completamente cualificado o un string vacío.

• PROCEDURE reset_compiler_option(name VARCHAR2, optionName VARCHAR2) Asigna el valor por defecto de la opción especificada en el segundo parámetro para el compilador Java o

Page 153: Java y Oracle 11g

Oracle /153

SQLJ proporcionados con Oracle. • FUNCTION resolver(name VARCHAR2, owner VARCHAR2, type VARCHAR2) RETURN VARCHAR2

Retorna la especificación de resolución para un objeto especificado en name (con su nombre corto), dentro del esquema especificado en owner, con el tipo de objeto especificado en type (que puede ser 'SOURCE' o 'CLASS'). El invocador debe tener privilegios EXECUTE y acceso al objeto para usar esta función. Por ejemplo, si se invoca dbms_java.resolver('tst', 'SCOTT', 'CLASS') el valor devuelto puede ser ((*

SCOTT)(* PUBLIC)). • FUNCTION derivedFrom(name VARCHAR2, owner VARCHAR2, type VARCHAR2) RETURN VARCHAR2

Retorna el nombre del origen del objeto especificado (con su nombre corto), con el esquema y tipo especificados (que puede ser 'SOURCE' o 'CLASS'). El invocador debe tener privilegios EXECUTE y acceso al objeto para usar esta función.

• FUNCTION fixed_in_instance(name VARCHAR2, owner VARCHAR2, type VARCHAR2) RETURN NUMBER Retorna el estado de guardado del objeto especificado (con su nombre corto), con el esquema y tipo especificados (que puede ser 'RESOURCE', 'SOURCE', 'CLASS', o 'SHARED_DATA'). El invocador debe tener privilegios EXECUTE y acceso al objeto para usar esta función. El valor devuelto es 0 para indicar no guardado, y 1 para indicar guardado.

• PROCEDURE set_output(buffersize NUMBER) Redirige la salida de los procedimientos almacenados de Java y triggers al paquete DBMS_OUTPUT.

• PROCEDURE start_debugging(host VARCHAR2, port NUMBER, timeout NUMBER) Inicia el agente de depuración sobre un host y puerto especificados.

• PROCEDURE stop_debugging Detiene el agente de depuración.

• PROCEDURE restart_debugging(timeout NUMBER) Reinicia el agente de depuración.

• PROCEDURE export_source(name VARCHAR2, schema VARCHAR2, blob BLOB) • PROCEDURE export_source(name VARCHAR2, blob BLOB) • PROCEDURE export_source(name VARCHAR2, clob CLOB)

Estos procedimientos se usan para exportar un origen Java como un objeto de esquema de origen Java a una base de datos de Oracle. El origen se especifica en el parámetro name. Se puede especificar un esquema, si no se utilizará el actual.

• PROCEDURE export_class(name VARCHAR2, schema VARCHAR2, blob BLOB) • PROCEDURE export_class(name VARCHAR2, blob BLOB)

Estos procedimientos se usan para exportar una clase de Java como un objeto de esquema de clase Java a una base de datos de Oracle. La clase se especifica en el parámetro name. Se puede especificar un esquema, si no se utilizará el actual.

• PROCEDURE export_resource(name VARCHAR2, schema VARCHAR2, blob BLOB) • PROCEDURE export_resource(name VARCHAR2, blob BLOB) • PROCEDURE export_resource(name VARCHAR2, schema VARCHAR2, clob CLOB) • PROCEDURE export_resource(name VARCHAR2, clob CLOB)

Estos procedimientos se usan para explorar un recurso a una base de datos Oracle como un objeto de esquema de recurso.

• PROCEDURE loadjava(options VARCHAR2) • PROCEDURE loadjava(options VARCHAR2, resolver VARCHAR2)

Estos procedimientos permiten cargar clases dentro de la base de datos, siendo una alternativa del comando de línea loadjava.

• PROCEDURE dropjava(options VARCHAR2) Elimina clases dentro de la base de datos, siendo una alternativa del comando de línea dropjava.

• PROCEDURE grant_permission(grantee VARCHAR2, tipo VARCHAR2, nombre VARCHAR2, accion VARCHAR2) Permite asignar permisos para usuarios o roles especificados. Donde: - grantee es el nombre de usuario, esquema o rol, o bien el valor 'PUBLIC' para indicar todos los usuarios. - tipo es la clase de permiso a conceder. Por ejemplo, para dar acceso a un archivo el tipo de permiso debe ser 'FilePermission'. Este argumento requiere un nombre completo cualificado de una clase que extienda a java.lang.security.Permission. Si la clase no está dentro de SYS, el nombre debe ser prefijado por

Page 154: Java y Oracle 11g

Oracle /154

el esquema; por ejemplo, miEsquema:miPaquete.MiPermiso. - nombre es el nombre del atributo tal como se define en la clase Permission. - acciones el tipo de acción que queremos especificar según el tipo de permiso. Por ejemplo, para un 'FilePermission' podemos especificar 'read' o 'write'. Por ejemplo, para dar permiso de escritura en un fichero se utilizará:

dbms_java.revoke_permission('MIESQUEMA', 'SYS:java.io.FilePermission', 'miarchivo.txt', 'write' );

• PROCEDURE restrict_permission(grantee VARCHAR2, tipo VARCHAR2, nombre VARCHAR2, accion VARCHAR2) Permite especificar limitaciones o excepciones a reglas generales.

• PROCEDURE grant_policy_permission(grantee VARCHAR2, esquemaPermiso VARCHAR2, tipo VARCHAR2, nombre

VARCHAR2) Se usa para conceder y limitar permisos del tipo PolicyTablePermission.

• PROCEDURE revoke_permission(esquema VARCHAR2, tipo VARCHAR2, nombre VARCHAR2, accion VARCHAR2) Permite revocar un permiso concedido.

• PROCEDURE disable_permission(key NUMBER) Permite desactivar un permiso concedido.

• PROCEDURE enable_permission(key NUMBER) Permite activar un permiso.

• PROCEDURE delete_permission(key NUMBER) Permite eliminar un permiso concedido.

• procedure set_preference(user VARCHAR2, type VARCHAR2, abspath VARCHAR2, key VARCHAR2, value

VARCHAR2) Permite insertar o actualizar un registro en la tabla SYS:java$prefs$ como sigue:

CALL dbms_java.set_preference('SCOTT', 'U', '/my/package/method/three', 'windowsize', '22:32');

El parámetro user especifica el nombre del esquema. El parámetro type puede ser 'U' (usuario) o 'S' (sistema). El parámetro abspath especifica la ruta absoluta de la preferencia. El parámetro key especifica la clase usada para buscar y value el valor de la clave preferente.

Page 155: Java y Oracle 11g

Oracle /155

IV. CARACTERÍSTICAS DE ORACLE GRID

La g de Oracle Database 11g significa "grid" (rejilla). La rejilla soporta la habilidad de tratar la infraestructura como un servicio compartido cuya composición es gestionada fuera de la vista de las aplicaciones clientes. En este sentido, el servicio de computación puede ser análogo a una central eléctrica en la cual los sistemas que proporcionan el servicio no son vistos directamente por los usuarios finales. El origen de la electricidad y el sistema usado para manejarla no son vistos por los usuarios.

1. Arquitectura de Rejilla

En una arquitectura de rejilla podemos gestionar recursos que soporten dinámicamente las necesidades del usuario final. La arquitectura de rejilla ayuda a mejorar la disponibilidad de la aplicación y también soporta el aprovisionamiento de recursos para manejar cargas de trabajo cambiantes. Los recursos de la rejilla incluyen servidores, discos e instancias. En versiones anteriores Oracle proporcionaba capacidades para alguna de las funcionalidades de la rejilla, pero Oracle Database 11g introduce herramientas adicionales para facilitar la administración de la rejilla en nuestras aplicaciones, proporcionando un conjunto completo de opciones. Funcionalidades soportadas por la rejilla incluyen:

• «Real Application Clusters» (RAC). Podemos crear varias instancias que accedan a la misma base de datos, permitiendo distribuir la carga de memoria entre varios servidores. Se pueden añadir nuevas instancias al clúster si es necesario. • «Automated Storage Management» (ASM). Podemos crear grupos de discos disponibles y asignar ficheros de datos a los grupos de discos. Oracle entonces gestionará el acceso al grupo de discos y redistribuirá los ficheros como sea necesario. • Tecnología de replicación. La réplica rápida de datos entre bases de datos soporta el uso de las bases de datos como parte de una rejilla de orígenes de datos disponibles. La tecnología de replicación incluye tablespaces transportables, vistas materializadas, replicación basada en registros, y transacciones distribuidas. • Tecnología de seguridad. Podemos apoyar el uso compartido de bases de datos por aplicaciones diferentes o procesos comerciales. En una base de datos compartida podemos tener que crear bases de datos virtuales que prevengan el acceso no autorizado de los datos que pertenecen a otras áreas de negocio o aplicaciones. • Planificación de recursos mediante «Oracle Enterprise Manager» (OEM). Podemos usar los procedimientos del paquete DBMS_SCHEDULER para crear programas en Oracle que soporten la ejecución de scripts al nivel del sistema operativo además de tareas que se ejecuten con la base de datos. Podemos crear programas mediante el procedimiento CREATE_PROGRAM, planificarlos mediante CREATE_SCHEDULE, y enviar la tarea mediante CREATE_JOB.

Veremos los fundamentos de las funcionalidades de rejilla y como encajan en la arquitectura de computación de la rejilla. La implementación de la rejilla requiere que la infraestructura sea configurada para soportar la arquitectura distribuida y flexible requerida.

1.1. Hardware y elementos de configuración del sistema operativo.

Para soportar una arquitectura de rejilla con varias instancias debemos habilitar el soporte de instancias sobre servidores independientes. Si las instancias son parte de un entorno RAC, el entorno debe tener un área de disco compartido que sea accesible mediante redes de alta velocidad. Oracle se ejecuta sobre la mayoría de sistemas operativos, así que las capacidades de rejilla no serán dificultadas por las restricciones de un sistema operativo particular. RAC no es soportado a través de sistemas operativos en el entorno de rejilla. En entornos RAC y de rejilla se usan normalmente servidores "blade". Un "blade" es un sistema computacional que incluye procesadores y memoria sobre una única placa en el servidor, mientras que otras funcionalidades de servidor son compartidas mediante un conjunto de "blades". (Cada servidor "blade" es una delgada "tarjeta" que contiene únicamente microprocesador, memoria y buses.) Podemos instalar un nuevo "blade" dentro de un servidor, y el "blade" tendrá las ventajas del poder, acceso a red, almacenamiento, y capacidades de infraestructura ya instaladas. Los servidores "blade" pueden modificarse para soportar requerimientos de proceso adicionales sin impactar significativamente al área requerida para soportar el servidor. Los servidores mismos deben tener acceso virtual (esto es, una aplicación de usuario debería ser capaz de conectarse a una dirección IP que a su vez pueda conectarse a otra de varios servidores en un conjunto). Si

Page 156: Java y Oracle 11g

Oracle /156

uno de los servidores no está habilitado, otros servidores en el conjunto serán capaces de atender los requerimientos sin interrumpir la aplicación. Los usuarios que estén conectados a la aplicación tendrán sus conexiones redirigidas a una instancia de otro servidor. Los servidores deben tener discos externos compartidos, una dirección IP privada, una dirección IP pública, e interruptores redundantes. La arquitectura de almacenamiento debe también soportar el concepto de rejilla (varios servidores accediendo a un servicio de disco compartido). La arquitectura de servidor y el sistema operativo debe soportar una tecnología de almacenamiento que soporte acceso concurrente para el servicio de disco compartido. La arquitectura de rejilla de Oracle soporta «Storage Area Networks» (SANs), «Network Attached Storage» (NAS) e iSCSI (una forma en modo bloque de NAS). Cuando se usan en la rejilla, SANs e iSCSI requieren un sistema de ficheros de clúster o volúmenes raw. Los volúmenes NAS pueden ser compartidos a través de servidores sin la necesidad de un sistema de ficheros de clúster. El almacenamiento compartido puede ser usado por los ficheros críticos de la base de datos (los ficheros de datos, ficheros de control, ficheros de registro de deshacer en línea, y ficheros de parámetros del sistema) además de los ficheros adicionales para administrar el clúster de instancias. Los ficheros deben ser accesibles por todos los servidores del clúster. El software de Oracle puede también ser almacenado en el área compartida. Los sistemas de ficheros tradicionales no soportan los requerimientos de acceso de las arquitecturas de rejilla, así que necesitamos usar un sistema de ficheros que soporte los requerimientos de acceso concurrente. Las opciones más comunes son dispositivos raw y sistemas de ficheros de clúster. Los dispositivos raw han sido usados por años para soportar los entornos RAC de Oracle porque soportan acceso concurrente para varios procesadores. Sin embargo, su administración ha sido tradicionalmente más compleja que la de los sistemas de ficheros. Tanto los dispositivos raw como los sistemas de ficheros requieren almacenamiento en modo bloque como SANs e iSCSI. En general, los sistemas de fichero son más simples de manejar que los dispositivos raw (ya que pueden ser fácilmente redimensionados, movidos y renombrados). En general, la velocidad de acceso es comparable. A menos que tengamos razones específicas para usar dispositivos raw en nuestro entorno, deberíamos favorecer el uso de sistemas de ficheros de clúster en nuestra rejilla. Oracle proporciona su propio sistema de ficheros de clúster, el «Oracle Cluster File System» (OCFS) para soportar acceso compartido a ficheros. 1.1.1. «Oracle Cluster File System». Se ha diseñado «Oracle Cluster File System» (OCFS) para soportar acceso concurrente a un conjunto compartido de ficheros de base de datos. Se puede descargar desde la página oficial de Oracle. En general, la instalación de OCFS sigue los siguientes pasos:

1. Descargar el software OCFS para nuestro entorno. 2. Descargar la documentación de instalación relacionada. Nótese que el proceso de instalación y el software es específico para cada sistema operativo y versión. 3. Realizar la instalación. 4. Copiar las utilidades OCFS dentro del directorio compartido (en UNIX, /usr/sbin). 5. Usar la utilidad OCFSTOOL para crear los ficheros de configuración (como /etc/ocf.conf). 6. Asegurarse de que el comando LOAD_OCFS es automáticamente ejecutado en el arranque (en Oracle Database 11g, esto debería realizarse durante la instalación). 7. Usar la utilidad FDISK para crear las particiones apropiadas que serán formateadas con el «Oracle Cluster File System». Usar OCFSTOOL para formatear las particiones. 8. Crear puntos de montaje para el sistema de ficheros OCFS (/u01, /u02, y así) y montar cada sistema de ficheros. 9. Crear entradas en el fichero /etc/fstab de cada sistema de ficheros OCFS.

Como con el almacenaje conectado por red, es útil reanudar uno o varios nodos RAC después de la instalación para verificar que el número correcto de volúmenes OCFS han sido montados. 1.1.2. Acceso a otros servidores. En un entorno RAC debemos ser capaces de ejecutar comandos sobre todos los otros servidores del clúster sin proporcionar una contraseña. Por ejemplo, en UNIX debemos ser capaces de usar los comandos rsh y rcp entre los servidores. Deberíamos trabajar con nuestros administradores de red y sistema para determinar la solución técnica apropiada para nuestro entorno. Nuestra elección para estos requerimientos puede estar limitada por las políticas y prácticas de seguridad de nuestro entorno. Por ejemplo, podemos necesitar usar los ficheros /home/oracle/rhosts para permitir hosts equivalentes para el usuario de Oracle. Podemos especificar los hosts con los cuales las conexiones están permitidas mediante el

Page 157: Java y Oracle 11g

Oracle /157

método de acceso rhosts. 1.1.3. «Oracle Cluster Manager». El «Oracle Cluster Manager» (OCM) se instala mediante el Instalador Universal de Oracle. OCM requiere que los nombres de host público y privado de cada servidor en el clúster sean introducidos en dos pantallas separadas. Debemos asegurarnos de que estos nombres correspondan exactamente para evitar problemas en los pasos de instalación posteriores. OCM facilita un agente que monitoriza los servidores para determinar si están todavía disponibles. Dependiendo de la versión de Oracle RAC que usemos, este agente puede denominarse "watchdogd" o "hangcheck-timer". Debemos crear directorios de registro (como $ORACLE_HOME/oracm/log) para que Oracle los use antes de comenzar el OCM. Ahora se puede iniciar OCM e instalar el software Oracle RAC.

1.2. Añadiendo servidores a la rejilla.

Cuando un servidor está disponible ("provisional") para una base de datos RAC, el administrador de sistemas deber realizar varios pasos:

1. Traer el servidor dentro de la misma red que los otros nodos del clúster. 2. Habilitar el nuevo servidor para acceder al almacenamiento compartido. 3. Habilitar el nuevo servidor para acceder a otros servidores del clúster. 4. Instalar el software para el clúster en el servidor. El nuevo servidor debería ser reconocido como parte del mismo clúster que los otros servidores, y debería tener el acceso apropiado al almacenamiento compartido. Hay que asegurarse de que el nuevo servidor usa el mismo software de clúster que usen los otros servidores del clúster. 5. Instalar el software de Oracle en el servidor. Usar el Instalador Universal de Oracle para instalar el software de Oracle y añadir el nuevo servidor al la base de datos RAC mediante el Asistente de Creación de Bases de datos (DBCA). Durante la instalación (la cual requiere la edición empresarial), seleccionar la instalación sólo de software, con lo cual no se crea una base de datos. Ejecutar el fichero de configuración (sobre UNIX, es root.sh) para completar la instalación.

1.2.1. Añadir instancias a la rejilla. Para crear una instancia sobre el nuevo servidor, hay que asegurarse de que el «Global Services Daemon» (GSD) esté ejecutándose sobre todos los servidores. Para arrancar GSD sobre UNIX hay que usar el siguiente comando: gsdctl start

Una vez que se está ejecutando GSD, hay que arrancar el OUI y elegir la opción "Cluster database". Debemos ir a la página de administración de instancia, en la cual podemos decidir añadir instancias al clúster existente. Se nos solicitará información similar a la mostrada durante la instalación de la base de datos (detalles del grupo de registros de rehacer, información de tablespace y nombre de instancia). Cuando aceptemos las opciones, el DBCA comenzará y creará la instancia requerida. Para eliminar una instancia de la rejilla, debemos ir en el OUI a la página de administración de instancia y seleccionar borrar instancia.

1.3. Compartir datos entre la rejilla.

En una rejilla podemos disponer de varias bases de datos RAC o de varias bases de datos de instancia única, los cuales podemos administrar a través de las tecnologías de la rejilla. Para soportar varias configuraciones de base de datos, Oracle ofrece un conjunto de tecnologías. Las tecnologías de replicación incluyen:

• Enlaces de base de datos. Podemos establecer enlaces entre cuentas de bases de datos independientes, para usarlos solo en replicaciones de solo lectura o para actualizar copias de tablas. • Procedimientos y triggers. Para mantener datos replicados sincrónicamente, podemos crear un trigger que cree una transacción en una base de datos remota cuando una transacción sea confirmada en una base de datos local. Si la transacción en la base de datos remota falla, la transacción local falla y las dos bases de datos permanecen sincronizadas. • Vistas materializadas. Podemos crear vistas materializadas basadas sobre objetos remotos. Las vistas materializadas pueden ser refrescadas con datos desde el sistema fuente y confirmadas al momento o sobre una planificación predeterminada. Además de usar registros de vistas materializadas, podemos usar refrescos rápidos para enviar cambios incrementales desde el sistema fuente a la vista materializada.

Page 158: Java y Oracle 11g

Oracle /158

• Tablespaces transportables. Podemos mover grandes volúmenes de datos desde una base de datos a otra mediante el transporte de tablespaces. Por ejemplo, podemos "despegar" un tablespace de un entorno de pruebas y entones "pegarlo" en varias bases de datos diferentes. Desde Oracle Database 10g, las bases de datos fuente y destino pueden estar en diferentes plataformas. • Tablas externas. Podemos tratar ficheros externos de la base de datos como si fuesen tablas de la misma, consultándolas como si las líneas de los ficheros fuesen filas de una tabla. Estos ficheros pueden estar localizados en discos compartidos. • «Oracle Streams». Esta tecnología usa las entradas de registros de rehacer para generar el SQL necesario para replicar transacciones entre bases de datos. • Data Pump Export e Import. Podemos usar estas tecnologías para crear tareas en el lado servidor que extraigan y carguen datos desde nuestras bases de datos.

Podemos usar todas estas tecnologías para hacer que los datos estén disponibles a través de nuestra rejilla.

1.4. Administración de la rejilla.

En Oracle Database 11g, Oracle Enterprise Manager (OEM) introduce o modifica varios componentes diseñados para soportar la administración de la rejilla. Estos componentes incluyen los siguientes:

• «Grid Control Console». Una interfaz OEM que permite de forma fácil administrar todos los hosts, bases de datos, oyentes, y aplicaciones servidoras de nuestro entorno computacional. • «Oracle Management Agent». Un proceso desplegado sobre cada host monitoreado. Es responsable de supervisar todos los objetivos del host, para comunicar esta información al administrador de servicio intermedio, y para manejar y mantener el host y sus objetivos. El agente requiere al menos 360 MB de espacio inicial de disco y 20 MB de memoria. • «Oracle Management Service». Una aplicación J2EE que muestra la «Grid Control Console», trabaja con todos los agentes de administración para procesar información de tareas y supervisión, y usar el «Management Repository» como almacén de datos. El «Oracle Management Service» se instala y es desplegado sobre el servidor de aplicación de Oracle. El servicio requiere al menos 240 MB de espacio de disco y 1 GB de memoria. El servidor de aplicación requiere 500 MB de disco y al menos 512 de memoria. • «Oracle Management Repository». Dos tablespaces en una base de datos de Oracle que almacena información acerca de los objetivos y aplicaciones manejadas con OEM. El «Oracle Management Agent» recolecta los datos y usa el «Oracle Management Service» para cargarlos dentro del «Oracle Management Repository». El «Oracle Management Repository» puede ser accedido por varios administradores en la rejilla. El tamaño recomendado para el repositorio es de 1 GB de espacio en disco y de 3 GB de memoria.

Cuando se instala el «Oracle Enterprise Manager 11g Grid Control» podemos usar una nueva base de datos o una base de datos existente para el repositorio. Si usamos una base de datos existente, la base de datos debe ser local o remota sobre el servidor sobre el cual realizamos la instalación. El repositorio debe ser instalado en una base de datos para la cual esté habilitado el control de acceso de grano fino (comprobar la vista V$OPTION para verificar esta opción).

Nota. El asistente de configuración del «Enterprise Manager Repository» fallará si el usuario SYSMAN ya existe en la base de datos que se especifica para el repositorio, o si el repositorio ya existe en la base de datos.

Para instalar el «Oracle Management Agent» hay que ejecutar el OUI sobre cada servidor del entorno. Durante la instalación hay que elegir "Additional Management Agent" como tipo de instalación. La opción de instalación silenciosa también está disponible. Oracle proporciona ficheros de respuesta estándar para ser usados por la instalación silenciosa. Después de la instalación se ejecuta el fichero root.sh como superusuario para completar el proceso de instalación. 1.4.1. Habilitar búsqueda de parches y descargas. Podemos usar OEM para acceder a «Oracle Metalink» para buscar parches y descargas para nuestro entorno. Si no especificamos nuestra información de conexión durante la instalación, podemos ir a la página "Grid Control" de OEM y acceder a la página "Setup | Patching Setup". En esta página podemos introducir nuestras credenciales de Metalink y el sitio de búsqueda de parches Metalink: http://oracle.com/support/metalink/index.html

Podemos usar esta configuración para buscar y descargar parches desde la interfaz de OEM.

Page 159: Java y Oracle 11g

Oracle /159

1.4.2. Asignar credenciales para el sistema de tareas. En sistemas basados en Windows (NT 4, Windows 2000, Windows 2003, Windows XP, Windows Vista y Windows 7), podemos asignar las credenciales apropiadas para que el sistema de tareas trabaje apropiadamente con OEM. Cuando enviamos una tarea, el usuario debe tener el permiso "Log on as a batch job" habilitado. Por defecto, el «Management Agent» se instala como un usuario LocalSystem. Para habilitar esta opción en Windows 2000 y Windows XP, hay que iniciar la herramienta "Políticas de seguridad local" en el Panel de Control. Se elige "Políticas locales | Derechos de usuario". Se hace doble clic sobre la entrada "Log on as a batch job" y se añade el nombre del usuario. Hay que reiniciar el servidor para que los cambios tengan efecto. En Windows NT 4, se elige la herramienta "User Manager" dentro de la opción "Programs | Administrative Tools" del menú de inicio. Se selecciona "Policies | User Rights" y se hace clic sobre "Show Advanced User Rights". Se selecciona "Log on as a batch job" en el menú desplegable y se añade el nombre del usuario. 1.4.3. Configurar bases de datos para supervisión. Para habilitar la supervisión de una base de datos debemos permitir que la cuenta DBSNMP sea accesible. Debido a que esta cuenta puede estar bloqueada en nuestro entorno, debemos verificar que es accesible (podemos necesitar usar el comando ALTER USER DBSNMP ACCOUNT UNLOCK). Podemos entonces ir a la página "Configurar base de datos | Propiedades" de la consola de control de rejilla de OEM y configurar la base de datos para supervisión. En esta página se introduce la contraseña para el usuario DBSNMP en la base de datos destino. Para supervisión ASM, navegar a "Targets | All Targets" desde la consola de control de rejilla de OEM. Cuando configuramos el destino debemos introducir la contraseña SYS para usar en supervisión ASM.

1.5. Lanzar OEM.

OEM es una interfaz gráfica para administrar los componentes de la rejilla. Es una interfaz bastante intuitiva, pero hay algunas cosas de las que hay que ser conscientes dentro de OEM:

• Si tenemos configurado un Agente de Administración en el servidor, los destinos soportados sobre el mismo servidor serán detectados y serán supervisados mediante OEM. • Podemos configurar notificación por e-mail automática desde OEM por defecto. Podemos configurar métodos de notificaciones adicionales si es necesario. • Podemos agrupar destinos, crear roles, y crear administradores con OEM.

OEM se apoya sobre el «Oracle Management Repository», «Oracle Management Agent», y «Oracle Management Service» para funcionar apropiadamente. Ya que el repositorio es una base de datos, podemos pararla e iniciarla mediante los comandos SHUTDOWN y STARTUP en SQL*Plus. El «Oracle Management Service», sin embargo, necesita el uso de la utilidad EMCTL instalada con Oracle. Para iniciar el «Oracle Management» se usa el siguiente comando en UNIX: emctl start oms

Y para parar el servicio: emctl stop oms

Podemos comprobar su estado mediante: emctl status oms

Para comenzar todos los componentes del servidor de aplicaciones, incluyendo el «Management Service»: opmnctl startall

Para parar todos los componentes: opmnctl stopall.

En Windows los agentes de administración de Oracle son manejados mediante "Servicios" en el Panel de Control. Para comenzar los agentes sobre plataformas UNIX se usa el comando: emctl start agent

Podemos parar un agente mediante: emctl stop agent

Y podemos comprobar su estado mediante: emctl status agent

Una vez que todos los componentes han sido iniciados, podemos acceder a la «OEM Grid Control Console» mediante la apropiada URL, la cual está normalmente en el siguiente formato: http://nombre_del_host_mgmtserver.dominio:puerto/em

1.5.1. Inicialización de parámetros para el repositorio. La siguiente tabla muestra los parámetros de inicialización de base de datos requeridos para la base de datos

Page 160: Java y Oracle 11g

Oracle /160

en la cual el repositorio es almacenado.

Nota. No hay que asignar valor para RESOURCE_MANAGER_PLAN.

Parámetro Valor mínimo

job_queue_processes 10

db_block_size 8192

remote_login_passwordfile EXCLUSIVE

timed_statistics TRUE

open_cursors 300

shared_pool_size 67108864

dispatchers NULL o no presente en el fichero

aq_tm_processes 1 o mayor

compatible 9.0.1.3 o más grande

La siguiente tabla lista parámetros adicionales recomendados para el «Oracle Management Repository».

Parámetro Valor

db_cache_size 134217728

session_cached_cursors 200

large_pool_size 0

processes 150

fast_start_mttr_target 300

hash_area_size 1048576

sort_area_size 1048576

pga_aggregate_target 33554432

undo_management AUTO

undo_retention 10800

undo_tablespace UNDOTBS

2. Oracle Real Application Clusters

En un entorno de Oracle Real Application Clusters (RAC) varias instancias pueden acceder a una única base de datos. Estas instancias son normalmente creadas sobre servidores independientes que están conectados mediante interconexiones de alta velocidad con acceso a un área de disco compartido. Los ficheros de base de datos residen sobre un conjunto de discos compartidos, y cada instancia tiene sus propios archivos de control y archivos de registros de deshacer en línea que son también almacenados sobre los discos compartidos. Un usuario puede conectarse directamente a la base de datos mediante una de las instancias asociadas con la base de datos; si una de las instancias se cae, la conexión puede ser reconectada dinámicamente a la base de datos mediante otra instancia del clúster. RAC, por lo tanto, proporciona una solución de disponibilidad alta (considerando fallos en el servidor y la instancia) y una solución escalable de alto rendimiento (al aumentar la memoria y los procesadores disponibles en los procesos conectados). Veremos cómo instalar y gestionar un RAC. Muchos detalles son específicos del sistema operativo, pero el proceso de configuración es el mismo entre plataformas.

2.1. Pasos de preinstalación.

Antes de empezar la instalación de Oracle RAC necesitamos verificar que nuestro entorno y clúster están apropiadamente configurados para soportarlo. Los servidores que hospedarán las instancias se denominan los nodos del clúster. Cada nodo debería cumplir con lo siguiente:

• Cada uno de los nodos debería tener discos externos compartidos. • Cada nodo debería tener interruptores redundantes para soportar requerimientos de sobrefallos. • Cada nodo debería tener una dirección IP privada para conexiones privadas y una IP pública para conexiones de clientes y conexiones de sobrefallos para otros nodos. Oracle Database 11g proporciona una herramienta llamada VIPCA (Asistente de Configuración de IP Virtual) para soportar la configuración de nuestras direcciones IP. • Cada nodo debe soportar el protocolo TCP/IP y un protocolo de interconexión de software compatible.

Nota. Debemos elegir una interconexión que sea soportada por Oracle.

Page 161: Java y Oracle 11g

Oracle /161

Oracle proporciona «clusterware», simplificando el proceso de instalación y configuración. Los pasos de instalación son descritos en el siguiente capítulo. Para el área de disco compartido, Oracle recomienda el uso de su funcionalidad de Administración de Almacenamiento Automático.

2.2. Instalación de RAC.

La instalación de RAC en Oracle Database 11g es un proceso de dos pasos. En el primer paso se usa la Instalador Universal de Oracle (OUI) para instalar CRS (Cluster Ready Services). CRS, disponible como parte de Oracle Database 11g, proporciona un conjunto integrado de soluciones para administrar clústeres. Podemos usar CRS para administrar membresía de clúster, servicios de grupo, administración de recursos de grupo y configuración de alta disponibilidad. Podemos usar CRS para definir servicios que soporten distribución de cargas de trabajo a través de servidores. El Depósito de Carga de Trabajo Automático (AWR) recoge la estadística de los servicios.

Nota. RAC está disponible tanto con Standard Edition como con Enterprise Edition de Oracle Database 11g.

Después de completar el primer paso (instalar el CRS), podemos comenzar con el segundo paso de instalación: instalar el software de Oracle Database 11g con RAC en un directorio inicial de Oracle distinto del usado en el primer paso. Deberíamos usar también el instalador para crear y configurar servicios para nuestro entorno RAC. Después de que se complete la instalación, OUI lanzará el Asistente de Configuración de Base de datos para completar la configuración del entorno y crear la base de datos RAC.

Nota. Si tenemos instalada una versión previa de Oracle el Asistente de Actualización de Base de datos actualizará nuestra base de datos a la versión 11g de RAC.

2.2.1. Almacenamiento. Desde Oracle Database 10g, podemos usar el Administrador de Almacenamiento Automático (ASM) para crear grupos de discos y simplificar su mantenimiento. Los administradores de base de datos pueden añadir nuevos discos a grupos de discos; Oracle gestionará el contenido y la distribución de los ficheros entre los discos involucrados. Podemos usar ASM para proporcionar redundancia desde dentro de Oracle si no hay disponible redundancia por hardware. Cuando definimos el grupo de discos podemos especificar un grupo de fallos; Oracle usará el grupo de fallos como una versión redundante del grupo primario de discos en el grupo de discos. Los programas de instalación de Oracle se refieren a la redundancia basada en software como redundancia "normal". Podemos especificar varios grupos de fallo para protección de redundancia adicional (bajo el costo de espacio adicional). Si hay hardware de replicación podemos usar redundancia externa basada en el sistema de ficheros. Muchos entornos RAC usan una combinación de dispositivos físicos y volúmenes lógicos gestionados por software. Podemos usar grupos de discos ASM para intentar reducir la complejidad de gestión de la base de datos RAC (Oracle gestiona los ficheros y el sistema de ficheros). Podemos administrar los ficheros y grupos de discos manualmente o mediante la opción de control de rejilla del Administrador Empresarial de Oracle. 2.2.2. Parámetros de inicialización. Como parte de configuración de las instancias debemos asignar valores a los parámetros de inicialización de base de datos mantenidos en el fichero de parámetros del servidor. Las instalaciones RAC deberían usar el fichero de parámetros del servidor para controlar el conjunto de parámetros compartidos por las instancias. Los parámetros de inicialización mostrados en la siguiente tabla deben tener los mismos valores para todas las instancias del clúster RAC.

Parámetro Descripción

ACTIVE_INSTANCE_COUNT Permite designar una instancia en un clúster de dos instancias como la instancia primaria y la otra instancia como la secundaria. Este parámetro no tiene funcionalidad en un clúster con más de dos instancias. Cuando se asigna este parámetro a 1, la primera instancia comenzará siendo la primaria y aceptará las conexiones de clientes. La segunda instancia comenzará siendo la secundaria y puede aceptar conexiones de clientes sólo si la primera instancia falla.

Page 162: Java y Oracle 11g

Oracle /162

CLUSTER_DATABASE Valor booleano que especifica si RAC está habilitado. Hay que

asignarlo a TRUE.

CONTROL_FILES Especifica uno o más nombres de ficheros de control, separados por comas.

DB_BLOCK_SIZE Especifica (en bytes) el tamaño de los bloques de base de datos de Oracle.

DB_DOMAIN Especifica la localización lógica de la base de datos dentro de la estructura de red. El valor consiste de los componentes de extensión de un nombre global de base de datos, formado por identificadores válidos separados por puntos.

DB_FILES Especifica el número máximo de ficheros de base de datos que pueden ser abiertos por esta base de datos. El valor máximo válido es el número máximo de archivos, sujeto a las restricciones del sistema operativo, que pueden ser especificados por la base de datos, incluyendo ficheros que pueden ser añadidos por comandos. Si se incrementa este valor debemos parar y reiniciar todas las instancias que acceden a la base de datos antes de que el nuevo valor tenga efecto.

DB_NAME Especifica el identificador de la base de datos de hasta 8 caracteres. Este parámetro debe ser especificado y debe corresponder al

nombre especificado en el comando CREATE DATABASE.

DB_RECOVERY_FILE_DEST Especifica la localización por defecto del área de recuperación instantánea. Esta área contiene copias multiplexadas de los ficheros de control actuales y registros de deshacer en línea, así como los registros de instantáneas y copias de seguridad RMAN. Debe especificarse este parámetro junto con el parámetro de

inicialización DB_RECOVERY_FILE_DEST_SIZE.

DB_RECOVERY_FILE_DEST_SIZE Especifica (en bytes) el límite del espacio total usado por los ficheros de recuperación de base de datos creados en el área de recuperación instantánea.

DB_UNIQUE_NAME Especifica un nombre único global para la base de datos. Cada

DB_UNIQUE_NAME de base de datos debe ser único dentro de la empresa.

MAX_COMMIT_PROPAGATION_DELAY Este parámetro no debería ser cambia excepto un limitado conjunto de circunstancias específicas al clúster de base de datos. Este parámetro especifica la cantidad máxima de tiempo permitido antes de que número de cambio de sistema (SCN) sostenido en el SGA de una instancia sea refrescado por el proceso de escritura de registro. Determina si el SCN local debería ser refrescado desde el valor del bloqueo cuando obtenemos la instantánea SCN de una consulta. Las unidades están en centésimas de segundo. Asignando este parámetro a cero provocamos que el SCN sea refrescado

inmediatamente después de un COMMIT. El valor por defecto (700) es un límite superior que permite al mecanismo de alto rendimiento permanecer.

SPFILE Nombres del fichero de parámetro de servidor en uso. Este parámetro puede ser definido en un fichero de parámetros del lado cliente para indicar el nombre del fichero de parámetros de servidor usado. Cuando se usa el fichero por defecto del servidor, el valor es asignado internamente por el servidor.

TRACE_ENABLED Valor booleano que controla el trazado de Oracle. Cuando se

asigna a TRUE, Oracle registra información en ficheros de registro cuando ocurren errores. Oracle registra esta información para todas las instancias.

UNDO_MANAGEMENT Especifica qué modo de gestión de espacio de deshacer debería

usar el sistema. Cuando se asigna a AUTO, la instancia comienza en modo automático.

Page 163: Java y Oracle 11g

Oracle /163

Nota. La asignación para DML_LOCKS debe ser idéntica para todas las instancias sólo si se asignar a cero.

La siguiente tabla lista los parámetros de inicialización que deben tener valores únicos para cada instancia en el clúster RAC, junto con sus descripciones.

Parámetro Descripción

INSTANCE_NUMBER Especifica un único número que mapea las instancias en una lista de grupos libre para cada objeto de base de datos creados con el parámetro de almacenamiento

FREELIST GROUPS.

ROLLBACK_SEGMENTS Asigna uno o más segmentos de rollback por nombre a esta instancia. Cuando

UNDO_MANAGEMENT es asignado a AUTO, ROLLBACK_SEGMENTS se ignora.

THREAD Si se especifica, este parámetro deber tener valores únicos sobre todas las instancias. El parámetro especifica el número de hilos de deshacer que serán usados por una instancia.

UNDO_TABLESPACE Especifica el tablespace de deshacer que será usado cuando una instancia arranca. Si este parámetro se especifica cuando la instancia está en un modo manual de administración de deshacer, entonces ocurrirá un error y el arranque fallará.

Nota. Dentro de una instancia, se asigna INSTANCE_NUMBER y THREAD al mismo valor.

Además de los parámetros mostrados previamente, debemos especificar valores para los siguientes parámetros de inicialización:

Parámetro Descripción

CLUSTER_DATABASE_INSTANCES Asigna el número de instancias del entorno RAC.

DISPATCHERS Se asigna este parámetro para habilitar una configuración de servidor compartida (en el cual varios procesos de usuario se unen a un distribuidor).

2.3. Inicia y parar instancias RAC.

Podemos usar Control de Rejilla para administrar todas las instancias asociadas con una base de datos RAC. Podemos parar, iniciar y supervisar bases de datos, instancias y sus oyentes mediante Control de Rejilla. Al igual que con bases de datos que no son RAC, podemos usar SQL*Plus para introducir comandos de apagados y arranque. Para bases de datos RAC podemos también usar la utilidad SRVCTL. Independientemente de la herramienta usada para apagar o arrancar la instancia, las siguientes reglas se aplican a clústeres RAC:

• El apagado de una instancia no afecta a las operaciones de otras instancias ejecutándose. • Apagar una base de datos montada en modo compartido requiere apagar cada una de las instancias del clúster RAC. • Después de un apagado normal (SHUTDOWN NORMAL) o inmediato (SHUTDOWN IMMEDIATE), no es necesario recuperar ninguna instancia. Si se usa SHUTDOWN ABORT sobre una instancia RAC, una instancia todavía ejecutándose realiza la recuperación de instancia para la instancia parada. Si la instancia abortada fue la última que se ejecutaba, la siguiente instancia que se inicie realizará la recuperación. • Si se usa SHUTDOWN TRANSACTIONAL con la opción LOCAL, todas las transacciones en la instancia serán confirmadas o rechazadas antes del apagado. Las transacciones de otras instancias no se verán afectadas. Si no se especifica la cláusula LOCAL, el apagado transaccional esperará por todas las transacciones sobre todas las instancias que se inicien antes de que el comando SHUTDOWN se complete.

Podemos usar comandos de SQL*Plus para apagar y arrancar bases de datos RAC una a una hasta que todo el entorno haya comenzado. Podemos arrancar una instancia sobre nuestro nodo local mediante el comando STARTUP MOUNT. Podemos usar la utilidad SRVCTL para administrar varias instancias con un único comando. La sintaxis para arrancar instancias es: SRVCTL START INSTANCE -d <db_name> -i <inst_name_list> [-o <start_options>] [-c <connect_str> | -q]

Las palabras SRVCTL START especifican el servicio que será iniciado (INSTANCE, DATABASE, SERVICE, NODEAPPS o ASM). Se pueden pasar varias instancias como parte del valor del parámetro inst_name_list (separándolas con comas). Los valores para start_options son OPEN, MOUNT y NOMOUNT. El parámetro connect_str permite pasar una cadena de conexión cuando conectamos la base de datos. El parámetro q, si se

Page 164: Java y Oracle 11g

Oracle /164

especifica, le dice a SRVCTL que solicite al usuario información de conexión cuando SRVCTL se ejecute; por defecto SRVCTL se conectará a la base de datos local como SYSDBA. El siguiente comando iniciará y abrirá una base de datos local llamada MYDB: SRVCTL START –database –d MYDB –o OPEN

El siguiente comando iniciará dos instancias para la base de datos MYDB: SRVCTL START instance –d MYDB –i MYDB1,MYDB2

Para instancias ASM hay que especificar el nombre de nodo (-n), el nombre de instancia (-i), y la opción de inicio (-o), como en el siguiente ejemplo: SRVCTL START ASM -n MYNODE1 -i ASM1 –o OPEN

Nota. Para ver las instancias que son parte de nuestro clúster, podemos usar OEM o SQL*Plus. Dentro de SQL*Plus, se consulta la vista V$ACTIVE_INSTANCES. Esta vista mostrará una fila por cada instancia, mostrando el número de instancia, el nombre de host, y el nombre de la instancia.

Podemos usar el siguiente comando SRVCTL STOP {DATABASE | INSTANCE | SERVICE | NODEAPPS | ASM }

Para parar bases de datos, instancias y otros servicios.

Comando SRVCTL Descripción

srvctl add {database | instance | service |

nodeapps | asm}

Añade una configuración a nuestra configuración del clúster de

base de datos. SRVCTL ADD SERVICE añade servicios a una base de datos y los asigna a instancias. Podemos usar el comando

SRVCTL ADD SERVICE para configurar la política "Transparent Application Failover" para un servicio.

srvctl config {database | service |

nodeapps|asm}

Muestra la información de configuración almacenada en el Registro de Clúster de Oracle.

srvctl disable {database | instance |

service|asm}

Desactiva el objeto indicado y evita que sea iniciado automáticamente.

srvctl enable {database | instance |

service|asm}

Activa el objeto indicado para ser iniciado automáticamente.

srvctl getenv {database | instance |

service | nodeapps}

Muestra el estado de entorno.

srvctl modify {database | instance |

service | nodeapps}

Modifica la configuración del objeto indicado sin quitar y añadir los recursos CRS.

srvctl relocate service Traslada los nombres de servicio desde una instancia a otra.

srvctl remove {database | instance |

service | nodeapps | asm}

Elimina el objeto indicado del entorno del clúster.

srvctl setenv {database | instance |

service | nodeapps}

Asigna valores de entorno en el fichero de configuración.

srvctl start {database | instance | service

| nodeapps | asm}

Inicia el CRS habilitado, aplicaciones no ejecutándose en la base de datos, todas o las instancia indicadas, todos o los servicios indicados, o aplicaciones del nivel de nodo.

srvctl status {database | instance |

service | nodeapps | asm}

Muestra el estado actual del objeto indicado.

srvctl stop {database | instance | service |

nodeapps | asm}

Para las aplicaciones CRS para la base de datos, todas o las instancias indicadas, todos o los servicios indicados, o aplicaciones del nivel de nodo.

srvctl unsetenv {database | instance |

service | nodeapps}

Desasigna los valores de entorno en el fichero de configuración.

2.4. Transparencia de sobrefallos de aplicación.

RAC proporciona soporte transparente para sobrefallos de aplicación (TAF) en el evento de fallo de una instancia. Para protegerse contra el fallo de hardware, el fichero de base de datos almacenado debe ser tolerante a fallos (por lo general mediante el uso de cadenas RAIS, replicación, o replicación por software forzando ASM). Cuando un usuario se conecta a la base de datos a través de una instancia, cualquier transacción ejecutada por el usuario se registra en el hilo de registro de deshacer para esta instancia. Si una instancia falla (o se apaga mediante un SHUTDOWN ABORT), una de las instancias supervivientes realizará las recuperaciones necesarias. Con TAF, las conexiones de usuario serán restablecidas mediante otra instancia del mismo clúster, conectado a la misma base de datos.

Page 165: Java y Oracle 11g

Oracle /165

Cuando instalamos y configuramos un entorno RAC, Oracle configura los servicios de red por nosotros. Por ejemplo, si creamos un servicio llamado db.us.acme.com, la entrada de nombre de servicio en el fichero tnsnames.ora puede ser reensamblado como sigue: db.us.acme.com=

(description= (load_balance=on) (failover=on)

(address_list=

(address=(protocol=tcp)(host=db1-server)(port=1521))

(address=(protocol=tcp)(host=db2-server)(port=1521)))

(connect_data= (service_name=db.us.acme.com)))

El usuario puede conectarse a la instancia mediante el nombre de servicio db.us.acme.com (por ejemplo, mediante una sesión remota de SQL*Plus) y ejecutar consultas. Mientras el usuario está conectado, podemos apagar el host db1-server. Si el usuario (todavía en la misma sesión) intenta ejecutar otra consulta, la sesión será transparentemente restablecida mediante el host db2-server y la consulta será ejecutada. La entrada del fichero listener.ora para la base de datos contendrá entradas similares a las siguientes: listeners_db.us.acme.com=

(address=(protocol=tcp)(host=db1-server)(port=1521)) (address=(protocol=tcp)(host=db2-server)(port=1521))

Si el usuario ha cambiado variables del entorno dentro de la sesión (mediante ALTER SESSION), o ha asignado variables específicas de la sesión, estos valores pueden necesitar ser restablecidos en la nueva sesión. 2.4.1. Servicios. Podemos crear y administrar servicios dentro de nuestro clúster RAC. Podemos asignar servicios para ejecutar una o más instancias; los servicios integran recursos del clúster de base de datos para simplificar la gestión de clúster. Podemos usar SVRCTL para crear y añadir servicios. Podemos identificar otras instancias que estén disponibles para soportar cambios de niveles de servicios o para interrupciones planificadas.

2.5. Añadir nodos e instancias a un clúster.

Podemos añadir nodos a un clúster existente y podemos añadir instancias a un entorno RAC existente. Los nodos deben cumplir con los criterios listados previamente (deben ser configurados con el protocolo de red apropiado y deben soportar transmisiones de datos de alta velocidad con los otros nodos del clúster). Generalmente lo más simple es realizar una copia de un nodo existente en el clúster. Los pasos de instalación específicos para el nuevo nodo varían según el sistema operativo. En general, un administrador de clúster en UNIX añadirá el nuevo nodo a la configuración de «clusterware» (según las direcciones de su fabricador). Podemos entonces iniciar el Instalador Universal de Oracle (OUI) en el modo añadir nodo (en UNIX, mediante <CRS_HOME>/OUI/bin/addNode.sh y en Windows mediante <CRS_HOME>\oui\bin\addNode.bat). OUI entonces presentará una serie de pantallas de recogida de datos para añadir el nuevo nodo al clúster así como para verificar la configuración. OUI entonces iniciará el proceso de instalación, instanciando scripts si es necesario, copiando los ficheros CRS al nuevo nodo si es necesario, y solicitando que ejecutemos varios scripts al nivel del sistema operativo. OUI entonces guardará el inventario del clúster. Podemos entonces ejecutar la utilidad CRSSETUP para añadir información CRS al nuevo nodo. Entonces podemos ejecutar una utilidad final, RACGONS, para configurar el puerto de los Servicios de Notificación de Oracle para el nuevo servidor. Una vez que el nodo ha sido añadido al clúster, podemos añadir una instancia. Se inicia el Asistente de Configuración de Base de datos (DBCA) y se elige "Real Application Clusters database". Podemos entonces seleccionar "Añadir instancia" y el nombre de la instancia de base de datos que será accedida. Tras introducir los parámetros de la instancia y aceptar la información resumida se inicia la creación de la instancia. Oracle creará la instancia y sus servicios de red. Para eliminar una instancia existente de un clúster RAC, se inicia el DBCA sobre un nodo distinto del noto que hospeda la instancia que será eliminada. El DBCA mostrará una lista de instancias actuales en la configuración; se selecciona una de la lista. DBCA mostrará una lista de los servicios asignados a esta instancia para poderlos recolocar en otras instancias del clúster. La instancia seleccionada será des registrada del clúster RAC.

2.6. Administración de registro y servicios del clúster.

El Registro de Clúster de Oracle (OCR) contiene detalles de configuración para la base de datos, servicios y otros componentes del clúster. Como se ha visto podemos usar comando de configuración SRVCTL para

Page 166: Java y Oracle 11g

Oracle /166

mostear los detalles de configuración almacenados en el OCR. Deberíamos hacer copias del registro regularmente para soportar cualquier recuperación que sea necesaria más tarde. Una de las instancias registradas en el clúster automáticamente recuperará el OCR cada cuatro horas, reteniendo tres copias de respaldo. Copias de respaldo adicionales ser realizan una vez por día y una vez por semana. No podemos modificar la frecuencia de las copias o el número de copias retenidas. Podemos realizar copias de respaldo adicionales mediante la herramienta OCRCONFIG. Las opciones de OCRCONFIG se muestran en la siguiente tabla.

Opción Propósito

-export Para exportar el contenido del OCR dentro del fichero destino.

-import Para importar el contenido OCR desde un fichero exportado previamente.

-restore Para restaurar el OCR desde un fichero de respaldo OCR creado automáticamente.

-backuploc Para cambiar la localización del fichero de respaldo OCR. Para esta entrada hay que usar una ruta completa que sea accesible por todos los nodos.

-showbackup Para mostrar la localización, tiempo y nombre del nodo de origen para los tres últimos ficheros de respaldo creados automáticamente.

-upgrade Para actualizar el OCR a su última versión.

-downgrade Para des actualizar el OCR a su versión más temprana.

-help Para mostrar las ayudas de este comando.

3. Seguridad en Oracle

A la hora de establecer una conexión con un servidor Oracle, es necesario que utilicemos un modo de acceso, el cual describa de qué permisos dispondremos durante nuestra conexión. Estos permisos se definen sobre un nombre de usuario. Un usuario no es más que un conjunto de permisos que se aplican a una conexión de base de datos. Así mismo, el usuario también tiene otras funciones:

- Ser el propietario de ciertos objetos. - Definir el tablespace por defecto para sus objetos. - Realizar copias de seguridad. - Aplicar cuotas de almacenamiento.

Un privilegio no es más que un permiso dado a un usuario para que realice cierta operación. Estas operaciones pueden ser de dos tipos:

- Operación de sistema: necesita el permiso de sistema correspondiente. - Operación sobre objeto: necesita el permiso sobre el objeto en cuestión.

Y por último, un rol de base de datos no es más que una agrupación de permisos de sistema y de objeto.

3.1. Creación de usuarios.

El sistema de Oracle viene con muchos usuarios ya creados incluyendo a SYSTEM y SYS. El usuario SYS es el propietario de las tablas internas que Oracle usa para gestionar las bases de datos; el usuario SYSTEM es propietario de tablas y vistas adicionales. Para crear nuevos usuarios deberemos registrarnos en el sistema como SYSTEM, porque este usuario tiene este privilegio. Cuando instalamos Oracle, lo habitual es entrar como el usuario SYSTEM y crear un usuario para nosotros o para el administrador del sistema. La forma más simple de crear un usuario es usar el comando CREATE USER, cuya sintaxis básica es la siguiente: CREATE USER nombre_usuario

IDENTIFIED [ BY contraseña | EXTERNALLY ]

DEFAULT TABLESPACE tablespace_por_defecto

TEMPORARY TABLESPACE tablespace_temporal

DEFAULT ROLE [ roles, ALL [EXCEPT roles], NONE ]

QUOTA cantidad {K | M} ON tablespace

QUOTA UNLIMITED ON tablespace

PROFILE nombre_de_perfil

PASSWORD EXPIRE

ACCOUNT {LOCK|UNLOCK};

Page 167: Java y Oracle 11g

Oracle /167

Si no asignamos una cuota el usuario no podrá crear objetos. Si no asignamos un pefil al usuario se la aplicará el perfil por defecto. Si queremos conectarnos a Oracle usando el identificador de usuario y contraseña que usamos al conectarnos al sistema operativo del ordenador, podemos usar la palabra clave EXTERNALLY en vez de una contraseña en la cláusula IDENTIFIED del comando CREATE USER. Un Administrador de sistema (el cual tiene los máximos privilegios) puede querer tener la seguridad extra de tener una contraseña propia. Vamos a llamar al administrador de sistema Pedro en este ejemplo: CREATE USER Pedro IDENTIFIED BY profesor;

Ahora existe la cuenta de Pedro y está asegurada mediante una contraseña. Podemos establecer que la cuenta de usuario se asocie con un tablespace específico, y que esté limitada su cuota de espacio en disco y su uso de recursos. La cláusula DEFAULT TABLESPACE permite establecer el tablespace por defecto para la creación de objetos del usuario. Si se omite se utilizará el tablespace SYSTEM. La cláusula TEMPORARY TABLESPACE indica el tablespace que se utilizará para la creación de objetos temporales en las operaciones internas de Oracle. Si se omite se utilizará el tablespace TEMP. La cláusula DEFAULT ROLE permite asignar roles de permisos durante la creación del usuario.

3.2. Eliminación de usuarios.

La eliminación de usuarios se hace a través de la instrucción DROP USER. Su sintaxis es: DROP USER usuario [CASCADE];

La cláusula CASCADE permite borrar el usuario y todos los objetos que posea.

3.3. Gestión de contraseñas.

Si queremos cambiar la contraseña del usuario podemos usar el comando ALTER USER, tal como sigue: ALTER USER Pedro IDENTIFIED BY otracontraseña;

Ahora Pedro tiene la contraseña "otracontraseña" en vez de "profesor". Las contraseñas pueden expirar, y las cuentas pueden ser bloqueadas ante repetitivos intentos fallidos de conectarnos. Cuando cambiamos nuestra contraseña, puede mantenerse un histórico de contraseñas para prevenir que puedan reutilizarse. Las características de expiración de las contraseñas son determinadas por el perfil asignado a la cuenta de usuario. Los perfiles, los cuales se crean con el comando CREATE PROFILE, son administrados por el DBA (administrador de la base de datos). Los perfiles pueden forzar los siguientes aspectos sobre las contraseñas y cuentas de usuario:

• El tiempo de vida de nuestra contraseña, lo que determina la frecuencia en la que debe cambiarse la contraseña. • El periodo de gracia, después de fecha de expiración de la contraseña, durante el cual podemos cambiarla. • El número consecutivos de intentos fallidos de conexión antes de que la cuenta sea automáticamente bloqueada. • El número de días que la cuenta permanecerá bloqueada. • El número de días que deben pasar antes de que podamos reutilizar una contraseña. • El número de cambios de contraseñas que podemos hacer ante de reutilizar una contraseña antigua.

Funcionalidades adicionales de administración de contraseñas permiten que se haga cumplir con una longitud mínima y complejidad en las contraseñas. Además del comando ALTER USER, podemos usar el comando PASSWORD en SQL*Plus para cambiar nuestra contraseña. Si usamos el comando PASSWORD, nuestra contraseña no será mostrada en la consola cuando la escribamos. Los administradores de base de datos pueden cambiar cualquier contraseña de usuario mediante el comando PASSWORD; otros usuarios pueden cambiar sólo su propia contraseña. Cuando introducimos el comando PASSWORD, se nos solicita la antigua y nueva contraseña, tal como se muestra a continuación: CONNECT Pedro/otracontraseña

PASSWORD

Changing password for Pedro

Old password: otracontraseña

New password: nuevacontraseña

Retype new password: nuevacontraseña

Cuando la contraseña haya sido cambiada recibiremos el siguiente mensaje:

Page 168: Java y Oracle 11g

Oracle /168

Password changed

Podemos asignar otra contraseña a un usuario desde una cuenta DBA. Simplemente se añade el nombre de usuario al comando PASSWORD. En este caso no se nos solicitará la antigua contraseña: PASSWORD Juan

Changing password for Juan

New password:

Retype new password:

3.3.1. ¿Cómo forzar la expiración de la contraseña? Podemos usar perfiles para gestionar la expiración, reutilización y complejidad de las contraseñas. Podemos limitar el tiempo de vida de una contraseña, y bloquear una cuenta si la contraseña es demasiado vieja. También podemos forzar que una contraseña sea moderadamente compleja, y bloquear una cuenta sobre la cual se han intentado varios accesos de registro fallidos. Por ejemplo, si asignamos el recurso FAILED_LOGIN_ATTEMPTS de un perfil de usuario a 5, entonces cuatro fallos consecutivos de registro serán permitidos, pero al quinto intento la cuenta será bloqueada.

Nota. Si se proporciona la contraseña correcta al quinto intento, el recurso FAILED LOGIN ATTEMPT se reinicializa a cero, permitiendo así cinco intentos más consecutivos antes de que la cuenta se bloquee.

En el siguiente listado, se crea el perfil PERFIL_LIMITADOR para el usuario Juana: CREATE PROFILE PERFIL_LIMITADOR LIMIT FAILED_LOGIN_ATTEMPTS 5;

CREATE USER Juana IDENTIFIED BY sucontraseña PROFILE PERFIL_LIMITADOR;

GRANT CREATE SESSION TO Juana;

Si se realizan cinco intentos consecutivos fallidos de conexión en la cuenta de Juana, la cuenta será automáticamente bloqueada por Oracle. Si en el sexto intento se usa la contraseña correcta para la cuenta de Juana, recibiremos un error. Para desbloquear la cuenta se usa la cláusula ACCOUNT UNLOCK del comando ALTER USER (desde una cuenta DBA), tal como se muestra a continuación: ALTER USER Juana ACCOUNT UNLOCK;

También podemos bloquear manualmente una cuenta mediante la cláusula ACCOUNT LOCK del comando ALTER USER. ALTER USER Juana ACCOUNT LOCK;

Nota. Podemos especificar ACCOUNT LOCK como parte del comando CREATE USER.

Si una cuenta se ha cerrado debido a repetidos fracasos de conexión, automáticamente será desbloqueada cuando se exceda al valor de tiempo especificado en el perfil PASSWORD_LOCK_TIME. Por ejemplo, si PASSWORD_LOCK_TIME está asignado a 1, la cuenta Juana del ejemplo previo será bloqueada por un día, después de lo cual la cuenta se desbloqueará. Podemos establecer el tiempo de vida máximo de una contraseña mediante el recurso PASSWORD_LIFE_TIME. Por ejemplo, podemos forzar a los usuarios del perfil PERFIL_LIMITADOR a cambiar su contraseña cada 30 días de la siguiente forma: ALTER PROFILE PERFIL_LIMITADOR LIMIT PASSWORD_LIFE_TIME 30;

En este ejemplo, se usa el comando ALTER PROFILE para modificar el perfil PERFIL_LIMITADOR. El valor de PASSWORD_LIFE_TIME es asignado a 30, de forma que cada cuenta que use este perfil forzará al usuario a cambiar la contraseña cada 30 días. Podemos también especificar un periodo de gracia para cuando expire una contraseña, mediante el parámetro PASSWORD_GRACE_TIME. Si la contraseña no es cambiada dentro del periodo de gracia, la cuenta expirará.

Nota. Si se usa el parámetro PASSWORD_LIFE_TIME, deberemos proporcionar a los usuarios una forma sencilla de cambiar sus contraseñas.

Una cuenta "expirada" es diferente de una cuenta "bloqueada". Una cuenta bloqueada puede ser desbloqueada automáticamente con el paso del tiempo. Una cuenta expirada, sin embargo, requiere la intervención manual del DBA para ser rehabilitada. Para rehabilitar una cuenta expirada se ejecuta el comando ALTER USER. En el siguiente ejemplo, el DBA expira manualmente la contraseña de Juana: ALTER USER Juana PASSWORD EXPIRE;

Usuario modificado.

Page 169: Java y Oracle 11g

Oracle /169

A continuación, Juana intenta conectarse a su cuenta. Cuando ella proporciona su contraseña inmediatamente se la solicitará una nueva contraseña para la cuenta: CONNECT Juana/contraseña

ERROR:

ORA-28001: la contraseña ha vencido

Cambiando la contraseña para Juana

Contraseña Nueva:

Confirmar Contraseña:

Contraseña cambiada

Conectado.

También podemos forzar que los usuarios cambien su contraseña cuando accedan por primera vez a sus cuentas, a través de la cláusula PASSWORD EXPIRE del comando CREATE USER. El comando CREATE USER no permite, sin embargo, asignar una fecha de expiración para la nueva contraseña asignada por el usuario; para hacer esto, debemos usar el parámetro de perfil PASSWORD_LIFE_TIME, tal como se ha visto en los ejemplos previos. Podemos consultar la columna Expiry_Date de la tabla DBA_USERS del diccionario de datos para ver las fechas de expiración de cualquier cuenta. Los usuarios que quieran ver la fecha de expiración de sus cuentas pueden consultar la columna Expiry_Date de la vista USER_USERS del diccionario de datos. 3.3.2. ¿Cómo forzar limitaciones al reutilizar contraseñas? Para evitar que una contraseña pueda ser reutilizada, podemos usar uno de los dos siguientes parámetros de perfil: PASSWORD_REUSE_MAX o PASSWORD_REUSE_TIME. Estos dos parámetros son mutuamente exclusivos; si se asigna el valor de uno de ellos, el otro debe estar asignado a UNLIMITED. El parámetro PASSWORD_REUSE_TIME especifica el número de días que deben pasar antes de que una contraseña pueda ser reutilizada. Por ejemplo, si asignamos PASSWORD_REUSE_TIME a 60, no podremos reutilizar la misma contraseña antes de 60 días. El parámetro PASSWORD_REUSE_MAX especifica el número de cambios de contraseña que deben ocurrir antes de que una contraseña pueda ser reutilizada. Si intentamos reutilizar la contraseña antes del límite, Oracle rechazará el cambio de contraseña. Por ejemplo, podemos asignar PASSWORD_REUSE_MAX para el perfil PERFIL_LIMITADOR creado previamente: ALTER PROFILE PERFIL_LIMITADOR LIMIT

PASSWORD_REUSE_MAX 3

PASSWORD_REUSE_TIME UNLIMITED;

Si el usuario Juana intenta ahora reutilizar una contraseña reciente no podrá. Por ejemplo, supongamos que ella cambia su contraseña, como en la siguiente línea: ALTER USER Juana IDENTIFIED BY contraseña1;

Y ella la cambia otra vez: ALTER USER Juana IDENTIFIED BY contraseña2;

Durante su siguiente cambio de contraseña, ella intenta reutilizar una contraseña reciente, y su intento falla: ALTER USER Juana IDENTIFIED BY contraseña1;

ERROR en línea 1:

ORA-28007: la contraseña no puede ser reutilizada

3.4. Perfiles de usuario.

Los perfiles se utilizan para limitar la cantidad de recursos del sistema y las bases de datos disponibles para un usuario. Si no se definen perfiles para un usuario se utiliza el perfil por defecto, que especifica recursos ilimitados. Los recursos que pueden ser limitados mediante perfiles son los siguientes:

Recurso Descripción

SESSIONES_PER_USER El número de sesiones concurrentes que un usuario puede tener en una instancia.

CPU_PER_SESSION El tiempo de CPU, en centenas de segundos, que una sesión puede utilizar.

CONNECT_TIME El número de minutos que una sesión puede permanecer activa.

Page 170: Java y Oracle 11g

Oracle /170

IDLE_TIME El número de minutos que una sesión puede permanecer sin que sea utilizada de manera activa.

LOGICAL_READS_PER_SESSION El número de bloques de datos que se pueden leer en una sesión.

LOGICAL_READS_PER_CALL El número de bloques de datos que se pueden leer en una operación.

PRIVATE_SGA La cantidad de espacio privado que una sesión puede reservar en la zona de SQL compartido de la SGA.

COMPOSITE_LIMIT El número de total de recursos por sesión, en unidades de servicio. Esto

resulta de un cálculo ponderado de CPU_PER_SESSION,

CONNECT_TIME, LOGICAL_READS_PER_SESSION y PRIVATE_SGA,

cuyos pesos se pueden variar con el comando ALTER RESOURCE COST.

Los perfiles se pueden crear mediante el comando CREATE PROFILE, y se pueden modificar con la sentencia ALTER PROFILE. En general, el perfil por defecto debe ser adecuado para los usuarios normales; los usuarios con requerimientos especiales deberían tener perfiles especiales.

3.5. Cuentas de base de datos sobre cuentas del sistema operativo.

Los usuarios pueden entrar en la base de datos una vez que han dado un nombre de usuario y una contraseña. Sin embargo, es posible aprovecharse del Sistema Operativo para obtener un nivel adicional de autentificación. La cuenta de la base de datos (BD) y del Sistema Operativo (SO) pueden relacionarse de manera que la de la BD se diferencie sólo en un prefijo de la cuenta del SO. Este prefijo se fija en el parámetro OS_AUTHENT_PREFIX del fichero init.ora. Por defecto está puesto a "OPS$", pero puede tomar cualquier forma, incluso ser la cadena nula, "", con lo que no existe prefijo que diferencie las cuentas en la BD y en el SO. Se puede mostra el valor actual de este parámetro con la orden: SHOW PARAMETER OS_AUTHENT_PREFIX;

Por ejemplo, consideremos una cuenta del SO llamada alu10. La correspondiente cuenta en la BD sería OPS$ALU10. Cuando el usuario alu10 está en activo en el SO, puede acceder a su cuenta OPS$ALU10 sin especificar la contraseña, como se muestra a continuación: CONNECT /

En este caso el carácter "/" toma el lugar de la combinación del nombre de usuario y de la contraseña que debería aparecer. Las cuentas pueden ser creadas con contraseñas aunque no vaya a ser utilizada. Ya que de este modo será posible acceder a la cuenta de OPS$ALU10 desde otra cuenta diferente a la alu10 del SO. La sentencia de creación de la cuenta OPS$ALU10 puede ser la siguiente: CREATE USER ops$alu10

IDENTIFIED BY 01ula

DEFAULT TABLESPACE USERS

TEMPORARY TABLESPACE TEMP;

Así, la forma de conectarse como OPS$ALU10 desde una cuenta del SO diferente es: CONNECT ops$alu10/01ula

Existen dos formas de evitar este tipo de conexión. La primera es creando el usuario BD sin especificar una contraseña, utilizando la cláusula IDENTIFIED EXTERNALLY. De esta manera evitamos la necesidad de especificar una contraseña para la cuenta, obligando a la conexión entre las cuentas de la BD y del SO: CREATE USER ops$alu10

IDENTIFIED EXTERNALLY;

La otra manera de evitar este tipo de conexión es crear una cuenta con una contraseña imposible. Se puede crear una contraseña no válida indicando un valor encriptado de menos de 16 caracteres: CREATE USER usuario IDENTIFIED BY VALUES '123AB456C'

3.6. Usuarios globales.

«Oracle Advanced Security» permite centralizar la gestión de la información relacionada con el usuario, incluidas las autorizaciones, en un servicio de directorio basado en LDAP. Los usuarios pueden ser identificados en la base de datos como usuarios globales, lo que significa que son autentificados por SSL y que la gestión de estos usuarios se realiza fuera de la base de datos por el servicio de directorio centralizado. Se definen roles globales en una base de datos y son conocidos sólo por la base de datos, pero la autorización para dichos roles la realiza el servicio de directorio.

Page 171: Java y Oracle 11g

Oracle /171

Esta gestión centralizada permite la creación de usuarios de empresa y roles de empresa. Los usuarios empresariales son definidos y gestionados en el directorio. Estos usuarios tienen identidades únicas en toda la empresa y se pueden asignar roles empresariales que determinan sus privilegios de acceso a través de múltiples bases de datos. Un rol de empresa se compone de uno o más roles globales, y pueden ser considerados como un contenedor para roles globales. 3.6.1. Creación de un usuario que es autorizado por el servicio de directorio. Tenemos dos opciones para especificar los usuarios que están autorizados por un servicio de directorio. Esquemas privados: La siguiente declaración ilustra la creación de un usuario global con un esquema privado, autentificado por SSL, y autorizado por el servicio de directorio de la empresa: CREATE USER scott CREATE USER scott

IDENTIFIED GLOBALLY AS 'CN=scott,OU=division1,O=oracle,C=US';

La cadena proporcionada en la cláusula AS proporciona un identificador (nombre completo o DN) significativo para el directorio de la empresa. En este caso, scott es un usuario global. Sin embargo, la desventaja aquí es que el usuario scott entonces se debe crear en cada base de datos que deba acceder, además de en el directorio. Esquemas compartidos: Varios usuarios de la empresa pueden compartir un solo esquema en la base de datos. Estos usuarios están autorizados por el servicio de directorio de la empresa pero no poseen esquemas propios en la base de datos. Estos usuarios no son creados individualmente en la base de datos, se conectan a un esquema común en la base de datos. El proceso de creación de un usuario independiente del esquema es el siguiente:

1) Crear un esquema compartido en la base de datos de la siguiente manera. CREATE USER appschema INDENTIFIED GLOBALLY AS '';

2) En el directorio, ahora crear varios usuarios de la empresa, y un objeto de mapeo. El objeto de mapeo le dice a la base de datos cómo asignar los DN de los usuarios al esquema compartido. Podemos hacer un mapeado completo de DN (una entrada de directorio por cada DN único), o podemos mapear, por ejemplo, cada usuario que contiene los siguientes componentes DN al appschema:

OU=division,O=Oracle,C=US

La mayoría de los usuarios no necesitan sus propios esquemas y la implementación de usuarios independientes de esquema divorcia al usuario de las bases de datos. Podemos crear varios usuarios que compartan el mismo esquema en una base de datos, y como usuarios de la empresa, pueden acceder también a esquemas compartidos en otras bases de datos. 3.6.2. Ventajas de autentificación y autorización global. Algunas de las ventajas de la autentificación y autorización de usuarios global son las siguientes:

• Proporciona autenticación segura usando SSL, Kerberos, o la autentificación nativa de Windows NT. • Permite la gestión centralizada de usuarios y permisos a través de toda la empresa. • Es fácil de administrar. No tenemos que crear un esquema para cada usuario en cada base de datos de la empresa. • Facilita el inicio de sesión único: los usuarios sólo tienen que firmar una vez para acceder a varias bases de datos y servicios. Además, los usuarios que utilizan contraseñas pueden tener una sola contraseña para acceder a varias bases de datos. • Dado que la autentificación y autorización de usuarios globales proporciona acceso basado en contraseña, los usuarios basados en contraseña previamente definidos en la base de datos pueden migrar al directorio (usando la utilidad de migración de usuario) para ser administrados centralmente. Esto hace que la autentificación y autorización globales esté disponibles para las versiones previas de Oracle que sean compatibles. • Los enlaces de bases de datos CURRENT_USER conectan como un usuario global. Un usuario local puede conectarse como un usuario global en el contexto de un procedimiento almacenado, es decir, sin guardar la contraseña de usuario global en la definición del enlace.

3.7. Usuarios con permisos especiales: SYSOPER y SYSDBA.

Cuando nos conectamos a Oracle, podemos diferenciar entre dos grupos de usuarios: • Grupo 1: Usuarios que sólo se pueden conectar a la base de datos cuando está abierta. • Grupo 2: Usuarios que se pueden conectar a la base de datos tanto cuando esté abierta como cuando esté

Page 172: Java y Oracle 11g

Oracle /172

cerrada. Los usuarios del Grupo 2 son aquellos que tiene el privilegio SYSOPER y/o el SYSDBA. Estos dos privilegios caracterizan a aquellos usuarios de Oracle que pueden hacer operaciones de "seguridad", como el arranque y parada de la base de datos. Para poder conectarnos con estos privilegios (SYSDBA y SYSOPER) tenemos que conectarnos con nuestras credenciales indicando que queremos tener estos privilegios. Por ejemplo, si somos el usuario SYS y queremos conectarnos con permisos SYSDBA usaríamos: CONNECT SYS/contraseña AS SYSDBA

Análogamente, para conectarnos con permisos SYOPER, podemos usar: CONNECT SYS/contraseña AS SYSOPER

En Oracle existe un fichero de contraseñas donde se limita cual es el número de usuarios que se pueden crear en la base de datos con estos privilegios. Este fichero de Oracle contiene las contraseñas de los usuarios que tienen los privilegios de SYSDBA y/o SYSOPER. Este archivo se crea cuando creamos la base de datos, sin embargo también puede ser recreado mediante la utilidad orapwd. Para utilizar este comando y recrear el archivo de contraseñas tenemos que tener en cuenta que el ejecutable orapwd se encuentra en $ORACLE_HOME/bin/orapwd. Por ejemplo, si queremos establecer el número máximo en 12 usuarios: $orapwd file=$ORACLE_HOME/dbs/orapwdorasite.ora password=oracle entries=12

File= nombre del fichero de contraseñas.

Password= contraseña para SYS AS SYSDBA.

Entries= número máximo de DBA.

Los argumentos file y password son obligatorios.

Como vemos, mediante entries limitamos el número de usuarios máximos que pueden tener ese privilegio en la base de datos.

3.8. Roles estándar.

La creación de roles permite asignar un grupo de permisos a un usuario, y poder modificar este grupo de permisos sin tener que ir modificando todos los usuarios. Si asignamos un rol con 10 permisos a 300 usuarios, y posteriormente añadimos un permiso nuevo al rol, no será necesario ir añadiendo este nuevo permiso a los 300 usuarios, ya que el rol se encarga automáticamente de propagarlo. La sintaxis básica es: CREATE ROLE nombre_rol [NOT IDENTIFIED | IDENTIFIED [BY contraseña | EXTERNALLY]] ;

Una vez que el rol ha sido creado será necesario añadirle permisos a través del comando GRANT. Si creamos un nuevo usuario, como Juana, no tendrá ningún privilegio del sistema. En la mayoría de casos, un usuario de aplicación recibirá privilegios a través de roles. Podemos agrupar privilegios del sistema y acceso a objetos dentro de roles específicos según las necesidades de los usuarios de aplicación. Podemos crear nuestros propios roles para acceder a aplicaciones, y podemos usar los roles por defecto de Oracle para algún requerimiento de acceso al sistema. Los roles estándar más importantes creados durante la creación de la base de datos son:

CONNECT, RESOURCE y DBA Se proporcionan para compatibilidad con versiones previas de Oracle.

DELETE_CATALOG_ROLE,

EXECUTE_CATALOG_ROLE,

SELECT_CATALOG_ROLE

Se proporcionan estos roles para acceder a las vistas y paquetes del diccionario de datos.

EXP_FULL_DATABASE,

IMP_FULL_DATABASE

Se proporcionan para usar las utilidades Import y Export.

AQ_USER_ROLE,

AQ_ADMINISTRATOR_ROLE

Estos roles son necesarios para Oracle Advanced Queuing.

SNMPAGENT Este rol es usado por el Enterprise Manager Intelligent Agent.

RECOVERY_CATALOG_OWNER Se requiere este rol para la creación de un catálogo de recuperación propietario de esquema.

SCHEDULER_ADMIN Este rol permite la ejecución de los procedimientos del paquete

DBMS_SCHEDULER. Debería restringirse sólo a los DBA's.

CONNECT, RESOURCE y DBA se proporcionan para compatibilidad y no deberían usarse más. CONNECT da a los usuarios la habilidad de conectarse (iniciar una sesión) y realizar funciones básicas. Los usuarios con el rol CONNECT pueden crear tablas, vistas, secuencias, clústeres, sinónimos y enlaces de base de datos. Los usuarios con el rol RESOURCE pueden crear sus propias tablas, secuencias, procedimientos, triggers, tipos

Page 173: Java y Oracle 11g

Oracle /173

de datos, operadores, tipos de índices, índices y clústeres. Los usuarios con este rol pueden recibir también el privilegio del sistema UNLIMITED TABLESPACE, que les permite saltarse las cuotas de todos los tablespaces. Los usuarios con el rol DBA pueden realizar funciones de administración de base de datos, incluido crear y alterar usuarios, espacios de tabla y objetos. En lugar de CONNECT, RESOURCE y DBA, deberíamos crear nuestros propios roles que tengan privilegios para ejecutar privilegios específicos del sistema.

3.9. Permisos del sistema.

Ya hemos dicho que los privilegios de sistema son permisos para realizar ciertas operaciones en la base de datos. El modo de asignar un privilegio es a través de la instrucción GRANT y el modo de cancelar un privilegio es a través de la instrucción REVOKE. 3.9.1. Instrucción «GRANT». Para conceder permisos se utiliza el comando GRANT, el cual tiene la siguiente sintaxis: GRANT [privilegios_de_sistema | roles] TO [usuarios | roles | PUBLIC] [WITH ADMIN OPTION];

Es posible dar más de un privilegio de sistema o rol, separándolos por comas. También es posible asignarle uno (o varios) privilegios a varios usuarios, separándolos por comas. Si se le asigna el privilegio a un rol, se asignará a todos los usuarios que tengan ese rol. Si se asigna el privilegio a PUBLIC, se asignará a todos los usuarios actuales y futuros de la base de datos. La cláusula WITH ADMIN OPTION permite que el privilegio/rol que hemos concedido, pueda ser concedido a otros usuarios por el usuario al que estamos asignando. Veamos algunos ejemplos sencillos:

Permiso para acceder a la base de datos (permiso de sistema): GRANT CREATE SESSION TO un_usuario;

Permiso para usuario de modificación de datos (permiso sobre objeto): GRANT SELECT, INSERT, UPDATE, DELETE ON una_tabla TO un_usuario;

Permiso de solo lectura sobre una tabla para todos: GRANT SELECT ON una_tabla TO PUBLIC;

Permisos de sistema (system privileges): Los permisos de sistema más importantes son CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE USER, CREATE PROCEDURE, CREATE SYNONYM, ALTER TABLE, ALTER VIEW, ALTER PROCEDURE, ALTER SYNONYM, DROP TABLE, DROP VIEW, DROP PROCEDURE, DROP SYNONYM, ... Se pueden usar las siguientes sintaxis para conceder los permisos de sistema: GRANT privilegios TO {usuario, | rol, | PUBLIC} [IDENTIFIED BY contraseña] [WITH ADMIN OPTION];

GRANT rol TO {usuario, | rol, | PUBLIC} [IDENTIFIED BY contraseña] [WITH ADMIN OPTION];

GRANT ALL PRIVILEGES TO {usuario, | rol, |PUBLIC} [IDENTIFIED BY contraseña] [WITH ADMIN OPTION];

Podemos obtener la lista de permisos del sistema con el comando siguiente: SELECT * FROM SYSTEM_PRIVILEGE_MAP;

Por ejemplo, si deseamos dar permisos de creación y borrado de tablas al usuario Empleado podemos usar: GRANT CREATE ANY TABLE, DROP ANY TABLE TO Empleado;

Los permisos de sistema son auditables. Permisos sobre objetos (object privileges): Los permisos sobre objetos más importantes son: SELECT, UPDATE, INSERT, DELETE, ALTER, DEBUG, EXECUTE, INDEX, y REFERENCES. Se pueden usar las siguientes sintaxis para conceder permisos sobre objetos: GRANT permiso [(columna, columna, ...)] ON [esquema.]objeto

TO {usuario, | rol, | PUBLIC} [WITH GRANT OPTION] [WITH HIERARCHY OPTION];

GRANT ALL PRIVILEGES [(columna, columna, ...)] ON [esquema.]objeto

TO {usuario, | rol, | PUBLIC} [WITH GRANT OPTION] [WITH HIERARCHY OPTION];

GRANT permiso [(columna, columna, ...)] ON DIRECTORY nombre_directorio

TO {usuario, | rol, | PUBLIC} [WITH GRANT OPTION] [WITH HIERARCHY OPTION];

GRANT permiso [(columna, columna, ...)] ON JAVA [RE]SOURCE [esquema.]objeto

TO {usuario, | rol, | PUBLIC} [WITH GRANT OPTION] [WITH HIERARCHY OPTION];

Con la opción WITH HIERARCHY OPTION damos permisos sobre todos los sub-objetos, incluso sobre los que se creen después de ejecutar el comando GRANT. Con la opción WITH GRANT OPTION damos permiso para que el que los recibe los pueda a su vez asignar a otros usuarios y roles. La opción "GRANT ALL PRIVILEGES..." se puede escribir también como "GRANT ALL..."

Page 174: Java y Oracle 11g

Oracle /174

Podemos obtener la lista de permisos de las tablas con el comando siguiente: SELECT * FROM all_tab_privs_made;

3.9.2. Instrucción «REVOKE». Para quitar permisos concedidos se utiliza el comando REVOKE, el cual tiene la siguiente sintaxis: REVOKE [privilegios_de_sistema | roles] FROM [usuarios | roles |PUBLIC];

Es posible eliminar más de un privilegio de sistema o rol, separándolos por comas. También es posible eliminar uno (o varios) privilegios a varios usuarios, separándolos por comas. Si se elimina el privilegio de un rol, se eliminará de todos los usuarios que tengan ese rol. Si se elimina el privilegio de PUBLIC, se eliminará de todos los usuarios actuales y futuros de la base de datos. Como es lógico, sólo se podrá eliminar un privilegio/rol, si previamente ha sido concedido a través de la instrucción GRANT. Ejemplos: REVOKE DBA FROM ADMINISTRADOR;

REVOKE CREATE USER FROM PEPOTE;

REVOKE DROP USER FROM JUANCITO;

RECOKE CONNECT, RESOURCE FROM PEPOTE, JUANCITO;

REVOKE CONNECT, RESOURCE, DBA, EXP_FULL_DATABASE, IMP_FULL_DATABASE

FROM CONTROL_TOTAL;

REVOKE CONTROL_TOTAL FROM ADMINISTRADOR;

Revocando privilegios de objetos. El modo de eliminar permisos de objeto es con: REVOKE [ALL {PRIVILEGES} | SELECT | INSERT | UPDATE | DELETE]

[(columna, ...)] ON objeto FROM [usuario | rol | PUBLIC] {WITH ADMIN OPTION};

Al igual que con los permisos de sistema, es posible asignar un permiso de objeto sobre uno o varios usuario y/o roles (separados por comas). Si se asigna a PUBLIC será accesible en toda la base de datos. Si se incluye la cláusula WITH ADMIN OPTION, este permiso podrá ser concedido por el usuario al que se le ha asignado. 3.9.3. Lo que los usuarios pueden conceder. Un usuario puede conceder privilegios sobre cualquier objeto del que sea propietario. El administrador de la base de datos puede conceder cualquier privilegio del sistema. Supongamos que el usuario Dora es propietario de la tabla Ventas y es un administrador de la base de datos. Crea dos nuevos usuarios, Bob y Judy, con estos privilegios: CREATE USER Judy IDENTIFIED BY contraseña;

/

GRANT CREATE SESSION TO Judy;

/

CREATE USER Bob IDENTIFIED BY contraseña;

/

GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SYNONYM TO Bob;

/

ALTER USER Bob DEFAULT TABLESPACE USERS QUOTA 5m ON USERS;

Esta secuencia de comandos da a Judy y Bob la habilidad de conectarse a Oracle, y da a Bob algunas capacidades extra. Pero, ¿pueden hacer algo con las tablas de Dora? No sin permisos explícitos. Para dar acceso a nuestras tablas se usan la siguiente sintaxis del comando GRANT: GRANT { permiso | ALL [PRIVILEGES] }

[(columna [, columna] . . .)]

[, { permiso | ALL [PRIVILEGES] }

[(columna [, columna] . . .)] ] . . .

ON objeto TO {usuario | rol}

[WITH GRANT OPTION]

[WITH HIERARCHY OPTION];

Los permisos que un usuario puede conceder incluyen los siguientes: • Sobre tablas de usuario, vistas y vistas materializadas:

FLASHBACK. INSERT. UPDATE (todas o columnas específicas) DELETE.

Page 175: Java y Oracle 11g

Oracle /175

SELECT. Los permisos INSERT, UPDATE y DELETE sólo pueden concederse sobre vistas materializadas si son actualizables.

• Sobre tablas, también se pueden conceder: ALTER (tabla – todas o columnas específicas) DEBUG. REFERENCES. INDEX (columnas en una tabla) ON COMMIT REFRESH. QUERY REWRITE. ALL (de los elementos previamente listados)

• Sobre procedimientos, funciones, paquetes, tipos de datos abstractos, librerías, tipos de índices y operadores:

EXECUTE. DEBUG.

• Sobre secuencias: SELECT. ALTER.

• Sobre directorios (para tipos de datos BFILE LOB y tablas externas): READ. WRITE.

• Sobre tipos de datos abstractos y vistas: UNDER (para la habilidad de crear subvistas bajo una vista, o subtipos bajo un tipo)

Cuando se ejecuta otro procedimiento o función de usuario, normalmente se ejecuta usando los permisos de su propietario. Esto significa que no necesitamos explícitamente acceder a los datos que el procedimiento o función usa; podemos ver sólo el resultado de la ejecución, no los datos subyacentes. También podemos crear procedimientos almacenados que se ejecuten bajos los permisos del usuario que los invoca en vez de los permisos del propietario. Dora le da a Bob acceso SELECT a la tabla Ventas: GRANT SELECT ON Ventas TO Bob;

La cláusula WITH GRANT OPTION del comando GRANT permite al recibidor de permisos pasarlos a otros. Si Dora concede permisos a Bob usando WITH GRANT OPTION, Bob podrá dar permisos de acceso a las tablas de Dora a otros usuarios. Movernos a otro usuario con «CONNECT». Para realizar pruebas, Dora se conecta a la base de datos con el nombre de usuario de Bob usando el comando CONNECT. Este comando puede usarse a través de uno de los siguientes métodos:

• Indicando el nombre de usuario y contraseña sobre la misma línea de comando. • Escribiendo sólo el comando y respondiendo a las peticiones de credenciales. • Escribiendo el comando y el nombre de usuario, y después respondiendo a la petición de contraseña.

Los dos últimos métodos evitan mostrar la contraseña y por tanto son inherentemente más seguros. El siguiente comando es una muestra de conexión a la base de datos: CONNECT Bob/contraseña

Una vez conectado, Dora selecciona los datos de una tabla a la cual Bob tiene permisos de acceso. SELECT * FROM Dora.Ventas;

Se crea una vista llamada VENTAS, la cual simplemente selecciona los datos de la tabla Dora.Ventas: CREATE VIEW VENTAS AS SELECT * FROM Dora.Ventas;

Esta vista producirá exactamente los mismos resultados que la tabla Dora.Ventas. Si ahora Dora retorna a su propia cuenta y crea una vista que seleccione sólo parte de la tabla Ventas: CONNECT Dora/contraseña

/

CREATE VIEW ALGUNAS_VENTAS AS

SELECT * FROM Ventas WHERE IdCliente = 1;

Si entonces le concede permisos SELECT y UPDATE a Bob sobre esta vista, y revoca todos los permisos de Bob sobre la tabla Ventas: GRANT SELECT, UPDATE ON ALGUNAS_VENTAS TO Bob;

Page 176: Java y Oracle 11g

Oracle /176

/

REVOKE ALL ON Ventas FROM Bob;

Y si ahora Dora se reconecta con la cuenta de Bob para probar los cambios: CONNECT Bob/contraseña

/

SELECT * FROM VENTAS;

Se produce un error: ERROR en línea 1:

ORA-04063: vista "BOB.VENTAS" tiene errores

Intentar hacer una selección sobre la vista VENTAS falla porque para su definción se necesitan permisos de acceso a la tabla subyacente Dora.Ventas, para la cual el usuario Bob ya no tiene permisos de acceso. Sin embargo, un intento de seleccionar la vista Dora.ALGUNAS_VENTAS: SELECT * FROM Dora.ALGUNAS_VENTAS;

No provoca ningún tipo de error; aunque no se tengan permisos sobre la tabla subyacente Dora.Ventas, el acceso a parte de esta tabla está permitido a través de esta vista. Es decir, podemos crear una vista usando virtualmente alguna restricción o computación en sus columnas, y entonces podemos dar permisos a otros usuarios de acceso a la vista, en vez de a las tablas subyacentes. Esos usuarios sólo verán la información que devuelva la vista. Ahora, si se crea la vista ALGUNAS_VENTAS_CORTO bajo la cuenta de Bob: CREATE VIEW ALGUNAS_VENTAS_CORTO AS SELECT * FROM Dora.ALGUNAS_VENTAS;

Y se actualiza un registro: UPDATE ALGUNAS_VENTAS_CORTO SET Cantidad = 88 WHERE IdVenta = 3;

Cuando la vista ALGUNAS_VENTAS_CORTO es consultada, se muestran los efectos de la actualización. Una consulta sobre Dora.ALGUNAS_VENTAS debería mostrar los mismos resultados, ya que la actualización se realizó sobre la tabla subyacente.

Nota. Necesitamos conceder el permiso SELECT a cualquier tabla de la que queramos actualizar o borrar registros. Esto está de acuerdo con el desarrollo ANSI estándar y refleja el hecho de que un usuario que sólo tiene permiso UPDATE o DELETE sobre una tabla podría usar los comentarios de generación de la base de datos para descubrir información sobre los datos subyacentes.

Creación de sinónimos. Un método alternativo para crear una vista que incluya una tabla entera o una vista desde otro usuario es crear un sinónimo: CREATE SYNONYM BOB_VENTAS FOR Dora.ALGUNAS_VENTAS;

Este sinónimo puede ser tratado exactamente como una vista. Usando permisos no concedidos. Intentemos suprimir el registro que fue actualizado previamente: DELETE FROM ALGUNAS_VENTAS WHERE IdVenta = 3;

Se produce un error: ERROR en línea 1:

ORA-01031: permisos insuficientes

Bob no recibió el permiso DELETE por parte de Dora, así que el intento falla. Pasando permisos. Bob puede conceder autoridad a otros usuarios para acceder a sus tablas, pero no puede otorgar a otros usuarios a tablas que no le pertenecen. Así, si intenta dar la autoridad INSERT a Judy: GRANT INSERT ON Dora.ALGUNAS_VENTAS TO Judy;

Se produce un error: ERROR en línea 1:

ORA-01031: permisos insuficientes

Debido a que Bob no tiene esta autoridad, falla. Ahora, si Bob intenta pasar un permiso que sí tiene, como SELECT: GRANT SELECT ON Dora.ALGUNAS_VENTAS TO Judy;

Se produce un error: ERROR en línea 1:

ORA-01031: permisos insuficientes

Él no puede ceder este permiso tampoco, porque la vista ALGUNAS_VENTAS no le pertenece. Si se le hubiese

Page 177: Java y Oracle 11g

Oracle /177

concedido el acceso a ALGUNAS_VENTAS con WITH GRANT OPTION, entonces el comando precedente no hubiese fallado. La vista ALGUNAS_VENTAS_CORTO realmente le pertenece, así que puede tratar de pasar la autoridad de esta vista a Judy: GRANT SELECT ON ALGUNAS_VENTAS_CORTO TO Judy;

Pero el resultado es también un error: ERROR en línea 1:

ORA-01720: no existe la opción grant para 'DORA.ALGUNAS_VENTAS'

Ya que la vista ALGUNAS_VENTAS es realmente una de las vistas de Dora, y Bob no tiene concedido SELECT

WITH GRANT OPTION sobre la vista, la concesión de Bob falla. Adicionalmente, una nueva tabla, propiedad de Bob, es creada y cargada con la información actual de su vista ALGUNAS_VENTAS_CORTO: CREATE TABLE NO_VENTAS AS

SELECT * FROM ALGUNAS_VENTAS_CORTO;

Se concede el permiso SELECT sobre esta tabla a Judy: GRANT SELECT ON NO_VENTAS TO Judy;

Podemos hacer consultas a esta tabla a través de la cuenta de Judy: CONNECT Judy/contraseña

/

SELECT * FROM Bob.NO_VENTAS;

Si Dora quiere que Bob ser capaz de pasar sus permisos a otros, pueden añadir otra cláusula al comando GRANT: CONNECT Dora/contraseña

/

GRANT SELECT, UPDATE ON ALGUNAS_VENTAS TO Bob WITH GRANT OPTION;

La cláusula WITH GRANT OPTION permite a Bob pasar los accesos de ALGUNAS_VENTAS a Judy a través de su vista ALGUNAS_VENTAS_CORTO. Si cualquier otro usuario intenta acceder a una tabla para la cual no tiene el permiso SELECT siempre se producirá el siguiente mensaje: ERROR en línea 1:

ORA-00942: la tabla o vista no existe

Creación de un rol. Además de los roles por defecto vistos previamente, podemos crear nuestros propios roles con Oracle. Los roles que creamos pueden comprender tablas o permisos del sistema o una combinación de ambos. Para crear un rol necesitamos tener el permiso de sistema CREATE ROLE. La sintaxis para crear un rol es la siguiente: CREATE ROLE nombre_del_rol

[NOT IDENTIFIED |

IDENTIFIED {BY contraseña | USING [esquema.]paquete | EXTERNALLY | GLOBALLY }];

Cuando se crea un rol no tiene ningún permiso asociado. Dos ejemplo de creación de roles son los siguientes: CREATE ROLE Empleado;

CREATE ROLE Administrador;

El primer comando crea un rol llamado Empleado, el cual es usado en ejemplos posteriores. El segundo comando crea un rol llamado Administrador. Concediendo permisos a un rol. Una vez creado un rol, podemos darle permisos. La sintaxis del comando GRANT es la misma para roles como para usuarios. Cuando concedemos permisos a roles, se usa el nombre de rol en la cláusula del comando GRANT, tal como se muestra a continuación: GRANT SELECT ON Ventas TO Empleado;

El permiso de acceso a la tabla Ventas será concedido a cualquier usuario perteneciente al rol Empleado. Si somos administradores de base de datos, o tenemos concedidos el role de sistema GRANT ANY PRIVILEGE podemos conceder permisos del sistema (como CREATE SESSION, CREATE SYNONYM y CREATE VIEW). Estos permisos estarán entonces disponibles para cualquier usuario de nuestro rol. La habilidad de registrarnos dentro de una base de datos se realiza a través del permiso de sistema CREATE

SESSION. En el siguiente ejemplo, este permiso es concedido al rol Empleado. Este permiso es también concedido al rol Administrador, además del permiso CREATE VIEW:

Page 178: Java y Oracle 11g

Oracle /178

GRANT CREATE SESSION TO Empleado;

GRANT CREATE SESSION, CREATE VIEW TO Administrador;

Concediendo un rol a otro rol. Los roles pueden ser concedidos a otros roles. Podemos hacer esto a través del comando GRANT, tal como se muestra a continuación: GRANT Empleado TO Administrador;

Nota. No se pueden hacer concesiones circulares, así que no podemos conceder Administrador a Empleado.

En este ejemplo, el rol Empleado es concedido al rol Administrador. Incluso aunque no hayamos concedido directamente ningún permiso de tabla al rol Administrador, ahora heredará cualquier permiso concedido al rol Empleado. La organización de roles de este modo es un diseño común en organizaciones jerárquicas. Cuando concedemos un rol a otro (o a un usuario) podemos utilizar la cláusula WITH ADMIN OPTION: GRANT Empleado TO Administrador WITH ADMIN OPTION;

Cuando se usa esta cláusula, el destinatario tiene la autoridad de conceder el rol a otro usuario o roles. El destinatario también podrá alterar o borrar el rol. Concediendo un rol a usuarios. Los roles pueden ser concedidos a usuarios. Cuando se conceden a usuarios, los roles pueden ser pensados como conjuntos de permisos con nombre. En vez de conceder cada permiso a cada usuario, podemos conceder los permisos al rol y entonces conceder el rol a cada usuario. Esto simplifica enormemente las tareas administrativas involucradas con la gestión de permisos.

Nota. Los permisos que son concedidos a través de roles no pueden ser usados para vistas, procedimientos, funciones, paquetes o claves foráneas. Cuando se crean estos tipos de objetos de base de datos, debemos confiarles directamente los necesarios privilegios.

Podemos conceder un rol a un usuario a través del comando GRANT, tal como se muestra a continuación: GRANT Empleado TO Bob;

En este ejemplo, el usuario Bob hereda todos los permisos concedidos al rol Empleado (CREATE SESSION y SELECT sobre la tabla Ventas). Cuando concedemos un rol a un usuario, podemos incluir la cláusula WITH ADMIN OPTION: GRANT Administrador TO Dora WITH ADMIN OPTION;

Esto permitirá a Dora la autoridad de conceder el rol Administrador a otros usuarios o roles, o de alterar y eliminar el rol. Añadiendo una contraseña a un rol. Se puede usar el comando ALTER ROLE para un único propósito: cambiar la autoridad necesaria para habilitar el rol. Por defecto, los roles no tienen contraseñas asociadas con ellos. Para habilitar la seguridad de un rol se usa la palabra clave IDENTIFIED en el comando ALTER ROLE. Hay dos formas de implementar esta seguridad. Primera, podemos usar la cláusula IDENTIFIED BY del comando ALTER ROLE para especificar una contraseña, tal como se muestra a continuación: ALTER ROLE Administrador IDENTIFIED BY contraseña;

Cada vez que un usuario intente activar este rol, se le requerirá la contraseña. Sin embargo, si este rol es asignado como el rol por defecto del usuario, no se le requerirá ninguna contraseña cuando el usuario se registre. Los roles pueden atarse también a permisos del sistema operativo. Si esta capacidad está disponible en el sistema operativo, se usa la cláusula IDENTIFIED EXTERNALLY en el comando ALTER ROLE. Cuando el rol se habilite, Oracle comprobará el sistema operativo para verificar el acceso. El siguiente ejemplo muestra cómo cambiar un rol para esta funcionalidad: ALTER ROLE Administrador IDENTIFIED EXTERNALLY;

En muchos sistemas UNIX, el proceso de comprobación usa el fichero /etc/group. Para usar este fichero en algún sistema operativo, el parámetro de inicio OS_ROLES del fichero init.ora debe ser asignado a TRUE. El siguiente ejemplo de este proceso de comprobación es para una instancia de base de datos llamada "Local" sobre un sistema UNIX. El fichero de servidor /etc/group puede contener la siguiente entrada: ora_local_administrador_d:NONE:1:dora

Esta entrada concede el rol Administrador a la cuenta llamada Dora. El sufijo _d indica que este rol es asignado por defecto cuando Dora se registra. Un sufijo _a indicaría que este rol estaría habilitado con WITH ADMIN

Page 179: Java y Oracle 11g

Oracle /179

OPTION. Si más de un usuario tienen concedido este rol, los nombres de usuario adicionales deben ser añadidos a la entrada de /etc/group, tal como se muestra a continuación: ora_local_manager_d:NONE:1:dora,judy

Si usamos esta opción, los roles en la base de datos serán habilitados a través del sistema operativo. Eliminando una contraseña de un rol. Para eliminar una contraseña de un rol, se usan la cláusula NOT IDENTIFIED en el comando ALTER ROL, tal como se muestra a continuación: ALTER ROLE Administrador NOT IDENTIFIED;

Habilitando y deshabilitando roles. Cuando una cuenta de usuario es alterada, una lista de roles por defecto para ese usuario pueden ser creados a través de la cláusula DEFAULT ROLE del comando ALTER USER. La acción por defecto de este comando asigna todos los roles del usuario como roles por defecto, habilitándolos cada vez que el usuario se registra.

Nota. El número máximo de roles que un usuario puede tener habilitados cada vez es determinado por el parámetro de inicialización MAX_ENABLED_ROLES.

La sintaxis para este parte del comando ALTER USER es la siguiente: ALTER USER nombre_del_usuario

DEFAULT ROLE {[rol1, rol2]

[ALL | ALL EXCEPT rol1, rol2] [NONE]};

Un usuario puede ser modificado para tener, por defecto, roles específicos habilitados, todos los roles habilitados, todos habilitados excepto roles específicos, o ningún rol habilitado. Por ejemplo, el siguiente comando ALTER USER habilita el rol Empleado cuando Bob se registra: ALTER USER Bob

DEFAULT ROLE Empleado;

Para habilitar un rol que no sea por defecto, se usa el comando SET ROLE, tal como se muestra a continuación: SET ROLE Empleado;

Para ver qué roles están habilitados dentro de la sesión actual, hay que consultar la vista del diccionario de datos SESSION_ROLES. Consultando la vista SESSION_PRIVS podemos ver los permisos de sistema habilitados actualmente. También se pueden usar las cláusulas ALL y ALL EXCEPT del comando ALTER USER. SET ROLE ALL;

SET ROLE ALL EXCEPT Empleado;

Si un rol tiene asociada una contraseña, ésta debe ser especificada mediante la cláusula IDENTIFIED BY: SET ROLE Administrador IDENTIFIED BY contraseña;

Para deshabilitar un rol en nuestra sesión, se usan el comando SET ROLE NONE. Esto deshabilita todos los roles de la sesión actual. Una vez que todos los roles han sido deshabilitados deberemos habilitar los que queramos. Ya que puede ser necesario ejecutar un comando SET ROLE NONE de vez en cuando, podemos conceder el permiso CREATE SESSION directamente a los usuarios en vez de usar roles. Revocando los permisos de un rol. Para revocar los permisos de un rol se usa el comando REVOKE, descrito previamente. Se debe especificar el permiso, nombre del objeto y nombre del rol, tal como se muestra a continuación: REVOKE SELECT ON Ventas FROM Empleado;

Con este comando, los usuarios del rol Empleado ya no podrán consultar la tabla Ventas. Eliminando un rol. Para eliminar un rol se usa el comando DROP ROL, tal como se muestra a continuación: DROP ROLE Administrador;

El rol especificado y sus permisos asociados son eliminados de la base de datos. Las concesiones y revocaciones de permisos del sistema y de objetos tienen efecto inmediato. Las concesiones y revocaciones de roles se observan sólo cuando un usuario actual envía un comando SET ROLE o se inicia una nueva sesión de usuario. Concediendo actualizaciones a columnas específicas. Podemos querer conceder a usuarios el permiso SELECT para más columnas de las que les queremos conceder el permiso UPDATE. Debido a que las columnas SELECT pueden restringirse a través de una vista, para restringir las columnas que pueden ser actualizadas se necesita una forma especial del comando GRANT de usuario. A continuación se muestra un ejemplo para dos columnas de la tabla Ventas:

Page 180: Java y Oracle 11g

Oracle /180

GRANT UPDATE (Producto, Cantidad) ON Ventas TO Judy;

Revocando privilegios de objetos. La sintaxis para revocar permisos es similar a la del comando GRANT: REVOKE { permiso_de_objeto | ALL [permisos]}

[(columna [, columna] . . . )]

[, { permiso_de_objecto | ALL [permisos]}

[(columna [, columna] . . . )]] . . .]

ON objecto

FFROM {usuario | rol} [, {usario | rol}]

[CASCADE CONSTRAINTS] [FORCE];

REVOKE ALL elimina cualquiera de los permisos listados previamente, desde SELECT hasta INDEX; si se revocan permisos individuales se dejarán intactos los que se habían concedido. La cláusula WITH GRANT OPTION es revocada junto con el permiso al cual se conectó; se revoca entonces en cascada a todos los usuarios que recibieron sus accesos a través de WITH GRANT OPTION. Si un usuario define restricciones de integridad referencial sobre un objeto, Oracle elimina estas restricciones si revocamos los permisos sobre el objeto usando la opción CASCADE CONSTRAINTS. La opción FORCE se aplica a los tipos de datos definidos por el usuario, y revoca el permiso EXECUTE sobre tipos de datos definidos por el usuario con tablas o dependencias de tipos; todos los objetos dependientes serán marcados como inválidos y los datos en tablas dependientes serán inaccesibles hasta que se concedan los permisos necesarios. Seguridad por usuario. Se puede conceder el acceso a tablas específicamente, tabla por tabla y vista por vista, para cada usuario o rol. Hay, sin embargo, una técnica adicional que simplifica este proceso en algunos casos. Supongamos una consulta sobre la tabla Ventas donde seleccionamos el nombre del cliente asociado con la venta, el nombre del producto y el precio del producto: SELECT NombreCliente, Producto, Precio FROM Ventas;

Si queremos restringir el acceso a esta tabla a cada cliente, de forma que cada cliente pueda ver sólo sus ventas, podemos crear tantas vistas como clientes aplicando permisos diferentes. Pero si el nombre de cliente coincide con el nombre de usuario de la base de datos, también podemos crear una única vista con una cláusula WHERE que filtre por el usuario actual (dado por la pseudo-columna USER de SQL*Plus). CREATE OR REPLACE VIEW Mis_Ventas AS

SELECT * FROM Ventas

WHERE NombreCliente = USER;

El propietario de la tabla Ventas puede crear esta vista y conceder el permiso SELECT a todos los usuarios que son clientes. Un usuario puede entonces consultar la tabla Ventas a través de la vista Mis_Ventas, de forma que sólo podrá ver únicamente sus registros de venta. El siguiente ejemplo muestra cómo el administrador Dora crea un usuario para el cliente Juan, y cómo le concede permisos para que consulte sus ventas: CREATE USER Juan IDENTIFIED BY contraseña;

/

GRANT CREATE SESSION TO Juan;

/

GRANT SELECT ON Mis_Ventas TO Juan;

/

CONNECT Juan/contraseña

/

SELECT * FROM Dora.Mis_Ventas;

El resultado de la última consulta serán aquellos registros de venta donde el campo NombreCliente es igual a 'Juan'. Concediendo accesos en general. En vez de conceder accesos a cada usuario, el comando GRANT puede ser generalizado para todo el mundo: GRANT SELECT ON Mis_Ventas TO PUBLIC;

Esto permite a todos acceder a la vista, incluidos los usuarios creados después de aplicar este comando. Sin embargo, para acceder a la vista cada usuario tendrá que usar el nombre de usuario del propietario de la vista (en este ejemplo, Dora) como un prefijo. Para evitar esto, Dora puede crear un sinónimo público: CREATE PUBLIC SYNONYM Mis_Ventas FOR Dora.Mis_Ventas;

Page 181: Java y Oracle 11g

Oracle /181

De esta forma, cualquiera puede acceder a Mis_Ventas sin prefijarlo con el esquema del propietario. 3.9.4. Concesión de recursos limitados. Cuando concedemos cuotas de recursos en una base de datos de Oracle, se usa el parámetro QUOTA del comando CREATE USER o ALTER USER, tal como se muestra a continuación. ALTER USER Bob

QUOTA 100M ON USERS;

En este ejemplo, se le concede a Bob una cuota de 100 Megabytes en el espacio de tablas USERS. Se puede asignar un espacio de cuota, cuando se crea el usuario, a través del comando CREATE USER. Si queremos quitar los límites a la cuota de un usuario, podemos conceder al usuario el permiso de sistema UNLIMITED

TABLESPACE (no se puede conceder a un role este permiso). Podemos usar también perfiles para forzar otros límites de recursos, como la cantidad de tiempo de CPU o el tiempo de espera para las respuestas a un usuario. Se crea un perfil detallando estos límites de recursos y entonces se asigna a uno o más usuarios.

3.10. Metadatos sobre permisos y usuarios.

El diccionario de datos de Oracle proporciona varias vistas que se pueden utilizar para consultar los usuarios y permisos registrados. Las más relevantes son:

• Para obtener los usuarios: - dba_users: lista los usuarios con permisos de administrador. - user_users: lista el usuario actual. - all_users: lista todos los usuarios de la base de datos.

• Para obtener los roles. - dba_roles: lista los roles registrados en la base de datos.

• Roles asignados a roles o usuarios. - dba_role_privs: lista las asociaciones de los roles o usuarios de administrador con los roles que tienen asignados. - user_role_privs: lista las asociaciones del usuario actual con los roles que tiene asignado.

• Privilegios asignados a roles o usuarios. - dba_sys_privs: lista las asociaciones entre usuarios o roles con sus privilegios asignados.

• Permisos sobre tablas asignados a roles o usuarios. - dba_tab_privs: lista los permisos sobre tablas indicando el usuario, el permiso, quién lo concedió, etc.

• Roles asignados a roles. - role_role_privs: lista la asociación entre roles y a que roles fueron asignados.

• Privilegios de cada rol: - role_sys_privs: lista las asociaciones entre roles y sus privilegios.

• Límites de recursos - user_resource_limits: lista registros indicando por cada nombre de recursos si tiene limitaciones.

• Perfiles y sus límites de recursos asociados - dba_profiles: lista las asociaciones entre perfiles, recursos y límites.

• Límites de recursos en cuanto a restricciones en claves - user_password_limits: lista registros indicando por cada recurso si tiene limitaciones de clave.

• Límites de recursos en cuanto a espacio máximo en tablespaces - dba_ts_quotas: lista los tablespaces indicando los administradores y los límites de espacio. - user_ts_quotas: lista los tablespaces del usuario actual y los límites de espacio.

4. Bases de datos virtuales privadas.

En el capítulo previo se ha visto cómo usar vistas para establecer cierta seguridad a la hora de mostrar registros que cumplan criterios específicos. En este capítulo veremos cómo usar bases de datos virtuales privadas (VPD) para proporcionar seguridad a nivel de registro a través de las tablas de nuestra aplicación. En VPD podemos asociar políticas de seguridad directamente a tablas, vistas y sinónimos de forma que ningún usuario podrá saltarse los ajustes de seguridad. En VPD, cualquier consulta usada para acceder a una tabla, vista o sinónimo protegido por las políticas de VPS es modificado dinámicamente para incluir condiciones limitantes (una cláusula WHERE o AND). La modificación ocurre transparentemente, y el usuario verá sólo los datos que pasen la condición limitante en la cláusula WHERE. Este acceso de grano fino permite un mayor control sobre los accesos a las tablas de nuestra

Page 182: Java y Oracle 11g

Oracle /182

aplicación. Las políticas VPD pueden aplicarse a los comandos SELECT, INSERT, UPDATE, INDEX y DELETE. Se pueden crear diferentes políticas de seguridad para cada uno de estos accesos. Desde Oracle Database 10g, VPD se ha extendido para incluir VPD a nivel de columna, en las cuales las políticas de seguridad se aplican sólo cuando una determinada columna o columnas son accedidas. Oracle Database 11g también soporta máscara de columna, con lo cual columnas protegidas pueden mostrar sólo valores nulos cuando se accede a los registros. Además, las opciones de auditora se han mejorado, de forma que los DBA's pueden realizar auditorias de grado fino para los comandos SELECT, INSERT, UPDATE y DELETE.

4.1. Cómo implementar VPD a nivel de tabla.

VPD permite personalizar el modo en el que diferentes usuarios pueden ver la misma tabla.

Nota. El mecanismo para implementar VPD solo está disponible con la Enterprise Edition de Oracle. Además, VPD hace uso del paquete SYS.DBMS_RLS, el cual debería haber sido instalado automáticamente con el resto de paquetes predefinidos. Si no se pueden ejecutar los procedimientos de este paquete debemos instalarlos personalmente. Para hacer esto debemos conectarnos como un usuario privilegiado a una consola de SQL*Plus y ejecutar los siguientes archivos de script:

carpeta_instalación_Oracle \Rdbms\Admin\dbmsrlsa.sql

carpeta_instalación_Oracle \Rdbms\Admin\prvtrlsa.plb

4.1.1. Configuración inicial. Para seguir los ejemplos mostrados es este capítulo se necesita acceder con una cuenta de SYSDBA privilegiada (accediendo con el rol SYSDBA). VPD requiere el uso de un paquete llamado SYS.DBMS_RLS; desde la cuenta SYSDBA privilegiada hay que conceder el permiso EXECUTE sobre el paquete SYS.DBMS_RLS a todos los usuarios. También hay que crear los usuarios que serán usados durante los ejemplo: Dora (que será el propietario de las tablas), Ana y Bob. GRANT EXECUTE ON SYS.DBMS_RLS TO PUBLIC;

CREATE USER Ana IDENTIFIED BY contraseña;

CREATE USER Bob IDENTIFIED BY contraseña;

GRANT CREATE SESSION TO Ana, Bob;

Desde la cuenta SYSDBA privilegiada se crea al usuario Dora (si es que no existe todavía), y se le dan los permisos del sistema CREATE ANY CONTEXT y CREATE PUBLIC SYNONYM: GRANT CREATE ANY CONTEXT, CREATE PUBLIC SYNONYM TO Dora;

Ahora crearemos dos tablas para el usuario Dora: la tabla STOCK_CUENTA y la tabla STOCK_TRX. La tabla STOCK_CUENTA contendrá un registro por cada cuenta que pueda realizar el seguimiento de stocks. La tabla STOCK_TRX contendrá un registro por cada stock vendido o comprado por el titular de la cuenta. CONNECT Dora/contraseña

/

CREATE TABLE STOCK_CUENTA (

Cuenta NUMBER(10),

NombreCuenta VARCHAR2(50)

);

/

INSERT INTO STOCK_CUENTA VALUES (1234, 'Ana');

INSERT INTO STOCK_CUENTA VALUES (7777, 'Bob');

/

CREATE TABLE STOCK_TRX (

Cuenta NUMBER(10),

Simbolo VARCHAR2(20),

Precio NUMBER(6,2),

Cantidad NUMBER(6),

Trx_Flag VARCHAR2(1) /* C (comprada) o V (vendida) */

);

/

INSERT INTO STOCK_TRX VALUES (1234, 'ADSP', 31.75, 100, 'C');

INSERT INTO STOCK_TRX VALUES (7777, 'ADSP', 31.50, 300, 'V');

INSERT INTO STOCK_TRX VALUES (1234, 'ADSP', 31.55, 100, 'C');

Page 183: Java y Oracle 11g

Oracle /183

INSERT INTO STOCK_TRX VALUES (7777, 'OCKS', 21.75, 1000, 'C');

COMMIT;

Una vez creadas estas tablas concedemos permisos de acceso a Ana y Bob: GRANT SELECT, INSERT ON STOCK_TRX TO Ana, Bob;

GRANT SELECT ON STOCK_CUENTA TO Ana, Bob;

Ahora ya tenemos la aplicación configurada (en el esquema Dora, propietario de las tablas). Con esta configuración crearemos primero un contexto de aplicación, tal como se muestra en la siguiente sección. 4.1.2. Creación de un contexto de aplicación. En siguiente paso en la configuración VPD es crear un contexto de aplicación. El propósito de este contexto es definir las reglas que serán usadas por cada usuario. El contexto será parte de cada característica de sesión. Primero se usa el comando CREATE CONTEXT para especificar el nombre del paquete que será usado para asignar las reglas: CONNECT Dora/contraseña

CREATE CONTEXT Dora USING Dora.CONTEXT_PACKAGE;

Nota. Los contextos de aplicación se crea a nivel de la base de datos, por tanto no pueden existir dos contextos con el mismo nombre en una base de datos.

Ahora se crea el paquete con el nombre indicado (CONTEXT_PACKAGE). Este paquete consiste de un procedimiento: CREATE OR REPLACE PACKAGE CONTEXT_PACKAGE AS

PROCEDURE SET_CONTEXT;

END;

/

CREATE OR REPLACE PACKAGE BODY CONTEXT_PACKAGE IS

PROCEDURE SET_CONTEXT

IS

v_user VARCHAR2(30);

v_id NUMBER;

BEGIN

DBMS_SESSION.SET_CONTEXT('Dora','SETUP','TRUE');

v_user := SYS_CONTEXT('USERENV','SESSION_USER');

BEGIN

SELECT Cuenta INTO v_id FROM STOCK_CUENTA WHERE NombreCuenta = v_user;

DBMS_SESSION.SET_CONTEXT('Dora','USER_ID', v_id);

EXCEPTION

WHEN NO_DATA_FOUND THEN

DBMS_SESSION.SET_CONTEXT('Dora','USER_ID', 0);

END;

DBMS_SESSION.SET_CONTEXT('Dora','SETUP','FALSE');

END SET_CONTEXT;

END CONTEXT_PACKAGE;

El corazón del cuerpo de este paquete es una sencilla consulta: SELECT Cuenta INTO v_id FROM STOCK_CUENTA WHERE NombreCuenta = v_user;

La tabla STOCK_CUENTA contiene una registro por cada usuario (Ana, Bob, y demás). Esta consulta toma el nombre del usuario que se ha registrado y recupera el valor de su número de cuenta. Si el usuario no aparece en la tabla se lanza una excepción y se toma el valor 0. El valor de número de cuenta es asignado a una variable de sesión denominada USER_ID: DBMS_SESSION.SET_CONTEXT('Dora','USER_ID', v_id);

Podremos referenciar este valor de contexto en nuestros programas, y las restricciones VPD estarán basadas en este valor de contexto. Una vez creado el paquete, concedemos acceso público al mismo: GRANT EXECUTE ON Dora.CONTEXT_PACKAGE TO PUBLIC;

/

CREATE PUBLIC SYNONYM CONTEXT_PACKAGE FOR Dora.CONTEXT_PACKAGE;

Las reglas para establecer el contexto de sesiones están ahora listas para ser activadas, tal como se describe en la siguiente sección.

Page 184: Java y Oracle 11g

Oracle /184

4.1.3. Creación de un trigger de registro. Para poder asignar el contexto dentro de la sesión de cada usuario, debemos crear un trigger que será ejecutado cada vez un usuario se registre en la base de datos. CREATE OR REPLACE TRIGGER Dora.SET_SECURITY_CONTEXT

AFTER LOGON ON DATABASE

BEGIN

Dora.CONTEXT_PACKAGE.SET_CONTEXT;

END;

Cada vez que un usuario se registre, Oracle ejecutará el procedimiento SET_CONTEXT dentro del paquete CONTEXT_PACKAGE del usuario Dora. Este procedimiento consultará la tabla STOCK_CUENTA y verá si existe el usuario; si es así, el contexto de sesión será modificado para reflejar el valor de su número de cuenta. Podemos probar que la configuración establecida funciona bien consultando la función SYS_CONTEXT, tal como se muestra a continuación: CONNECT Ana/contraseña

/

SELECT SYS_CONTEXT('USERENV', 'SESSION_USER') USUARIO,

SYS_CONTEXT('Dora', 'USER_ID') "NÚMERO CUENTA"

FROM DUAL;

El resultado de esta consulta debería ser el siguiente: USUARIO NÚMERO CUENTA

-------------- -----------------------

ANA 1234

Con esta información podemos restringir el acceso a los registros en tablas basadas en este número de cuenta. VPD permite restringir este acceso automáticamente sin necesidad de crear vistas. Cada vez que un usuario intente acceder a una de estas tablas, el acceso será restringido independientemente de la herramienta usada para el acceso. Si se registra un usuario no incluido en STOCK_CUENTA el número de cuenta será asignado a cero. 4.1.4. Creación de políticas de seguridad. Una vez creado un trigger de registro, para que el paquete de contexto tenga un efecto sobre la interacción de los usuarios con la base de datos, necesitamos crear un paquete de seguridad que actúe sobre la salida del paquete de contexto. Primero se define la cabecera del paquete. En este ejemplo hay dos tipos de acciones soportadas para la tabla STOCK_TRX (inserciones y selecciones), así que crearemos dos funciones dentro del paquete: CONNECT Dora/contraseña

CREATE OR REPLACE PACKAGE SECURITY_PACKAGE AS

FUNCTION STOCK_TRX_INSERT_SECURITY(Propietario VARCHAR2, NombreObjeto VARCHAR2)

RETURN VARCHAR2;

FUNCTION STOCK_TRX_SELECT_SECURITY(Propietario VARCHAR2, NombreObjeto VARCHAR2)

RETURN VARCHAR2;

END SECURITY_PACKAGE;

Ahora se crea el cuerpo del paquete. Ambas funciones tiene el mismo patrón, dirigido a generar un predicado que será aplicado cada vez que un usuario consulte la tabla STOCK_TRX. Si el nombre de usuario de sesión es el propietario de la tabla, entonces el predicado será NULL. Para otros usuarios, se evalúa la variable de contexto con el número de cuenta, y se pasa una condición de limitación a cualquier consulta. El objeto de estas funciones es generar un predicado como «WHERE Cuenta=1234» que será aplicado cada vez que la tabla es consultada. CREATE OR REPLACE PACKAGE BODY SECURITY_PACKAGE IS

FUNCTION STOCK_TRX_SELECT_SECURITY(Propietario VARCHAR2, NombreObjeto VARCHAR2)

RETURN VARCHAR2

IS

Predicado VARCHAR2(2000);

BEGIN

IF (SYS_CONTEXT('USERENV','SESSION_USER') = 'Dora') THEN

Predicado := NULL;

ELSE

Predicado := 'Cuenta = SYS_CONTEXT(''Dora'',''USER_ID'')';

END IF;

Page 185: Java y Oracle 11g

Oracle /185

RETURN Predicado;

END STOCK_TRX_SELECT_SECURITY;

FUNCTION STOCK_TRX_INSERT_SECURITY(Propietario VARCHAR2, NombreObjeto VARCHAR2)

RETURN VARCHAR2

IS

Predicado VARCHAR2(2000);

BEGIN

IF (SYS_CONTEXT('USERENV','SESSION_USER') = 'Dora') THEN

Predicado := NULL;

ELSE

Predicado := 'Cuenta = SYS_CONTEXT(''Dora'',''USER_ID'')';

END IF;

RETURN Predicado;

END STOCK_TRX_INSERT_SECURITY;

END SECURITY_PACKAGE;

Ya que este paquete será ejecutado por todos los usuario que tengan acceso a la tabla STOCK_TRX, se concede el permiso de ejecutarlo, y se crea un sinónimo público. GRANT EXECUTE ON Dora.SECURITY_PACKAGE TO PUBLIC;

/

CREATE PUBLIC SYNONYM SECURITY_PACKAGE FOR Dora.SECURITY_PACKAGE;

4.1.5. Aplicación de la política de seguridad a las tablas. Ahora que ya existe el paquete de seguridad, podemos relacionarlo con las tablas. Para añadir una política a una tabla se usa el procedimiento ADD_POLICY del paquete SYS.DBMS_RLS, tal como se muestra a continuación: BEGIN

SYS.DBMS_RLS.ADD_POLICY('Dora', 'STOCK_TRX', 'STOCK_TRX_INSERT_POLICY',

'Dora', 'SECURITY_PACKAGE.STOCK_TRX_INSERT_SECURITY', 'INSERT', TRUE);

SYS.DBMS_RLS.ADD_POLICY('Dora', 'STOCK_TRX', 'STOCK_TRX_SELECT_POLICY',

'Dora', 'SECURITY_PACKAGE.STOCK_TRX_SELECT_SECURITY', 'SELECT');

END;

El procedimiento ADD_POLICY es ejecutado dos veces. En la primera ejecución, la función STOCK_TRX_INSERT_SECURITY es especificada para ejecutarse durante el intento de insertar registros en la tabla STOCK_TRX. En la segunda ejecución, la función STOCK_TRX_SELECT_SECURITY es aplicada para consultar la tabla STOCK_TRX. Podemos tener distintas reglas para asegurar diferentes operaciones sobre la misma tabla. Las variables pasadas al procedimiento ADD_POLICY son el nombre de esquema, el nombre de la tabla, un nombre para la política, el nombre del esquema y nombre de la función de seguridad, y la operación afectada. 4.1.6. Probando VPD. Para ver los resultados de la política de seguridad, consultaremos la tabla. La tabla STOCK_TRX tiene cuatro registros, dos para la cuenta Ana (1234) y dos para la cuenta Bob (7777). Registramos uno de estos usuarios y consultamos la tabla: CONNECT Ana/contraseña

/

SELECT * FROM Dora.STOCK_TRX;

El resultado será: CUENTA SIMBOLO PRECIO CANTIDAD TRX_FLAG

-------------- -------------- -------------- -------------- --------------

1234 ADSP 31,75 100 C

1234 ADSP 31,55 100 C

Aunque el usuario Ana no proporciona una cláusula WHERE en su consulta, Oracle aplica un predicado a esta consulta, limitando el resultado a las filas de la cuenta con valor 1234. Si un usuario no incluido en la tabla STOCK_CUENTA intenta hacer una consulta sobre la tabla STOCK_TRX no recibirá ningún registro como resultado. Esto es así porque Oracle aplicará un predicado limitando los resultados a las filas con la cuenta de valor 0. Además de las selecciones, las inserciones serán limitadas por la política de seguridad; un usuario sólo podrá insertar filas dentro de la tabla STOCK_TRX si el valor de cuenta coincide con el contexto de usuario.

Page 186: Java y Oracle 11g

Oracle /186

4.2. Cómo implementar VPD a nivel de columna.

Desde Oracle Database 10g, podemos implementar VPD al nivel de columna. Si una columna con datos sensibles es referenciada en una consulta, podemos aplicar una política de seguridad o mostrar la columna con valores a NULL. En el VPD a nivel de columna se pueden mostrar todos los registros pero la columna puede ser escudada para el usuario, por tanto es un mecanismo independiente del VPD a nivel de registro. Podemos aplicar VPD a nivel de columna a una tabla o una vista. Para usar VPD a nivel de columna debemos especificar un nombre de columna en el parámetro de entrada sec_relevant_cols del procedimiento ADD_POLICY de SYS.DBMS_RLS. Si la política STOCK_TRX_SELECT_POLICY

no existe aún, podemos usar el siguiente comando: BEGIN

SYS.DBMS_RLS.ADD_POLICY (

object_schema=>'Dora',

object_name=>'STOCK_TRX',

policy_name=>'STOCK_TRX_SELECT_POLICY',

function_schema=>'Dora',

policy_function=>'SECURITY_PACKAGE.STOCK_TRX_SELECT_SECURITY',

sec_relevant_cols=>'Precio');

END;

Por defecto, los registros serán retornados según la función de política de seguridad cuando la columna Precio es referenciada por una consulta. Para usar la opción de máscara sobre la columna, hay que decirle a Oracle que retorne todos los registros asignando el parámetro sec_relevant_cols_opt a SYS.DBMS_RLS.ALL_ROWS, seguido del parámetro sec_relevant_cols. Cuando se usa esta versión de la política, todos los registros serán retornados por la consulta. Para las filas que el usuario no debería ser capaz de ver, la columna asegurada mostrará valores nulos. El valor será mostrado para los registros que el usuario pueda ver normalmente. La máscara de columna se aplica sólo sobre consultas, no sobre operaciones DML.

4.3. Cómo desactivar VPD.

Para quitar la funcionalidad VPD hay que dar marcha atrás a los pasos mostrados en este capítulo. Quitar la política de la tabla (a través del procedimiento SYS.DBMS_RLS.DROP_POLICY), eliminar el trigger de registro, y opcionalmente eliminar los otros paquetes. Para eliminar una política se ejecuta el procedimiento DROP_POLICY. Tiene tres parámetros para el nombre del esquema, el nombre del objeto, y el nombre de la política. El siguiente ejemplo elimina la política STOCK_TRX_INSERT_POLICY: SYS.DBMS_RLS.DROP_POLICY('Dora', 'STOCK_TRX', 'STOCK_TRX_INSERT_POLICY');

De forma similar, podemos eliminar la política STOCK_TRX_SELECT_POLICY: SYS.DBMS_RLS.DROP_POLICY('Dora', 'STOCK_TRX', 'STOCK_TRX_SELECT_POLICY');

En este momento, las políticas son eliminadas, pero el trigger de registro seguirá ejecutándose durante cada registro de usuario. Podemos usar el comando DROP TRIGGER para eliminarlo, para que la base de datos no tenga que realizar trabajos innecesarios.

4.4. Contenido del paquete «SYS.DBMS_RLS».

El siguiente listado describe los procedimientos incluidos en el paquete SYS.DBMS_RLS.

Procedimiento Propósito

ADD_POLICY Añade una política a una tabla, vista o sinónimo.

DROP_POLICY Elimina una política de una tabla, vista o sinónimo.

REFRESH_POLICY Invalidad los cursores asociados con políticas no estáticas.

ENABLE_POLICY Habilita (o deshabilita) una política previamente añadida a una tabla, vista o sinónimo.

CREATE_POLICY_GROUP Crea una política de grupo.

ADD_GROUPED_POLICY Añade una política al grupo de políticas especificado.

ADD_POLICY_CONTEXT Añade el contexto para la aplicación activa.

DELETE_POLICY_GROUP Elimina un grupo de políticas.

DROP_GROUPED_POLICY Elimina una política que es miembro del grupo especificado.

DROP_POLICY_CONTEXT Elimina el contexto de la aplicación.

ENABLE_GROUPED_POLICY Habilita una política dentro de un grupo.

DISABLE_GROUPED_POLICY Deshabilita una política dentro de un grupo.

Page 187: Java y Oracle 11g

Oracle /187

REFRESH_GROUPED_POLICY Recompila el comando SQL asociado con una política refrescada.

4.5. Cómo usar grupos de políticas.

Podemos crear grupos de políticas, añadir políticas a un grupo, y habilitar políticas dentro de un grupo. Podemos crear grupos que integren políticas que afecten a las mismas tablas. Si varias aplicaciones usan las mismas tablas, podemos usar grupos para gestionar las políticas al nivel de tabla que deberían habilitarse durante el uso de la aplicación. Todas las políticas en el grupo pueden aplicarse en tiempo de ejecución. Por defecto, todas las políticas pertenecen al grupo SYS_DEFAULT. Las políticas del grupo SYS_DEFAULT serán siempre ejecutadas dentro del grupo especificado en el contexto dado. No podemos eliminar el grupo SYS_DEFAULT. Para añadir un nuevo grupo de políticas se usa el procedimiento CREATE_POLICY_GROUP. Su sintaxis es: SYS.DBMS_RLS.CREATE_POLICY_GROUP (

object_schema VARCHAR2,

object_name VARCHAR2,

policy_group VARCHAR2);

Podemos entonces añadir una política al grupo mediante el procedimiento ADD_GROUPED_POLICY. Su sintaxis es: SYS.DBMS_RLS.ADD_GROUPED_POLICY (

object_schema VARCHAR2,

object_name VARCHAR2,

policy_group VARCHAR2,

policy_name VARCHAR2,

function_schema VARCHAR2,

policy_function VARCHAR2,

statement_types VARCHAR2,

update_check BOOLEAN,

enabled BOOLEAN,

static_policy IN BOOLEAN FALSE,

policy_type IN BINARY_INTEGER NULL,

long_predicate IN BOOLEAN FALSE,

sec_relevant_cols IN VARCHAR2);

Por ejemplo, podemos crear un grupo llamado TRXAUDIT, y entonces añadir la política STOCK_TRX_SELECT_POLICY al grupo: BEGIN

SYS.DBMS_RLS.CREATE_POLICY_GROUP('Dora','STOCK_TRX','TRXAUDIT');

SYS.DBMS_RLS.ADD_GROUPED_POLICY('Dora','STOCK_TRX','TRXAUDIT',

'STOCK_TRX_SELECT_POLICY', 'Dora', 'SECURITY_PACKAGE.STOCK_TRX_SELECT_SECURITY');

END;

Cuando la base de datos es accedida, la aplicación inicializa el contexto conductor para especificar el grupo de políticas a usar. En el comando CREATE CONTEXT se especifica el nombre del procedimiento a usar (para los ejemplos previos, el contexto es asignado a través del paquete Dora.CONTEXT_PACKAGE). CREATE CONTEXT Dora USING Dora.CONTEXT_PACKAGE;

Dora.CONTEXT_PACKAGE ejecuta el procedimiento SET_CONTEXT para asignar el contexto de aplicación: DBMS_SESSION.SET_CONTEXT('Dora','SETUP','TRUE');

Para los grupos de políticas, el tercer parámetro pasado a SET_CONTEXT debe ser el nombre del grupo. Para la aplicación de ejemplo rescribiremos el paquete CONTEXT_PACKAGE para soportar un tercer valor de entrada (grupo de políticas) y añadiremos al procedimiento SET_CONTEXT: DBMS_SESSION.SET_CONTEXT('Dora','SETUP', policy_group);

Podemos eliminar una política del grupo mediante el procedimiento DROP_GROUPED_POLICY, desactivarla mediante DISABLE_GROUPED_POLICY, o volver a activarla mediante ENABLE_GROUPED_POLICY. Se usa DELETE_POLICY_GROUP para eliminar el grupo entero.

5. Trabajando con espacios de tabla.

En este capítulo veremos los fundamentos del uso de tablespaces, así como los comandos necesarios para crear y modificar tablespaces. Para estas dos operaciones es necesario tener permisos de administrador de base de datos.

Page 188: Java y Oracle 11g

Oracle /188

5.1. Tablespaces y la estructura de las bases de datos.

La gente que trabaja con ordenadores está familiarizada con el concepto de un fichero; es un lugar en el disco donde se almacena la información, y tiene un nombre. Su tamaño no es normalmente fijo. Si añadimos información a un fichero, éste puede aumentar ocupando más espacio en disco, hasta el máximo posible. Este proceso es gestionado por el sistema operativo, y normalmente involucra distribuir la información dentro del fichero sobre varias pequeñas secciones del disco que no necesariamente están cerca una de otras. El sistema operativo gestiona la conexión lógica entre estas pequeñas secciones sin que tengamos que preocuparnos de ello. Oracle usa ficheros como parte de su esquema organizativo, pero su estructura lógica va más allá del concepto de un fichero. Un fichero de datos (datafile) es un fichero del sistema operativo usado para guardar datos de Oracle. Cada fichero de datos se asigna a un tablespace (una división lógica dentro de la base de datos). Tablespaces incluidos normalmente en una base de datos son SYSTEM (para el diccionario de datos interno de Oracle), SYSAUX (para objetos internos auxiliares), USERS (para los objetos de usuario), y otros para las tablas de la aplicación, los índices y estructuras adicionales de la base de datos. Los ficheros de datos pueden tener un tamaño fijo o pueden asignarse para auto-extenderse cuando se llenen, hasta un límite definido. Para añadir más espacio a un tablespace podemos extender manualmente nuestros ficheros de datos o añadir nuevos ficheros de datos. Se añaden nuevos registros a tablas existentes, y estas tablas pueden tener registros en varios ficheros de datos. Cada tabla tiene una única área lógica de espacio en disco, llamada segmento, el cual pertenece a un único tablespace. Cada segmento, a su vez, tiene un área inicial de espacio en disco llamada extensión inicial. Una vez el segmento ha ocupado su espacio se extiende a otra área única de espacio en disco.

5.1.1. Estructuras de Oracle. La estructura lógica de Oracle está formada por:

Tablespaces Pertenecen sólo a una base de datos y sirven para agrupar los datos de la base de datos. Segmento Sirven para almacenar las estructuras lógicas de la base de datos (tablas, índices, etc.). Extensiones División que se hace a cada segmento. Bloque Oracle (O bloque de datos) Es la unidad mínima de datos para Oracle y se corresponde a una

o más unidades de datos mínimas del sistema operativo en el que nos encontremos. La estructura física está formada por:

Datafiles Son archivos en disco que sirven para almacenar los datos físicamente (en una unidad de disco). Cada archivo de datos pertenece sólo a un tablespace. Su tamaño se puede gestionar.

Bloques La división mínima de los datos que hace el sistema operativo. Concepto de tablespace. Una base de datos se divide en unidades lógicas denominadas tablespaces. Un tablespace no es un fichero físico en el disco, simplemente es el nombre que tiene un conjunto de propiedades de almacenamiento que se aplican a los objetos (tablas, índices, secuencias, etc.) que se crean en la base de datos bajo el tablespace

Page 189: Java y Oracle 11g

Oracle /189

indicado. Un objeto de una base de datos debe estar almacenado obligatoriamente dentro de un tablespace. Las propiedades que se asocian a un tablespace son:

• Localización de los ficheros de datos. • Especificación de las cuotas máximas de consumo de disco. • Control de la disponibilidad de los datos (en línea o fuera de línea). • Backup de datos.

Cuando un objeto se crea dentro de un cierto tablespace, este objeto adquiere todas las propiedades antes descritas del tablespace utilizado.

En el esquema precedente podemos ver que, por ejemplo, la tabla ARTICULO se almacena dentro del tablespace A, y que por lo tanto tendrá todas las propiedades del tablespace A, que pueden ser:

• Sus ficheros de datos están en $ORACLE_HOME/datos/datos_tablespace_A. • Los objetos no pueden ocupar más de 10Mb de espacio de base de datos. • En cualquier momento se puede poner fuera de línea todos los objetos de un cierto tablespace. • Se pueden hacer copiar de seguridad sólo de ciertos tablespaces.

Si nos fijamos, se puede apreciar que es posible tener una tabla en un tablespace, y los índices de esa tabla en otro tablespace. Esto es debido a que los índices no son más que objetos independientes dentro de la base de datos, como lo son las tablas. Y al ser objetos independientes, pueden estar en tablespaces independientes. En el esquema también vemos que hay un tablespace Temporal. Este tablespace representa las propiedades que tendrán los objetos que la base de datos cree temporalmente para sus cálculos internos (normalmente para ordenaciones y agrupaciones). El tablespace RO difiere de los demás en que es un tablespace de solo lectura (Read Only), y que por lo tanto todos los objetos en él contenidos pueden recibir órdenes de consulta de datos, pero no de modificación de datos. Estos tablespaces puede residir es soportes de sólo lectura, como pueden ser CDROM's, DVD's, etc. Cuando se crea un tablespace, éste se crea de lectura/escritura. Después se puede modificar para que sea de solo lectura. Un tablespace puede estar en línea o fuera de ella (Online u Offline), esto es, que el tablespace completo está a disposición de los usuarios o está desconectado para restringir su uso. Cualquier objeto almacenado dentro de un tablespace no podrá ser accedido si el tablespace está fuera de línea. Concepto de fichero de datos (datafile). Un datafile es la representación física de un tablespace. Son los "archivos de datos" donde se almacena la información físicamente. Un datafile puede tener cualquier nombre y extensión (siempre dentro de las limitaciones del sistema operativo), y puede estar localizado en cualquier directorio del disco duro, aunque su localización típica suele ser «CARPETA_DE_INSTALACIÓN_DE_ORACLE/Database». Un datafile tiene un tamaño predefinido en su creación (por ejemplo 100Mb) y éste puede ser alterado en cualquier momento. Cuando creamos un datafile, éste ocupa tanto espacio en disco como hayamos indicado en su creación, aunque internamente esté vacío. Oracle hace esto para direccionar espacio continuo en disco y evitar así la fragmentación. Conforme se vayan creando objetos en ese tablespace, se irá ocupando el espacio direccionado. Un datafile está asociado a un solo tablespace y un tablespace está asociado a uno o varios datafiles.

Page 190: Java y Oracle 11g

Oracle /190

En el esquema previo podemos ver como el Tablespace A está compuesto (físicamente) por tres datafiles (DATOS_1.ORA, DATOS_2.ORA y DATOS_3.ORA). Estos tres datafiles son los ficheros físicos que soportan los objetos contenidos dentro del tablespace A. Aunque siempre se dice que los objetos están dentro del tablespace, en realidad las tablas están dentro del datafile, pero tienen las propiedades asociadas al tablespace. Cada uno de los datafiles utilizados está ocupando su tamaño en disco (50 Mb los dos primeros y 25 Mb el último), aunque en realidad sólo contengan dos objetos y estos objetos no llenen el espacio que está asignado para los datafiles. Los datafiles tienen una propiedad llamada AUTOEXTEND, que se si está activa se encarga de que el datafile crezca automáticamente (según un tamaño indicado) cada vez que se necesite espacio y no exista. Al igual que los tablespaces, los datafiles también pueden estar en línea o fuera de ella. Concepto de Segmento. Un segmento es aquel espacio lógico direccionado por la base de datos dentro de un datafile para ser utilizado por un solo objeto. Así, una tabla (o cualquier otro objeto) está dentro de su segmento, y nunca podrá salir de él, ya que si la tabla crece, el segmento también crece. Físicamente, todo objeto en base de datos no es más que un segmento en un datafile. Se puede decir que un segmento es a un objeto de base de datos, como un datafile a un tablespace: el segmento es la representación física del objeto en la base de datos (el objeto no es más que una definición lógica).

Podemos ver como el espacio que realmente se ocupa dentro del datafile es el segmento y que cada segmento pertenece a un objeto. Existen tres tipos de segmentos (principalmente):

- Segmentos de tipo TABLE: aquellos que contienen tablas. - Segmentos de tipo INDEX: aquellos que contienen índices. - Segmentos de tipo ROLLBACK: aquellos se usan para almacenar información de la transacción activa.

Concepto de extensión. Para cualquier objeto de base de datos que tenga cierta ocupación en disco, es decir, cualquier objeto que tenga un segmento relacionado, existe el concepto de extensión. Extensión es un espacio de disco que se direcciona de una sola vez, un segmento que se direcciona en un momento determinado de tiempo. El concepto de extensión es un concepto físico, unas extensiones están separadas de otras dentro del disco. Ya dijimos que todo objeto tiene su segmento asociado, pero lo que no dijimos es que este segmento, a su vez, se compone de extensiones. Un segmento, puede ser direccionado de una sola vez (10 Mb de golpe), o de varias veces (5 Mb hoy y 5 Mb mañana). Cada uno de las veces que se direcciona espacio se denomina extensión.

Page 191: Java y Oracle 11g

Oracle /191

En el esquema vemos como el objeto (tabla) FACTURA tiene un segmento en el datafile A-1, y este segmento está compuesto de 3 extensiones. Una de estas extensiones tiene un color distinto. Esto es porque existen dos tipos de extensiones:

- INITIAL (extensiones iniciales): estas son las extensiones que se direccionan en el momento de la creación del objeto. Una vez que un objeto está creado, no se puede modificar su extensión inicial. - NEXT (siguientes o subsiguientes extensiones): toda extensión direccionada después de la creación del objeto. Si la INITIAL EXTENT de una tabla está llena y se está intentando insertar más filas, se intentará crear una NEXT EXTENT (siempre y cuando el datafile tenga espacio libre y tengamos cuota de ocupación suficiente).

Sabiendo que las extensiones se crean en momentos distintos de tiempo, es lógico pensar que unas extensiones pueden estar fragmentadas de otras. Un objeto de base de datos no reside todo junto dentro del bloque, sino que residirá en tantos bloque como extensiones tenga. Por eso es crítico definir un buen tamaño de extensión inicial, ya que, si es lo suficientemente grande, el objeto nunca estará desfragmentado. Si el objeto tiene muchas extensiones y éstas están muy separadas en disco, las consultas pueden retardarse considerablemente, ya que las cabezas lectoras tienes que dar saltos constantemente. El tamaño de las extensiones (tanto las INITIAL como las NEXT), se definen durante la creación del objeto y no puede ser modificado después de la creación. Oracle recomienda que el tamaño del INITIAL EXTENT sea igual al tamaño del NEXT EXTENT. Concepto de bloque de datos. Un bloque de datos es el último eslabón dentro de la cadena de almacenamiento. El concepto de bloque de datos es un concepto físico, ya que representa la mínima unidad de almacenamiento que es capaz de manejar Oracle. Igual que la mínima unidad de almacenamiento de un disco duro es la unidad de asignación, la mínima unidad de almacenamiento de Oracle es el bloque de datos. En un disco duro no es posible que un fichero pequeño ocupe menos de lo que indique la unidad de asignación, así si la unidad de asignación es de 4 Kb, un fichero que ocupe 1 Kb, en realidad ocupa 4 Kb. Siguiendo con la cadena, cada segmento (o cada extensión) se almacena en uno o varios bloques de datos, dependiendo del tamaño definido para la extensión, y del tamaño definido para el bloque de datos. El tamaño de las unidades de asignación del SO se define durante el particionado del disco duro (mediante las herramientas FDISK, FIPS, etic.), y el espacio de los bloques de datos de Oracle se define durante la instalación y no puede ser cambiado. Como es lógico, el tamaño de un bloque de datos tiene que ser múltiplo del tamaño de una unidad de asignación, es decir, si cada unidad de asignación ocupa 4 K, los bloques de datos pueden ser de 4K, 8K, 12K… para que en el SO ocupen 1, 2, 3… unidades de asignación. 5.1.2. Contenido de los tablespace. Podemos consultar la vista del diccionario de datos USER_TABLESPACES para ver los tablespaces disponibles en la base de datos. Los tablespace son creados por usuarios con permisos mediante el comando CREATE

TABLESPACE. La sintaxis más simple de este comando es: CREATE TABLESPACE nombre_tablespace DATAFILE nombre_datafile tamaño_fichero ;

Los administradores de base de datos especifican el método de asignación de espacio del tablespace cuando lo crean. El siguiente ejemplo crea un tablespace donde el espacio inicial es de 100MB, pero este fichero puede extenderse automáticamente si es necesario: CREATE TABLESPACE Users_2

DATAFILE '/U01/ORADATA/Users_2_01.dbf'

SIZE 100M AUTOEXTEND ON;

Podemos especificar también más de un fichero de datos en la creación del tablespace. Por ejemplo: CREATE TABLESPACE Users_3

Page 192: Java y Oracle 11g

Oracle /192

DATAFILE '/U01/ORADATA/Users_3_01.dbf' SIZE 100M,

'/U01/ORADATA/Users_3_02.dbf' SIZE 250M;

Al crearse el tablespace también se crean los ficheros de datos especificados. La columna Contents de la vista USER_TABLESPACES muestra el tipo de los objetos soportados en cada tablespace. Lo siguiente muestra un listado de ejemplo para una instalación en Oracle Database 11g: SELECT TABLESPACE_NAME, CONTENTS FROM USER_TABLESPACES;

TABLESPACE_NAME CONTENTS

-------------------------- ------------------

SYSTEM PERMANENT

UNDOTBS1 UNDO

SYSAUX PERMANENT

TEMP TEMPORARY

USERS PERMANENT

En este ejemplo, los tablespace SYSTEM, SYSAUX y USERS soportan objetos permanentes (tablas, índices y otros objetos de usuario). El tablespace TEMP soporta sólo segmentos temporales (segmentos creados y gestionados por Oracle para soportar operaciones de ordenación). El tablespace UNDOTBS1 soporta administración de segmentos de deshacer. Cuando creamos una tabla sin especificar un tablespace, la tabla será almacenada en nuestro tablespace por defecto. Podemos ver el tablespace por defecto mediante la vista del diccionario de datos USER_USERS: SELECT DEFAULT_TABLESPACE, TEMPORARY_TABLESPACE FROM USER_USERS;

DEFAULT_TABLESPACE TEMPORARY_TABLESPACE

----------------------------- ---------------------------------

USERS TEMP

En este ejemplo, el tablespace por defecto es USERS y el tablespace temporal es TEMP. Estas asignaciones pueden ser modificadas mediante el comando ALTER USER. Aunque USERS es nuestro tablespace por defecto, podemos no tener ninguna cuota en el tablespace. Para ver cuánto espacio tenemos concedido en un tablespace podemos consultar la vista USER_TS_QUOTAS, tal como se muestra a continuación: SELECT * FROM USER_TS_QUOTAS;

TABLESPACE_NAME BYTES MAX_BYTES BLOCKS MAX_BLOCKS

-------------------------- ----------- ---------------- ----------- ------------------

USERS 131072 -1 16 -1

En este caso, los valores MAX_BYTES y MAX_BLOCKS (el espacio máximo que podemos usar) está asignados a –1. Si este valor es negativo quiere decir que no tenemos límites en la cuota del tablespace. En este ejemplo, el usuario sólo tiene 16 bloques en 131.072 bytes. Ya que un 1KB son 1.024 bytes, el usuario tiene 128KB asignados en el tablespace USERS. El espacio en los tablespace puede gestionarse localmente. Y como alternativa, el espacio puede ser gestionado por diccionario (los registros de gestión de espacio son mantenidos en el diccionario de datos). En general, la gestión local de tablespaces es sencilla de administrar. Podemos mostrar el tipo de gestión mediante la columna Extent_Management de la vista USER_TABLESPACES: SELECT TABLESPACE_NAME, EXTENT_MANAGEMENT FROM USER_TABLESPACES;

TABLESPACE_NAME EXTENT_MAN

--------------------------- ---------------------

SYSTEM LOCAL

UNDOTBS1 LOCAL

SYSAUX LOCAL

TEMP LOCAL

USERS LOCAL

En un tablespace gestionado localmente, el mapa de espacio para el tablespace se mantiene en las cabeceras de los ficheros de datos del tablespace. Para ver los ficheros de datos asignados al tablespace, el administrador de la base de datos puede consultar la vista DBA_DATA_FILES del diccionario de datos; no hay una vista equivalente para usuarios sin permisos. Los valores por defecto para almacenar son específicos del sistema operativo. Podemos consultar la vista USER_TABLESPACES para ver el tamaño de bloque y los tamaños por defecto para la extensión inicial, el

Page 193: Java y Oracle 11g

Oracle /193

tamaño de la siguiente extensión, el número mínimo de extensiones, el número máximo número de extensiones y el incremento para los objetos. SELECT Tablespace_Name, Block_Size, Initial_Extent, Next_Extent,

Min_Extents, Max_Extents, Pct_Increase

FROM USER_TABLESPACES;

Para sobrescribir los valores por defecto podemos usar la cláusula STORAGE cuando creamos una tabla o índice. El siguiente ejemplo aplica todas las opciones de almacenamiento al crear una tabla. CREATE TABLE Test1 (...)

TABLESPACE Users_2

STORAGE ( INITIAL 20K NEXT 30K MINEXTENTS 1 MAXEXTENTS 10

PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1

OPTIMAL 7K BUFFER_POOL DEFAULT);

A menos que haya objetos con requerimientos de espacio extraordinarios, deberíamos evitar tamaños personalizados para cada una de nuestras tablas o índices. Si no especificamos una cláusula STORAGE cuando creamos un objeto, se usarán los valores por defecto de su tablespace. Si no especificamos una cláusula TABLESPACE, el objeto será almacenado en el tablespace por defecto. Podemos consultar el espacio libre en un tablespace mediante la vista USER_FREE_SPACE del diccionario de datos. USER_FREE_SPACE mostrará una fila por cada extensión libre dentro del tablespace, así como su exacta localización y longitud. 5.1.3. Espacio de «RECYCLEBIN» en los tablespaces. Desde Oracle Database 10g, los objetos borrados no liberan su espacio asignado a menos que especifiquemos la cláusula PURGE cuando los borramos con el comando DROP. Por defecto, los objetos mantienen su espacio, permitiendo que más tarde los recuperemos mediante el comando FLASHBACK TABLE TO BEFORE DROP. El espacio actualmente usado por los objetos borrados es registrado en cada vista RECYCLEBIN del usuario (o, para el DBA, la vista DBA_RECYCLEBIN). Podemos ver cómo se utilizan muchos bytes en cada tablespace, y entonces usar el comando PURGE para purgar las entradas antiguas desde el RECYCLEBIN. Aquí hay un ejemplo: SELECT SPACE --número de bloque todavía asignados

FROM RECYCLEBIN

WHERE TS_NAME = 'USERS';

5.1.4. Tablespaces de solo-lectura. Los DBA's pueden modificar un tablespace mediante el comando ALTER TABLESPACE. Los tablespaces se pueden modificar para ser de solo lectura: ALTER TABLESPACE USERS READ ONLY;

O pueden ser modificados para ser de solo escritura: ALTER TABLESPACE USERS READ WRITE;

Los datos de un tablespace de solo lectura no pueden ser modificados. Aunque los usuarios sin permisos no pueden ejecutar estos comandos, deberíamos ser conscientes de que ellos pueden cambiar cómo se almacenan físicamente nuestros datos. Si podemos mover los datos que no cambian a nuestro propio tablespace, podemos designar el tablespace entero como de sólo lectura. La utilización de tablespaces de sólo lectura simplifica el esfuerzo de copias de respaldo y recuperación.

Nota. Podemos borrar objetos en un tablespace de solo lectura.

El estado de sólo lectura de los tablespaces se muestra mediante la columna Status de la vista USER_TABLESPACES del diccionario de datos, tal como se muestra en el siguiente ejemplo: ALTER TABLESPACE USERS READ ONLY;

/

SELECT Status FROM USER_TABLESPACES WHERE Tablespace_Name = 'USERS';

STATUS

----------------

READ ONLY

ALTER TABLESPACE USERS READ WRITE;

/

SELECT Status FROM USER_TABLESPACES WHERE Tablespace_Name = 'USERS';

STATUS

Page 194: Java y Oracle 11g

Oracle /194

----------------

ONLINE

5.1.5. Tablespaces «nologging». Podemos desactivar la creación de entradas en el registro de deshacer para objetos específicos. Por defecto, Oracle genera entradas en el registro para todas las transacciones. Si queremos saltarnos esta funcionalidad (imaginemos que estamos cargando datos y podemos recrearlos completamente para todas las transacciones) podemos especificar que la carga de objetos o el tablespace se mantenga en modo "nologging". Podemos ver el estado de "logging" actual de un tablespace consultando la columna Logging de la vista USER_TABLESPACES. El siguiente ejemplo crea un tablespace en modo "logging" y despues lo modifica al modo "nologging": CREATE TABLESPACE TS1 DATAFILE 'C:/OraData/Users_01.dbf' SIZE 10M LOGGING;

/

ALTER TABLESPACE TS1 NOLOGGING;

5.1.6. Tablespaces temporales. Cuando ejecutamos un comando que realiza una operación de ordenación o agrupación, Oracle puede crear un segmento temporal para gestionar los datos. El segmento temporal es creado dentro de un tablespace temporal, y el usuario que ejecuta el comando no tiene que administrar estos datos. Oracle creará dinámicamente el segmento temporal y liberará su espacio cuando la instancia finalice o se reinicie. Si no hay espacio libre disponible y los ficheros de datos del tablespace temporal no puede auto-extenderse, el comando fallará. Cada usuario de la base de datos tiene asociado un tablespace temporal (puede haber un único tablespace compartido para todos los usuarios). Un tablespace temporal se asigna al nivel de base de datos, así que todos los nuevos usuarios tendrán el mismo tablespace temporal a no ser que se especifique uno diferente durante el comando CREATE USER o ALTER USER. Desde Oracle Database 10g, podemos crear varios tablespaces temporales y agruparlos. Se asignan tablespaces temporales a un grupo mediante la cláusula TABLESPACE GROUP del comando CREATE TEMPORARY

TABLESPACE o ALTER TABLESPACE. Podemos entonces especificar el grupo como tablespace por defecto de usuario. Los grupos de tablespaces pueden ayudarnos a soportar operaciones en paralelo para ordenaciones. El siguiente ejemplo crea dos tablespaces dentro del mismo grupo: CREATE TEMPORARY TABLESPACE TS_1

DATAFILE 'D:\TEMP\TEmp_01.dbf' SIZE 10M

TABLESPACE GROUP TSGrupo1;

/

CREATE TEMPORARY TABLESPACE TS_2

DATAFILE 'D:\TEMP\TEmp_02.dbf' SIZE 15M

TABLESPACE GROUP TSGrupo1;

5.1.7. Tablespaces para el sistema de deshacer. Podemos usar la gestión automática de deshacer (Automatic Undo Management, AUM) para poner todos los datos de deshacer dentro de un mismo tablespace. Cuando creamos una tablespace de deshacer, Oracle gestiona el almacenamiento, retención y utilización del espacio para que podamos recuperar los datos. Cuando se asigna un tiempo de retención (en el fichero de parámetros de inicialización de la base de datos), Oracle hará el mejor esfuerzo para retener todos los datos de deshacer confirmados en la base de datos durante el número de segundos especificados. Con esta asignación, cualquier consulta que tome menos tiempo que el tiempo de retención no debería causar un error si el tablespace de deshacer tiene el tamaño apropiado. Mientras la base de datos esta ejecutándose, los DBA's pueden cambiar el valor del parámetro UNDO_RETENTION mediante el comando ALTER SYSTEM: ALTER SYSTEM SET UNDO_RETENTION = 60;

Desde Oracle Database 10g, podemos garantizar que los datos de deshacer son retenidos, aún sobre el costo de las transacciones actuales en la base de datos. Cuando creamos el tablespace de deshacer debemos especificar RETENTION GUARANTEE como parte de nuestro comando CREATE DATABASE o CREATE UNDO

TABLESPACE. Debemos tener cuidado con esta opción, porque podemos forzar que las transacciones fallen para garantizar la retención de antiguos datos de deshacer en el tablespace de deshacer. 5.1.8. Soporte de «FLASHBACK DATABASE». Desde Oracle Database 10g, podemos usar el comando FLASHBACK DATABASE para revertir una base de datos entera a un punto anterior del tiempo. Los DBA's pueden configurar los tablespaces para excluirlos de esta opción (el comando ALTER TABLESPACE FLASHBACK OFF le dice a Oracle que excluya la escritura de los

Page 195: Java y Oracle 11g

Oracle /195

datos modificados después de una transacción al área de base de datos flashback). Por ejemplo podemos hacer esto con el tablespace USER_1: ALTER TABLESPACE USER_1 FLASHBACK OFF;

5.1.9. Tablespaces transportables. Un tablespace transportable puede ser "despegado" de una base de datos y "pegado" en otra base de datos. Para ser transportable, un tablespace (o un conjunto de tablespaces) debe ser auto-contenido. El conjunto de tablespaces no puede contener ningún objeto que referencie otros objetos en otros tablespaces. Por lo tanto, si transportamos un tablespace que contenga índices, debemos mover el tablespace que contiene las tablas bases de los índices como parte del mismo conjunto de tablespaces transportables. Lo mejor es tener organizados y distribuidos nuestros objetos en tablespaces, así será fácil generar un conjunto auto-contenido de tablespaces para transportar. Para transportar tablespaces necesitamos genera un conjunto de tablespaces, copiar y mover este conjunto a la nueva base de datos, y pegar el conjunto en la nueva base de datos. Ya que esto implica operaciones con permisos debemos ser administradores privilegiados para ejecutarlas. Por ejemplo, podemos crear y poblar un tablespace de sólo lectura con datos históricos en un entorno de prueba y luego transportarlo a una base de datos de producción, aún entre plataformas. Cómo generar un conjunto de tablespaces transportables. Un conjunto de tablespaces transportables contiene todos los ficheros de datos de los tablespaces que serán movidos, así como una exportación de los metadatos de estos tablespaces. Podemos opcionalmente elegir si incluiremos restricciones de integridad referencial como parte del conjunto transportable. Si elegimos esta opción el conjunto de tablespaces transportables se incrementará para incluir las tablas requeridas para mantener las claves relacionadas. La integridad referencial es opcional porque podemos tener las mismas tablas de código en varias bases de datos. Por ejemplo, podemos mover un tablespace desde una base de datos de prueba a una base de datos de producción. Si tenemos una tabla PAISES en la base de datos de prueba, entonces podemos tener también una tabla PAISES idéntica en la base de datos de producción. Si las tablas de códigos son idénticas en las dos bases de datos, no necesitamos transportar esta parte de las restricciones de integridad referencial. Podemos transportar el tablespace y entonces re-ensamblar la integridad referencial en la base de datos destino sobre el tablespace que ha sido movido, simplificando así la creación del conjunto transportable. Para determinar si un conjunto de tablespaces es auto-contenido, se puede ejecutar el procedimiento DBMS_TTS.TRANSPORT_SET_CHECK. Este procedimiento toma dos parámetros de entrada: el conjunto de tablespaces y un modificador booleano asignado a TRUE si queremos considerar la integridad referencial.

Nota. Solo los usuarios con el rol EXECUTE_CATALOG_ROLE pueden ejecutar este procedimiento. Este rol solo es asignado inicialmente al usuario SYS.

En el siguiente ejemplo, no se considera la integridad referencial para la combinación de los tablespaces AGG_DATA y AGG_INDEXES: EXECUTE DBMS_TTS.TRANSPORT_SET_CHECK('AGG_DATA,AGG_INDEXES', FALSE);

Nota. No podemos transportar los tablespaces SYSTEM, SYSAUX o los temporales.

Si hay cualquier violación de auto-contención en el conjunto especificado, Oracle poblará la vista TRANSPORT_SET_VIOLATIONS del diccionario de datos. Si no hay violaciones la vista estará vacía. Una vez seleccionado un conjunto auto-contenido, haremos los tablespaces de solo lectura, tal como se muestra a continuación: ALTER TABLESPACE AGG_DATA READ ONLY;

ALTER TABLESPACE AGG_INDEXES READ ONLY;

A continuación, exportamos los metadatos para los tablespaces, usando los parámetros de exportación TRANSPORT_TABLESPACE y TABLESPACES con la herramienta EXP: EXP USERID=usuarioPrivilegiado/contraseña TRANSPORT_TABLESPACE=Y

TABLESPACES=(AGG_DATA,AGG_INDEXES) CONSTRAINTS=N GRANTS=Y TRIGGERS=N

Nota. Para conectarnos como SYSDBA, podemos no especificar el usuario en la línea de comandos, y cuando se solicite el usuario debemos entrar «/ AS SYSDBA».

Como se muestra en este ejemplo, podemos especificar si los triggers, restricciones y permisos son exportados con los metadatos de los tablespaces. Ahora podemos copiar los ficheros de datos de los

Page 196: Java y Oracle 11g

Oracle /196

tablespaces a un área independiente. Si es necesario podemos reponer los tablespaces en el modo lectura-escritura en su base de datos actual. Después de haber generado el conjunto de tablespaces transportables, podemos mover sus ficheros (incluyendo la exportación) a un área a la cual pueda acceder la base de datos destino. Cómo pegar el conjunto de tablespaces transportable. Una vez que el conjunto transportable ha sido movido a un área accesible desde la base de datos destino, podemos pegar el conjunto dentro de la base de datos destino. Primero se usa la herramienta de importación IMP para importar los metadatos exportados: IMP TRANSPORT_TABLESPACE=Y DATAFILES=(AGG_DATA.DBF, AGG_INDEXES.DBF)

En la importación debemos especificar los ficheros de datos que son parte del conjunto transportable. Podemos opcionalmente especificar los tablespaces (mediante el parámetro TABLESPACES) y los propietarios de los objetos (mediante el parámetro OWNERS). Después de que la importación se complete, todos los tablespaces en el conjunto transportable son dejados en el modo de solo lectura. Podemos utilizar el comando ALTER TABLESPACE READ WRITE para cambiar este modo. ALTER TABLESPACE AGG_DATA READ WRITE;

ALTER TABLESPACE AGG_INDEXES READ WRITE;

Nótese que no podremos cambiar el propietario de los objetos transportados. Los tablespaces transportables soportan movimiento muy rápido de grandes conjuntos de datos.

Nota. Desde Oracle Database 10g, los sistemas operativos origen y destino pueden ser diferentes. Los ficheros de datos serán convertidos mediante el comando de RMAN CONVERT TABLESPACE…TO

PLATAFORM.

5.2. Planificando el uso de nuestro tablespace.

Con todas las opciones vistas previamente, Oracle puede soportar entornos muy complejos. Podemos mantener conjunto de solo lectura de tablas de datos históricas junto a tablas de transacciones activas. Podemos poner las tablas más usada en ficheros de datos localizados en los discos más rápidos. Podemos particionar tablas y almacenar cada partición en tablespaces distintos. Con todas estas opciones disponibles deberíamos establecer unas reglas básicas para nuestra arquitectura de tablespaces. Este plan debería ser parte de nuestro esfuerzo inicial de diseño y así aprovecharnos de las ventajas de estas funcionalidades disponibles. 5.2.1. Separar tablas de actividad y estáticas. Las tablas activamente usadas por transacciones tienen consideraciones de espacio que difieren significativamente de las tablas estadísticas de consulta. Las tablas estáticas puede que nunca sean modificadas o movidas; las tablas de actividad pueden necesitar ser administradas activamente, movidas o reorganizadas. Para simplificar la administración de las tablas estáticas podemos aislarlas en tablespaces dedicados. Dentro de las tablas más activas puede haber divisiones remotas (alguna de ellas puede ser extremadamente crítica para el rendimiento de la aplicación, y podemos decidir moverlas a otro tablespace). Llevando este acercamiento un poco más lejos, podemos separar las particiones activas y estáticas de tablas e índices. Idealmente, esta separación nos permitirá enfocar nuestros esfuerzos sobre los objetos que tengan un impacto más directo sobre el rendimiento mientras eliminamos el impacto de uso de otros objetos sobre el entorno inmediato. 5.2.2. Separar índices y tablas. Los índices pueden ser administrados separadamente de las tablas (podemos crear o borrar índices mientras la tabla base permanece sin cambiar). Debido a que sus espacios son administrados independientemente, los índices deben ser almacenados en tablespaces dedicados. Entonces deberíamos ser capaces de crear y reconstruir índices sin preocuparnos del impacto de estas operaciones sobre el espacio disponible en nuestras tablas. 5.2.3. Separar objetos grandes y pequeños. En general, las tablas pequeñas tienden a ser tablas estáticas de consulta (como una lista de países, por ejemplo). Oracle proporciona opciones para afinar tablas pequeñas (como una caché) que no son apropiadas para tablas grandes (las cuales tienen su propio conjunto de opciones). Ya que los administradores de estos tipos de tablas pueden ser muy diferentes, deberíamos intentar separarlas. En general, separar tablas activas y estáticas tendrá en cuenta también este objetivo. 5.2.4. Separar tablas de aplicación de objetos principales. Los dos conjuntos de objetos principales de los que tenemos que ser consciente son los objetos

Page 197: Java y Oracle 11g

Oracle /197

fundamentales de Oracle y los objetos del negocio. Los objetos fundamentales de Oracle se almacenan en los tablespaces por defecto (SYSTEM, SYSAUX, los tablespaces temporales y los tablespaces de deshacer). No debemos crear ningún objeto de la aplicación en estos tablespaces o bajo ningún esquema proporcionado con Oracle. Dentro de nuestra aplicación podemos tener algunos objetos que son fundamentales para el negocio y pueden ser reutilizados por varias aplicaciones. Ya que estos objetos pueden necesitar ser indexados y gestionados en cuentas para las necesidades de varias aplicaciones, deberían mantenerse aparte de otros objetos de nuestra aplicación. Agrupando los objetos en la base de datos de acuerdo a las categorías descritas aquí pueden verse muy simplista, pero es una parte crítica para el despliegue sucesivo de una aplicación de base de datos escalable. Lo mejor es planificar la distribución del espacio en discos, para que sea más sencilla la implementación, afinado y administración de las estructuras de base de datos. Además, los administradores de base de datos pueden gestionar los tablespaces separadamente (usándolos fuera de línea, respaldándolos, o aislándolos en sus actividades de entrada-salida).

6. Usar SQL*Loader para cargar datos

En vez de usar comandos INSERT para insertar datos en las tablas, podemos crear un fichero que contenga los datos y utilizar la utilidad SQL*Loader para cargar los datos. SQL*Loader carga datos desde archivos externos dentro de tablas de Oracle. SQL*Loader usa dos ficheros principales: un fichero de datos, el cual contiene la información de los datos a cargar, y un fichero de control, el cual contiene información del formato de los datos, los registros y campos dentro del fichero, el orden en el cual deben ser cargados, y si es necesario, los nombres de los diversos ficheros que serán usados para datos. Podemos combinar la información del fichero de control dentro del fichero de datos mismo, aunque normalmente son dos ficheros separados para hacer más fácil reutilizar el fichero de control. Cuando se ejecuta, SQL*Loader creará automáticamente un fichero de log y un fichero "malo" (bad). El fichero de log registra el estado de la carga, como el número de registros procesados y el número de registros aceptados. El fichero "malo" contendrá todos los registros que fueron rechazados durante la carga debido a errores de los datos, como valores repetidos en columnas de clave primaria. Dentro del fichero de control podemos especificar comandos adicionales para gobernar el criterio de carga. Si estos criterios no son cumplidos por una fila, la fila será escrita a un archivo de "descarte". Los ficheros de log, "malo" y de "descarte" tienen la extensión .log, .bad, y .dsc, respectivamente. Los ficheros de control tienen normalmente la extensión .ctl. SQL*Loader es una potente herramienta para cargar datos por varias razones:

• Es altamente flexible, permitiendo manipular los datos que están siendo cargados. • Podemos usar SQL*Loader para romper un único dato grande dentro de varios conjuntos de datos durante el proceso de validación, reduciendo así significativamente el tamaño del proceso de transacción. • Podemos usar la opción de carga Direct Path para realizar cargas muy rápidas.

6.1. El fichero de control.

El fichero de control le dice a Oracle cómo leer y cargar los datos. El fichero de control le dice a SQL*Loader dónde encontrar los datos de origen para la carga y las tablas dentro de las cuales cargar los datos, además de cualquier otra regla que debamos aplicar durante el proceso de carga. Estas reglas pueden incluir restricciones para descartar (similares a la cláusula WHERE de las consultas) e instrucciones para combinar varias filas físicas de un archivo de entrada en una única fila durante una inserción. SQL*Loader usará el fichero de control para crear los comandos de inserción ejecutados para cargar los datos. El fichero de control es creado al nivel de sistema operativo, usando un editor de texto que permita guardar archivos de texto plano. Dentro del fichero de control los comandos no tienen que tener ningún requerimiento de formato rígido, pero estandarizando la sintaxis de nuestros comandos permitirá más tarde el mantenimiento del fichero de control. El listado siguiente muestra un ejemplo de fichero de control para cargar datos dentro de una tabla LIBRO: LOAD DATA

INFILE 'libros.dat'

INTO TABLE LIBRO

(Codigo POSITION(01:03) INTEGER EXTERNAL,

Titulo POSITION(05:100) CHAR "LTRIM(RTRIM(:Titulo))",

Page 198: Java y Oracle 11g

Oracle /198

Autor POSITION(101:120) CHAR "LTRIM(RTRIM(:Autor))",

Categoria POSITION(121:140) CHAR "LTRIM(RTRIM(:Categoria))")

En este ejemplo, los datos son cargados desde un fichero llamado libros.dat dentro de la tabla LIBRO. El fichero libros.dat contiene los datos de las cuatro columnas de la tabla LIBRO distribuidos por líneas, con espacios en blanco necesarios para que cada columna comience en una posición determinada de cada línea. De esta forma, la columna Autor siempre comenzará en la posición 101 de cada línea, incluso si el valor de Titulo tiene menos de 100 caracteres. Aunque este formato provoca un archivo grande, puede simplificar el proceso de carga. No hay que dar ninguna longitud para los campos, la posición inicial y final de cada columna determina la longitud máxima de cada campo. La cláusula INFILE determina el nombre del fichero de datos, y la cláusula INTO TABLE especifica la tabla dentro de la cual se cargarán los datos. En este ejemplo, cada columna es listada teniendo en cuenta la posición donde residen los datos en cada registro físico del fichero. Este formato permite cargar datos aunque las columnas de datos del origen no casen en el orden de las columnas de la tabla. Además, en este ejemplo se normalizan los datos eliminando los espacios en blancos innecesarios (mediante las funciones LTRIM y RTRIM). Los tipos de datos que podemos especificar son: CHAR

VARCHAR

VARCHARC

DATE

EXTERNAL INTEGER

EXTERNAL FLOAT

EXTERNAL DECIMAL

EXTERNAL ZONED

Para realizar la carga, el usuario que la ejecute debe tener el permiso INSERT sobre la tabla LIBRO. 6.1.1. Cargando datos de longitud variable. Si las columnas de nuestro fichero de entrada tienen longitudes variables, podemos usar los comandos de SQL*Loader para decirle a Oracle cómo determinar cuándo acaba un valor. En el siguiente ejemplo se usan comas para separar los datos de entrada: LOAD DATA

INFILE 'libros.dat'

BADFILE '/user/load/libros.bad'

TRUNCATE

INTO TABLE LIBRO

FIELDS TERMINATED BY ","

(Codigo,Titulo, Editor, Categoria, Fecha)

O bien: LOAD DATA

INFILE 'libros.dat'

BADFILE '/user/load/libros.bad'

TRUNCATE

INTO TABLE LIBRO

(Codigo INTEGER EXTERNAL TERMINATED BY ",",

Titulo CHAR TERMINATED BY ",",

Editor CHAR TERMINATED BY ",",

Categoria CHAR TERMINATED BY ","

Fecha DATE "DD-MM-YYYY" )

Nota. Debemos asegurarnos de usar un delimitador que no forme parte de los valores que serán cargados. En este ejemplo se usa una coma, así que ninguno de los datos de libro debe incluir una coma, sino será interpretada como delimitador del valor.

En este ejemplo, la cláusula FIELDS TERMINATED BY le dice a SQL*Loader que, durante la carga, cada valor de columna terminará con una coma. Como alternativa podemos expecificar en cada campo el caracter terminador. De esta forma, cada línea de datos podrá tener una longitud distinta. En este ejemplo se ha proporcionado el nombre de un archivo "malo" mediante la cláusula BADFILE. En general, se especifica el nombre del archivo "malo" cuando requerimos redirigir el archivo a un directorio diferente.

Page 199: Java y Oracle 11g

Oracle /199

Este ejemplo también muestra el uso de la cláusula TRUNCATE. Cuando este fichero sea ejecutado por SQL*Loader, la tabla LIBRO será truncada antes del inicio de la carga. La orden TRUNCATE no puede ser anulada, así que deberemos usar con cuidado esta opción. Además de TRUNCATE, podemos usar las siguientes opciones:

• APPEND Añade filas a la tabla. • INSERT Añade filas a una tabla vacía. Si la tabla no está vacía, la carga se aborta con un error. • REPLACE Vacía la tabla y entonces añade las nuevas fila. El usuario debe tener el permiso DELETE sobre la tabla.

6.2. Comienzo de la carga.

Para ejecutar los comando del fichero de control necesitamos ejecutar SQL*Loader con los parámetros apropiados. SQL*Loader se inicia a través del comando SQLLDR en una consola del sistema operativo (en UNIX se usa sqlldr).

Nota. El ejecutable SQL*Loader puede consistir del nombre SQLLDR seguido de un número de versión. Hay que consultar la documentación específica de Oracle para el nombre exacto. Para Oracle Database 11g, el fichero ejecutable debería llamarse SQLLDR.

Cuando se ejecute SQLLDR necesitamos especificar el fichero de control, nombre usuario/contraseña, y otra información de carga, tal como se muestra en la siguiente tabla. Podemos separar los argumentos de SQLLDR con comas. Se escribe la palabra clave, un signo igual (=) y el argumento apropiado.

Palabra clave Descripción

Userid Nombre de usuario y contraseña para la carga, separados con una barra.

Control Nombre del fichero de control.

Log Nombre del fichero de log.

Bad Nombre del fichero "malo".

Discard Nombre del fichero de descartes.

Discardmax Número máximo de filas a descartar antes de parar de cargar. El valor por defecto es permitir todos los descartes.

Skip Número de filas lógicas en el fichero de entrada que serán saltadas antes de empezar a cargar datos. Normalmente se usa durante recargas. El valor por

defecto es cero. Se utiliza dentro del argumento OPTIONS:

OPTIONS(SKIP=1)

Load Número de filas lógicas a cargar. El valor por defecto es todas.

Errors Número de errores permitidos. El valor por defecto es 50.

Rows Número de filas que se confirmarán a la vez. Se usa este parámetro para romper el tamaño de transacción durante la carga. El valor por defecto para rutas convencionales es de 64; el valor por defecto para cargas Direct Path es todas las filas.

Bindsize Tamaño de cadenas de rutas enlazadas, en bytes. El valor por defecto depende del sistema operativo.

Silent Suprime mensajes durante la carga.

Direct Usa carga Direct Path. El valor por defecto es FALSE.

Parfile Nombre del fichero de parámetros que contiene especificaciones de parámetros de carga adicionales.

Parallel Activa la carga en paralelo. El valor por defecto es FALSE.

File Fichero para asignar extensiones desde (para carga paralela).

Skip_Unusable_Indexes Permite cargas dentro de tablas que tienen índices en estados no usables. El valor

por defecto es FALSE.

Skip_Index_Maintenance Detiene el mantenimiento de índice para cargas Direct Path, dejándolos en estados

no usables. El valor por defecto es FALSE.

Readsize Tamaño del búfer de lectura; el valor por defecto es de 1MB.

External_table Usa una tabla externa para la carga; el valor por defecto es NOT_USED; y otros

valores válidos son GENERATE_ONLY y EXECUTE.

Columnarrayrows Número de filas para cadenas de columnas Direct Path; el valor por defecto es de 5.000.

Streamsize Tamaño en bytes del búfer del canal Direct Path; por defecto es de 256000.

Multithreading Un indicador de que debería usarse multiproceso durante Direct Path.

Page 200: Java y Oracle 11g

Oracle /200

Resumable Un indicador TRUE/FALSE para habilitar o deshabilitar operaciones recuperables

en la sesión actual; el valor por defecto es FALSE.

Resumable_name Identificador de texto para la operación recuperable.

Resumable_timeout Tiempo de espera para operaciones recuperables; el valor por defecto es de 7200 segundos.

Si se omite la palabra clave userid y no se proporcionan credenciales en el primer argumento, se debe preguntar por ellas. Si se pone una barra después del signo igual, se usará una cuenta identificada externamente. También podemos usar una cadena de especificación de base de datos Oracle Net para registrarnos dentro de una base de datos remota. Por ejemplo, nuestro comando puede comenzar con sqlldr userid=usernm/mypass@dev

La palabra clave silent le dice a SQLLDR que suprima cierta información: • HEADER suprime la cabecera SQL*LOADER. • FEEDBACK suprime la regeneración en cada punto de confirmación. • ERRORS suprime el registro (en el archivo de log) de cada fila que provoque un error. • DISCARDS suprime el registro (en el archivo de log) de cada fila que sea descartada. • PARTITIONS desactiva la escritura de estadísticas por partición en el archivo de log. • ALL suprime todo lo precedente.

Si queremos incluir varias de estas opciones, debemos separarlas con comas dentro de paréntesis. Por ejemplo, podemos suprimir la cabecera y la información de errores de la siguiente manera: sqlldr . . . silent=(HEADER,ERRORS)

Nota. Los comandos del fichero de control sobrescriben cualquiera de las opciones explicitadas en la línea de comandos.

Como ejemplo cargaremos un simple conjunto de datos dentro de la tabla LIBRO, la cual tiene tres columnas (Titulo, Autor, Categoria). Se crea un fichero de texto plano llamado libros.txt. Los datos a cargar serán dos líneas del archivo:

Archivo «libros.txt»

Libro uno, autor uno, ADULTO

Libro dos, autor dos, JUVENIL

Cada línea en el archivo se separa con un retorno de carro. Dentro de cada línea los datos se han separado con comas. En este caso no queremos eliminar los datos previamente cargados en la tabla LIBRO, así que el fichero de control tendrá el siguiente contenido:

Archivo «libros.ctl»

LOAD DATA

INFILE 'libros.txt'

APPEND

INTO TABLE LIBRO

FIELDS TERMINATED BY ","

(Titulo, Autor, Categoria)

Se guarda el archivo como libros.ctl, en el mismo directorio que el fichero de datos. A continuación, se ejecuta SQLLDR diciéndole a Oracle que use el fichero de control. Este ejemplo asume que la tabla LIBRO existe dentro del esquema empleado: sqlldr empleado/contraseña control=libros.ctl log=libros.log

Registros lógicos y físicos. Varias de las opciones del comando SQLLDR se refieren a registros lógicos. Un registro lógico es un registro que se inserta dentro de la base de datos. Dependiendo de la estructura del fichero de entrada, varios registros físicos pueden ser combinados para crear un único registro lógico. Por ejemplo, el fichero de entrada puede contener: Un libro, Un autor, ADULTO

En este caso debería existir una relación uno-a-uno entre el registro físico y el registro lógico que es creado. Pero el fichero de datos podría también contener: Un libro,

Un autor,

ADULTO

Page 201: Java y Oracle 11g

Oracle /201

Para combinar estos datos necesitamos especificar reglas de continuación. En este caso, los valores de las columnas son distribuidos en líneas separadas, así que un conjunto de registros físicos se corresponden con un registro lógico. Para combinarlos se usa la cláusula CONCATENATE dentro del fichero de control. En este caso hay que especificar CONCATENATE 3 para crear un único registro lógico a partir de tres registros físicos. La lógica para crear un único registro lógico a partir de varios registros físicos puede ser mucho más compleja que una simple concatenación. Podemos usar la cláusula CONTINUEIF para especificar las condiciones que indiquen la continuación. Incluso podemos manipular los datos de entrada para crear varios registros lógicos de un único registro físico (a través de varias cláusulas INTO TABLE). Podemos usar SQL*Loader para generar varias inserciones desde un único registro físico. Por ejemplo, supongamos que los datos de entrada están desnormalizados, incluyendo valores para una ciudad y la cantidad de lluvia caída a lo largo el día, en el formato (Ciudad, Precipitacion1, Precipitacion2, Precipitacion3). El fichero de control debería re-ensamblar lo siguiente (se indica la posición de los datos): . . .

INTO TABLE PRECIPITACION

WHEN Ciudad != ''

(Ciudad POSITION(1:5) CHAR, Precipitacion POSITION(6:10) INTEGER EXTERNAL) -- 1ª Fila --

INTO TABLE PRECIPITACION

WHEN CITY != ''

(Ciudad POSITION(1:5) CHAR, Precipitacion POSITION(11:16) INTEGER EXTERNAL) -- 2ª Fila --

INTO TABLE PRECIPITACION

WHEN CITY != ''

(Ciudad POSITION(1:5) CHAR, Precipitacion POSITION(16:21) INTEGER EXTERNAL) -- 3ª Fila --

Nótese que cada cláusula INTO TABLE opera con cada fila física. En este ejemplo, se generan tres filas lógicas en la tabla PRECIPITACION por cada fila física. También podríamos haber insertado filas en varias tablas.

6.3. Sobre la sintaxis del fichero de control.

Dentro de la cláusula LOAD (que especifica el número de filas lógicas a cargar) podemos especificar si la carga es recuperable (RECOVERABLE) o irrecuperable (UNRECOVERABLE). La cláusula UNRECOVERABLE sólo se aplica en cargas Direct Path. Además de usar la cláusula CONCATENATE, podemos usar la cláusula CONTINUEIF para controlar la manera en la cual los registros físicos son ensamblados dentro de registro lógicos. La cláusula THIS referencia al registro físico actual, mientras que la cláusula NEXT referencia el siguiente registro físico. Por ejemplo, podemos crear un ítem de continuación de dos caracteres al inicio de cada registro físico. Si este registro debe ser concatenado al registro precedente, se asigna este valor iguala a '**'. Entonces se puede usar la cláusula CONTINUEIF NEXT(1:2)='**' para crear un único registro lógico. El ítem de continuación '**' no será parte del registro combinado. La sintaxis para la cláusula INTO TABLE incluye una cláusula WHEN. Este cláusula WHEN sirve como un filtro que se aplica a cada registro antes de su inserción. Por ejemplo, podemos especificar: WHEN Categoria='ADULTO'

Para cargar sólo aquellos libros con una categoría determinada. Cualquier fila que no pase la condición será escrita al fichero de descartes. La cláusula WHEN sólo admite expresiones con los comparadores =, <> y != (es decir, sólo pueden realizarse comparaciones por igualdad o desigualdad). Se pueden encadenar varias condiciones mediante el operador AND, pero no mediante el operador OR. Se usa la cláusula TRAILING NULLCOLS si estamos cargando registros de longitud variable para los cuales la última columna no siempre tiene un valor. Gracias a este cláusula, SQL*Loader generará valores nulos para estas columnas. Como se ha visto previamente, podemos usar la cláusula FIELDS TERMINATED BY para cargar datos de longitud variable. En vez de ser terminados por un caracter, los campos pueden ser terminados con espacios en blanco o encapsulados entre caracteres o opcionalmente encapsulados por otros caracteres. Por ejemplo, la siguiente entrada carga el campo Autor y asigna los valores en mayúsculas durante la inserción. Si el valor está en blanco, se inserta un valor nulo: Autor POSITION(10:34) CHAR TERMINATED BY WHITESPACE

NULLIF Autor=BLANKS "UPPER(:Autor)"

Cuando se cargan valores de tipo DATE, podemos especificar la máscara de formato. Por ejemplo, si tenemos una columna llamada Fecha y los datos están en el formato Mon-DD-YYYY en las primeras once posiciones de la fila, podemos especificar la carga de este campo de la siguiente manera:

Page 202: Java y Oracle 11g

Oracle /202

Fecha POSITION (1:11) DATE "Mon-DD-YYYY"

Dentro de la cláusula INTO TABLE podemos usar la palabra clave RECNUM para asignar un número de registro a cada registro lógico tal como es leído desde el fichero de datos, y este valor puede ser insertado dentro de una columna designada de la tabla. La palabra clave CONSTANT permite asignar un valor constante a una columna durante la carga. Para columnas de caracteres, se encierra el valor constante entre comillas simples. Si se usa la palabra clave SYSDATE, la columna seleccionada será poblada con la fecha actual del sistema. Por ejemplo: FechaInsercion SYSDATE

Si usamos la opción SEQUENCE, SQL*Loader mantendrá una secuencia de valores durante la carga. A medida que los registros son procesados, el valor de secuencia será incrementado por el incremento especificado. Si las filas fallan durante la inserción, los valores de secuencia no serán reutilizados. Si usamos la palabra clave MAX dentro de la opción SEQUENCE, los valores de secuencia usarán el valor siguiente al máximo actual de la columna como valor inicial para la secuencia. La siguiente línea muestra cómo usar la opción SEQUENCE: Columna_secuencial SEQUENCE(MAX,1)

También podemos especificar el valor de inicio y el de incremento para una secuencia, que serán usados cuando insertemos. El siguiente ejemplo inserta valores empezando con el valor 100 e incrementando de 2 en 2. Si una fila es rechazada durante la inserción, el valor de secuencia será saltado. Columna_secuencial SEQUENCE(100,2)

Si almacenamos números en columnas de tipo VARCHAR2, deberíamos evitar usar la opción SEQUENCE para estas columnas. Un problemas es, por ejemplo, si nuestra tabla ya contiene valores de 1 a 10 en una columna de tipo VARCHAR2; entonces el máximo valor dentro de esta columna será '9' (el valor string más grande) y no '10'. Los ficheros de control SQL*Loader pueden soportar lógica y reglas del negocio complejos. Por ejemplo, nuestros datos de entrada para columnas con valores monetarios pueden implicar decimales; 9990 debería ser insertado como 99,90. En SQL*Loader, podemos insertar esto realizando los cálculos durante la carga de datos: Cantidad_monetaria POSITION (20:28) EXTERNAL DECIMAL(9) ":cantidad/100"

6.4. Administración de la carga de datos.

Cargar grandes volúmenes de datos es una operación por lotes. Las operaciones por lotes no deberían realizarse concurrentemente con las transacciones pequeñas prevalentes en muchas aplicaciones de bases de datos. Si tenemos muchos usuarios concurrentes ejecutando pequeñas operaciones sobre una tabla, deberíamos planificar las operaciones por lotes sobre dicha tabla para que ocurran cuando pocos usuarios accedan a la tabla. Oracle mantiene lecturas consistentes en las consultas de usuarios. Si ejecutamos un trabajo de SQL*Loader sobre la tabla al mismo tiempo que otros usuarios están consultando la tabla, Oracle mantendrá internamente instantáneas de la tabla para que los usuarios vean los datos tal como estaban cuando realizaron la consulta. Para minimizar la cantidad de trabajo, Oracle debe funcionar de modo que mantenga lectura consistente (y reducir al mínimo la degradación causada por esta sobrecarga), y planifique los trabajos de carga de larga duración para que se realicen cuando hay pocas acciones ocurriendo en la base de datos. En particular, debe evitar los conflictos con otros accesos a la misma tabla. Si diseñamos nuestro proceso de carga de datos será más sencillo de mantener y reutilizar. Debemos establecer normas para la estructura y formato de los ficheros de datos de entrada. Estandarizando los formatos de datos de entrada será más simple reutilizar viejos archivos de control para las cargas de datos. Para recurrentes planificaciones de cargas en la misma tabla, nuestro objetivo debería ser el de reutilizar el mismo archivo de control cada vez. Después de cada carga, tendremos que repasar y mover los ficheros de log, filas erróneas, datos y filas descartadas para no realizar sobre-escrituras accidentales. Dentro del fichero de control se pueden usar comentarios para indicar funciones de proceso especiales que deban realizarse. Para crear un comentario dentro del fichero de control, se comienza la línea con dos guiones, tal como se muestra a continuación: -- Limitar la carga a empleados de LA:

WHEN Ubicacion='LA'

Si tenemos comentado apropiadamente nuestro fichero de control incrementaremos la capacidad de reutilizarlo durante futuras cargas. 6.4.1. Cargas de datos repetitivas. La carga de datos no siempre trabaja exactamente como se planificó. Están involucradas muchas variables en

Page 203: Java y Oracle 11g

Oracle /203

la carga de datos, y no todas ellas están siempre bajo nuestro control. Por ejemplo, el propietario de los datos de origen puede cambiar el formato de los datos, invalidando parte de nuestro fichero de control. Las reglas del negocio pueden cambiar, forzando cambios adicionales. Las estructuras de datos y el espacio disponible pueden cambiar, lo que afecta a la habilidad de cargar datos. En un caso ideal, una carga de datos se completará totalmente o fallará totalmente. Sin embargo, en muchos casos, una carga de datos ocurrirá parcialmente, haciendo el proceso de recuperación más dificultoso. Si alguno de estos registros ha sido insertado dentro de la tabla, entonces intentar reinsertarlos provocará una violación de clave primaria. Si estamos generando la clave primaria durante la inserción (a través de la opción SEQUENCE), entonces estos registros pueden no fallar la segunda vez (y por tanto serán insertados dos veces). Para determinar cuándo una carga falla se usa el fichero de log. El fichero de log registrará los puntos de confirmación así como los errores encontrados. Todos los registros rechazados deberían estar en el archivo "malo" o en el archivo de descartes. Podemos reducir al mínimo el esfuerzo de recuperación forzando que la carga falle si se encuentras muchos errores. Para forzar que la carga aborte antes de que se encuentren un gran número de errores se usan la palabra clave ERRORS del comando SQLLDR. También podemos usar la palabra clave DISCARDMAX para limitar el número de descartes permitidos antes de abortar la carga. Si se asigna ERRORS a 0, el primer error causará que la carga falle. ¿Qué pasa si la carga falla después de que 100 registros sean insertados? Tenemos dos opciones: identificar y borrar los registros insertados y reaplicar la carga entera, o saltarse lo registros insertados. Podemos usar la palabra clave SKIP de SQLLDR para saltarse los primeros 100 registros durante el proceso de carga. La carga continuará en el registro 101 (que habrá sido corregido). Si no podemos identificar las filas que han sido cargadas en la tabla, necesitaremos usar la opción SKIP durante el proceso de recarga. Las asignaciones apropiadas para ERRORS y DISCARDMAX dependen de la carga. Si tenemos control completo sobre el proceso de carga, y los datos son apropiadamente "limpiados" antes de ser extraídos a un fichero de carga, podemos tener muy poca tolerancia a errores y descartes. Por otra parte, si no tenemos control sobre el origen del fichero de datos de entrada, necesitaremos asignar ERRORS y DISCARDMAX a un valor alto para permitir que la carga se complete. Después de que la carga se haya completado necesitaremos revisar el fichero de log, corregir los datos del fichero "malo", y recargar los datos usando el fichero "malo" original como el nuevo fichero de entrada. Si los registros han sido incorrectamente descartados, necesitaremos hacer cargas adicionales usando el fichero de descartes original como el nuevo fichero de entrada.

6.5. Ajustar la carga de datos.

Además de ejecutar el proceso de carga de datos en momentos con poco tránsito, podemos hacer otras cosas para mejorar el rendimiento de la carga. Los siguientes pasos afectan al entorno de base de datos y deben ser coordinados con el administrador de base de datos. No debería permitirse afinar una carga de datos porque tiene un impacto negativo sobre la base de datos o sobre los procesos del negocio que soporta. Primero, la carga de datos en lotes puede ocurrir mientras la base de datos está en modo NOARCHIVELOG. Mientras está en modo NOARCHIVELOG, la base de datos no permite que uno de sus archivos genere ficheros de deshacer antes de modificarlo. Eliminando el proceso de archivado mejoramos el rendimiento de las transacciones. Ya que los datos están siendo cargados desde un archivo, podemos recrear los datos cargados más tarde recargando el fichero de datos en vez de recuperarlos desde un archivo de deshacer. Sin embargo, hay una característica potencialmente significativa cuando desactivamos el modo ARCHIVELOG. No permite realizar una recuperación en un punto dado del tiempo a menos que el archivado esté habilitado. Si realizamos transacciones que no sean por lotes en la base de datos, probablemente necesitaremos ejecutar la base de datos en modo ARCHIVELOG todas las veces, incluyendo durante nuestras cargas. Aún más, cambiar entre el modo ARCHIVELOG y NOARCHIVELOG requiere que cerremos la instancia de base de datos. Si cambiamos la instancia a modo NOARCHIVELOG, realizamos nuestra carga de datos, y entonces cambiamos la instancia a modo ARCHIVELOG, deberemos realizar una copia de respaldo de la base de datos inmediatamente después del reinicio. En vez de ejecutar toda la base de datos en modo NOARCHIVELOG, podemos desactivar el archivado para nuestro proceso de carga de datos usando la palabra clave UNRECOVERABLE con SQL*Loader. La opción UNRECOVERABLE desactiva la escritura de entradas en los ficheros de deshacer para las transacciones de la carga de datos. Deberíamos usar sólo esta opción si queremos recrear las transacciones desde los ficheros de entrada en el caso de que sea necesario para futuras recuperaciones. La opción UNRECOVERABLE está sólo disponible para cargas Direct Path. Más que controlar las actividades de deshacer al nivel del proceso de carga, podemos controlarlas al nivel de

Page 204: Java y Oracle 11g

Oracle /204

la tabla o partición. Si definimos un objeto como NOLOGGING, entonces inserciones al nivel de bloque realizadas por SQL*Loader Direct Path y el comando INSERT /*+ APPEND */ no generarán entradas de deshacer. Inserciones al nivel de bloque requieren espacio adicional, y no reutilizan bloques existentes bajo la marca de agua de la tabla. Si nuestro sistema operativo tiene varios procesadores, podemos tomar la ventaja de varios CPU's realizando una carga de datos paralela. La opción PARALLEL de SQLLDR usa procesos de carga de datos de concurrencia múltiple para reducir la sobrecarga del tiempo requerido para cargar los datos. Además de estas posibilidades, deberíamos trabajar con nuestro administrador de base de datos para hacer seguro el entorno de la base de datos y que las estructura son las apropiadas para cargar datos. El esfuerzo de ajuste debería incluir lo siguiente:

• Preasignar espacio para la tabla, para minimizar extensiones dinámicas durante las cargas. • Asignar suficientes recursos de memoria para las áreas de memoria compartida. • Canalizar los procesos de escritura de datos creando varios procesos de escritura de base de datos (DBWR). • Quitar cualquier trigger innecesario durante la carga de datos. Si es posible, desactivar o eliminar los triggers antes de la carga, y realizar las operaciones de los triggers sobre los datos cargados manualmente después de que hayan sido cargados. • Eliminar o desactivar cualquier restricción innecesaria sobre la tabla. Podemos usar SQL*Loader para desactivar y reactivar dinámicamente restricciones. • Eliminar índices sobre las tablas. Si los datos han sido apropiadamente limpiados antes de la carga, entonces validaciones de unicidad y clave foránea no serán necesarias durante la carga. Eliminar índices antes de la carga incrementa significativamente el rendimiento.

6.5.1. Cargas Direct Path. SQL*Loader genera un gran número de comandos de inserción. Para evitar la sobrecarga asociada con el uso de numerosas inserciones, podemos usar la opción Direct Path en SQL*Loader. La opción Direct Path crea bloques de datos preformateados e inserta estos bloques dentro de la tabla. Como resultado, el rendimiento de nuestra carga puede mejorarse dramáticamente. Para usar la opción Direct Path debemos no realizar ninguna función sobre los valores que están siendo leídos desde el fichero de entrada. Cualquier índice sobre la tabla que está siendo cargada será ubicado dentro de un estado temporal DIRECT

LOAD (se puede consultar el estado del índice desde USER_INDEXES). Oracle moverá los antiguos valores del índice a un índice temporal. Una vez que la carga se completa, los valores antiguos serán combinados con los nuevos valores para crear el nuevo índice, y Oracle eliminará el índice temporal. Cuando el índice es otra vez válido, su estado es cambiado a VALID. Para minimizar la cantidad de espacio necesario para el índice temporal podemos preordenar los datos por las columnas del índice. El nombre del índice para el cual los datos son preordenados debería especificarse a través de la cláusula SORTED INDEXES en el fichero de control. Para usar la opción Direct Path se especifica DIRECT=TRUE

en la línea del comando SQLLDR o se incluye esta opción en el fichero de control. Si usamos la opción Direct Path, podemos usar la palabra clave UNRECOVERABLE para incrementar el rendimiento de la carga de datos. Esto instruye a Oracle para que no genere entradas de deshacer durante la carga. Si necesitamos recuperar la base de datos en un punto posterior, necesitaremos reejecutar la carga de datos para recuperar los datos de la tabla. Todas las cargas convencionales son recuperables, y todas las cargas Direct Path son recuperables por defecto. Las cargas Direct Path son más rápidas que las cargas convencionales, y las cargas Direct Path no recuperables son más rápidas todavía. Ya que las cargas no recuperables impactan en nuestras operaciones de recuperación, necesitamos pesar el coste de este impacto frente a los beneficios de rendimiento. Si nuestro entorno de hardware tiene recursos adicionales disponibles durante la carga, podemos usar la opción de carga Direct Path paralela para dividir el trabajo de carga de datos en varios procesos. Las operaciones Direct Path paralelas pueden completar el trabajo de carga más rápido que una carga Direct Path simple. En vez de usar la opción PARALLEL, podemos particionar la tabla. Desde SQL*Loader podemos cargar una única partición, y podemos ejecutar varios trabajos de SQL*Loader simultáneamente para poblar las particiones de una tabla particionada. Este método requiere más trabajo de administración de base de datos (para configurar y manejar las particiones), pero da más flexibilidad en la paralelización y planificación de las tareas de carga.

Page 205: Java y Oracle 11g

Oracle /205

Podemos tomar las ventajas de las funcionalidades de carga multihilo para cargas Direct Path para convertir columnas de arrays a búferes de stream y realizar cargas mediante búferes de stream en paralelo. Se usa el parámetro STREAMSIZE y la bandera MULTITHREADING para habilitar esta característica. Las cargas Direct Path pueden impactar en el espacio requerido para los datos de la tabla. Ya que la carga Direct Path inserta bloques de datos, no sigue el método habitual para asignar espacio dentro de una tabla. Los bloques son insertados al final de la tabla, después de la gran marca de agua, el cual es el bloque más alto dentro de la tabla en el cual han sido escrito datos. Si insertamos 100 bloques dentro de una tabla y entonces eliminamos todos sus registros, la gran marca de agua de la tabla estará asignada al bloque 100. Si entonces realizamos una carga de datos convencional, los registros serán insertados dentro de los bloques ya asignados. Si intentamos realizar una carga Direct Path, Oracle insertará los nuevos bloques de datos después del bloque 100, incrementando potencialmente el espacio asignado a la tabla. El único modo de bajar la gran marca de agua de una tabla es truncándola (lo cual elimina todos los registros y no podemos recuperarlos) o borrándola y recreándola.

6.6. Funcionalidades adicionales.

Además de las funcionalidades vistas en este capítulo, SQL*Loader soporta tipos de datos Unicode y expandidos. SQL*Loader puede cargar tipos de datos enteros y decimales empaquetados entre plataformas con diferentes órdenes de byte y aceptar zonas basadas en EBCDIC o datos decimales codificados en formato IBM. SQL*Loader también ofrece soporte para cargar columnas XML, tipos de objetos con subtipos y Unicode (caracteres UTF16). SQL*Loader también proporciona soporte nativo para fechas, horas e intervalos de tiempo. Si una tarea SQL*Loader falla, podremos reanudarla desde donde falló usando las opciones RESUMABLE, RESUMABLE_NAME y RESUMABLE_TIMEOUT. Por ejemplo, si el segmento en el cual la tarea de carga está escribiendo no puede extenderse, podemos desactivar la tarea de carga, solucionar el problema de espacio y reanudar la tarea. Nuestra habilidad para realizar estas operaciones depende de la configuración de la base de datos. Podemos acceder a ficheros externos como si fuesen tablas internas de la base de datos. Esta funcionalidad de tablas externas permite potencialmente evitar cargar grandes volúmenes de datos dentro de la base de datos. (Véase el capítulo dedicado a tablas externas.)

7. Importar y exportar con «Data Pump»

Desde Oracle Database 10g se incluye Data Pump, que proporciona utilidades para la extracción e importación de datos basadas en servidor. Esta funcionalidad incluye cambios significativos en la arquitectura y funcionalidad respecto a las utilidades originales de importación y exportación. Data Pump permite parar y reiniciar tareas, ver el estado de las tareas que se están ejecutando y restringir los datos que son exportados e importados.

Nota. Data Pump puede usar los ficheros generados por la utilidad original Export, pero la utilidad original Import no puede usar los ficheros generados por Data Pump Export.

Data Pump se ejecuta como un proceso de servidor, beneficiando a los usuarios de varias maneras. El proceso cliente que inicia la tarea puede desconectarse y más tarde re-enlazarse a la tarea. El rendimiento es mejor (en comparación con Export/Import) porque los datos no necesitan ser procesados por un programa cliente. Las extracciones y cargas Data Pump pueden ser paralelizadas, ganando así en rendimiento.

7.1. Creando un directorio.

Data Pump requiere que creemos directorios para los ficheros de datos y ficheros log que serán creados y leídos. Se usa el comando CREATE DIRECTORY para crear un directorio dentro de Oracle que apuntará a un directorio externo. Los usuarios que accederán a ficheros Data Pump deber tener permisos READ y WRITE sobre el directorio.

Nota. Antes de empezar, hay que verificar que el directorio externo existe y que el usuario que usará el comando CREATE DIRECTORY tiene el permiso de sistema CREATE ANY DIRECTORY.

El siguiente ejemplo crea un directorio llamado DTPUMP y concede accesos READ y WRITE al esquema Empleado: CREATE DIRECTORY DTPUMP AS 'E:\DTPUMP';

GRANT READ ON DIRECTORY DTPUMP TO Empleado, SYSTEM;

Page 206: Java y Oracle 11g

Oracle /206

GRANT WRITE ON DIRECTORY DTPUMP TO Empleado, SYSTEM;

Los esquemas Empleado y SYSTEM pueden ahora usar el directorio DTPUMP para tareas Data Pump.

7.2. Opciones de «Data Pump Export».

Oracle proporciona una utilidad, EXPDP, que sirve como interfaz para Data Pump. La siguiente tabla muestra los parámetros de línea de comandos para EXPDP cuando es creada una tarea.

Parámetro Descripción

ATTACH Conecta una sesión clienta a una tarea de Data Pump Export que se está ejecutando actualmente.

CONTENT Filtros que son exportados: DATA_ONLY, METADATA_ONLY o ALL.

DIRECTORY Especifica el directorio destino para el fichero de log y el fichero de volcado asignado.

DUMPFILE Especifica el nombre y directorio para ficheros de volcado (dump).

ESTIMATE Determina el método usado para estimar el tamaño del fichero de volcado

(BLOCKS o STATISTICS).

ESTIMATE_ONLY Valor Y/N usado para instruir a Data Pump sobre si los datos deberían ser exportados o estimados.

EXCLUDE Especifica el criterio para excluir objetos y datos que serán exportados.

FILESIZE Especifica el tamaño máximo de fichero para cada fichero de volcado exportado.

FLASHBACK_SCN SCN de la base de datos para flashback durante la exportación.

FLASHBACK_TIME Fecha de la base de datos para flashback durante la exportación.

FULL Le dice a Data Pump que exporte todos los datos y metadatos en un modo de exportación "Full".

HELP Muestra una lista de comandos y opciones disponibles.

INCLUDE Especifica el criterio por el cual los objetos y datos serán exportados.

JOB_NAME Especifica el nombre de la tarea; el valor por defecto es generado por el sistema.

LOGFILE Nombre y directorio opcional para el fichero de log de exportación.

NETWORK_LINK Especifica el enlace de base de datos origen para la tarea de Data Pump Export a una base de datos remota.

NOLOGFILE Valor Y/N usado para suprimir la creación del fichero de log.

PARALLEL Asigna el número de trabajadores para la tarea de Data Pump Export.

PARFILE Nombres del fichero de parámetros que se usa, si hay alguno.

QUERY Filtras registros desde las tablas durante la exportación.

SCHEMAS Nombres de los esquemas a ser exportados por el modo de exportación "Schema".

STATUS Muestra detalles del estado de la tarea Data Pump.

TABLES Lista las tablas y particiones que serán exportadas para el modo de exportación "Table".

TABLESPACES Lista los tablespaces a ser exportados.

TRANSPORT_FULL_CHECK Especifica si el tablespace que es exportado debería primero ser verificado como un conjunto autónomo.

TRANSPORT_TABLESPACES Especifica un modo de exportación "Transportable Tablespace".

VERSION Especifica la versión de los objetos de base de datos a ser creados, y así el fichero de volcado podrá ser compatible con versiones previas de Oracle. Las

opciones son COMPATIBLE, LATEST, y el número de versión de la base de datos (no menor que 10.0.0).

Como se hace referencia en esta tabla, existe cinco modos de exportación Data Pump: • La exportación "Full" extrae todos los datos y metadatos de la base de datos (parámetro FULL). • La exportación "Schema" extrae los datos y metadatos para un esquema de usuario específico (parámetro SCHEMA). • La exportación "Tablespace" extrae los datos y metadatos para tablespaces (parámetro TABLESPACES). • La exportación "Table" extrae los datos y metadatos para tablas y sus particiones (parámetro TABLES). • La exportación "Transportable Tablespace" extrae los metadatos de tablespaces específicos (parámetro TRANSPORT_TABLESPACES).

Page 207: Java y Oracle 11g

Oracle /207

Nota. Debemos tener el permiso de sistema EXP_FULL_DATABASE para poder realizar exportación "Full" o exportación "Transportable Tablespace".

Cuando realizamos una tarea, Oracle le da un nombre de tarea. Si especificamos un nombre para la tarea mediante el parámetro JOB_NAME debemos asegurarnos de que ese nombre no entre en conflicto con el nombre de alguna tabla o vista de nuestro esquema. Durante la tarea Data Pump, Oracle creará y mantendrá una tabla maestra durante la duración de la tarea. Esta tabla tendrá el mismo nombre que la tarea Data Pump. Cuando una tarea se está ejecutando, podemos ejecutar los comandos siguientes mediante la interfaz de Data Pump.

Parámetro Descripción

ADD_FILE Añade un fichero de volcado.

CONTINUE_CLIENT Finaliza el modo interactivo y entra en el modo de logging.

EXIT_CLIENT Finaliza la sesión cliente, pero conserva ejecutándose la tarea de servidor Data Pump Export.

HELP Muestra ayudas en línea para la importación.

KILL_JOB Mata la tarea actual y desenlaza las sesiones cliente relacionadas.

PARALLEL Modifica el número de trabajadores para la tarea Data Pump Export.

START_JOB Reinicia la tarea asociada.

STATUS Muestra detalles de estado de la tarea Data Pump.

STOP_JOB Para la tarea para reiniciarla más tarde.

Como indican estos parámetros, podemos cambiar muchas características en la ejecución de Data Pump Export a través de modo de comandos interactivos. Si el área de volcado se ejecuta fuera de espacio, podemos asociarnos a la tarea, añadir ficheros, y reiniciar la tarea en algún momento; no necesitamos matar la tarea o re-ejecutarla desde el principio. Podemos mostrar el estado de la tarea en algún momento, mediante el parámetro STATUS o mediante las vistas del diccionario de datos USER_DATAPUMP_JOBS y DBA_DATAPUMP_JOBS o la vista V$SESSION_LONGOPS.

7.3. Iniciando una tarea de «Data Pump Export».

Podemos guardar nuestros parámetros de tarea en un fichero de parámetros, referenciado a través del parámetro PARFILE de EXPDP. Por ejemplo, podemos crear un fichero llamado dp1.par con las siguientes entradas:

Archivo «dp1.par»

DIRECTORY=DTPUMP

DUMPFILE=metadataonly.dmp

CONTENT=METADATA_ONLY

Entonces se puede iniciar la tarea Data Pump Export con: EXPDP empleado/contraseña PARFILE=dp1.par

Oracle pasará entonces las entradas de dp1.par a la tarea de Data Pump Export. Se ejecutará por defecto un Data Pump Export de tipo "Schema", y la salida (los metadatos listados, pero no los datos) serán escritos a un fichero en el directorio DTPUMP definido previamente. Cuando ejecutemos el comando EXPDP, la salida será en el siguiente formato (hay líneas separadas por cada tipo de objeto):

Export: Release 10.1.0.1.0 on Wednesday, 26 May, 2004 17:29

Copyright (c) 2003, Oracle. All rights reserved.

Conectado a: Oracle10i Enterprise Edition Release 10.1.0.1.0

Iniciando "EMPLEADO"."SYS_EXPORT_SCHEMA_01": empleado/******** parfile=dp1.par

Procesando el tipo de objeto SCHEMA_EXPORT/SE_PRE_SCHEMA_PROCOBJACT/PROCACT_SCHEMA

Procesando el tipo de objeto SCHEMA_EXPORT/TYPE/TYPE_SPEC

Procesando el tipo de objeto SCHEMA_EXPORT/TABLE/TABLE

Procesando el tipo de objeto SCHEMA_EXPORT/TABLE/GRANT/OBJECT_GRANT

Procesando el tipo de objeto SCHEMA_EXPORT/TABLE/INDEX/INDEX

Procesando el tipo de objeto SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT

Procesando el tipo de objeto SCHEMA_EXPORT/TABLE/COMMENT

Procesando el tipo de objeto SCHEMA_EXPORT/VIEW/VIEW

Procesando el tipo de objeto SCHEMA_EXPORT/PACKAGE/PACKAGE_SPEC

Procesando el tipo de objeto SCHEMA_EXPORT/PACKAGE/PACKAGE_BODY

Procesando el tipo de objeto SCHEMA_EXPORT/PACKAGE/GRANT/OBJECT_GRANT

Page 208: Java y Oracle 11g

Oracle /208

Procesando el tipo de objeto SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT

Procesando el tipo de objeto SCHEMA_EXPORT/SE_EV_TRIGGER/TRIGGER

La tabla maestra "EMPLEADO"."SYS_EXPORT_SCHEMA_01" se ha cargado/descargado correctamente

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

El juego de archivos de volcado para EMPLEADO.SYS_EXPORT_SCHEMA_01 es:

E:\DTPUMP\METADATAONLY.DMP

El trabajo "EMPLEADO"."SYS_EXPORT_SCHEMA_01" ha terminado correctamente en 17:30

El fichero de salida, tal como se muestra en el listado, se denomina metadataonly.dmp. El fichero de volcado contiene entradas XML para recrear las estructura del esquema Empleado. Durante la exportación, Data Pump crea y usar una tabla externa llamada SYS_EXPORT_SCHEMA_01.

Nota. Los ficheros de volcado no sobrescriben ficheros previamente existentes en el mismo directorio.

Podemos usar varios directorios y ficheros de volcado para un único Data Pump Export. Dentro del parámetro DUMPFILE podemos listar varios directorios y ficheros con el siguiente formato: DUMPFILE=directorio1:file1.dmp, directorio2:file2.dmp

Por ejemplo, si queremos realizar una copia de seguridad de la tabla VENTAS del esquema EMPLEADO dentro del directorio referenciado por DTPUMP, podemos usar el siguiente fichero de parámetros: DIRECTORY=DTPUMP

DUMPFILE=backup.dmp

CONTENT=ALL

TABLES=VENTAS

7.3.1. Parando y reanudando tareas que se están ejecutando. Después de haber empezado una tarea Data Pump Export podemos cerrar la ventana cliente que hemos usado para iniciar la tarea. Debido a que está basado en servidor, la exportación continuará ejecutándose. Podemos asociarnos a la tarea, verificar su estado y alterarla. Por ejemplo, podemos iniciar la tarea mediante EXPDP: EXPDP empleado/contraseña PARFILE=dp1.par

Presionando CTRL-C salimos de la pantalla de registro, y Data Pump retornará al indicador Export: Export>

Se finaliza la operación a través del comando EXIT_CLIENT: Export> EXIT_CLIENT

Podemos entonces reiniciar el cliente y asociarnos a la tarea que se está ejecutando actualmente bajo nuestro esquema: EXPDP empleado/contraseña ATTACH

Si hemos dado nombre a la tarea Data Pump Export, podemos especificar el nombre como parte del parámetro ATTACH. Por ejemplo, si hemos llamado a la tarea EMPLEADO_JOB, podemos asociarnos a la tarea con: EXPDP empleado/contraseña ATTACH=EMPLEADO_JOB

Cuando nos asociamos a una tarea que se está ejecutando, Data Pump mostrará el estado de la tarea (sus parámetros de configuración básicos y su estado actual). Podemos entonces usar el comando CONTINUE_CLIENT para ver las entradas de log que se generen o podemos modificar la tarea que se está ejecutando: Export> CONTINUE_CLIENT

Podemos parar una tarea mediante la opción STOP_JOB: Export> STOP_JOB

Cuando la tarea se para, podemos entonces añadir ficheros de volcado adicionales en nuevos directorios, a través de la opción ADD_FILE. Podemos entonces reiniciar la tarea: Export> START_JOB

Podemos especificar una localización del fichero de log para la exportación mediante el parámetro LOGFILE. Si no especificamos un valor para LOGFILE, el fichero de log serán escrito en el mismo directorio que el fichero de volcado. 7.3.2. Exportando para otra base de datos. Podemos usar el parámetro NETWORK_LINK para exportar datos desde una base de datos diferente. Si nos hemos registrado en la base de datos HQ y tenemos un enlace de base de datos en otra base de datos, Data Dump puede usar este enlace para conectarse a la base de datos y extraer sus datos.

Page 209: Java y Oracle 11g

Oracle /209

Nota. Si la base de datos es de solo lectura, el usuario de la base de datos origen deber tener un tablespace administrado localmente asignado como un tablespace temporal; si no es así, la tarea fallará.

En nuestro fichero de parámetros (o en la línea de comandos de EXPDP) debemos asignar el parámetro NETWORK_LINK al nombre de nuestro enlace de base de datos. El Data Pump Export escribirá los datos desde la base de datos remota al directorio definido en nuestra base de datos local. 7.3.3. Uso de «EXCLUDE», «INCLUDE» y «QUERY». Podemos excluir o incluir conjuntos de tabla desde Data Pump Export mediante las opciones EXCLUDE e INCLUDE. Podemos excluir objetos por tipo o por nombre. Si un objeto es excluido, todos los objetos dependientes serán también excluidos. El formato para la opción EXCLUDE: EXCLUDE= tipo_objecto [ : cláusula] [, ...]

Nota. No podemos especificar EXCLUDE si especificamos CONTENT=DATA_ONLY.

Por ejemplo, para excluir el esquema Empleado de una exportación completa, el formato para la opción EXCLUDE debería ser como sigue. EXCLUDE=SCHEMA:"='Empleado'"

La opción EXCLUDE de este ejemplo contiene una condición limitante (='Empleado') dentro de las comillas dobles. La variable cláusula puede ser cualquier tipo de objeto de Oracle, incluyendo GRANT, INDEX y TABLE. Por ejemplo, para excluir de la exportación de todas las tablas aquellas cuyo nombre comience con 'TEMP', podemos especificar lo siguiente: EXCLUDE=TABLE:"LIKE 'TEMP%'"

Cuando escribimos este tipo de línea de comando, podemos necesitar usar caracteres de escape de forma que las marcas de comilla y otros caracteres especiales sean pasados apropiadamente a Oracle. El comando EXPDP estará con el siguiente formato: EXPDP empleado/contraseña EXCLUDE=TABLE:\"LIKE \'TEMP%\'\"

Nota. Podemos especificar más de una opción EXCLUDE dentro de la misma tarea de Data Pump Export.

Si no se proporciona valor para cláusula, todos los objetos del tipo especificado son excluidos. Para excluir todos los índices, por ejemplo, podemos especificar lo siguiente: EXPDP empleado/contraseña EXCLUDE=INDEX

Para obtener un listado de los objetos que podemos filtrar, podemos consultar las siguientes vistas del diccionario de datos: DATABASE_EXPORT_OBJECTS, SCHEMA_EXPORT_OBJECTS y TABLE_EXPORT_OBJECTS. Si el valor de tipo_objeto es CONSTRAINT, las restricciones NOT NULL no serán excluidas. Además, las restricciones necesarias para crear una tabla (como la de clave primaria para tablas organizadas por índices) tampoco serán excluidas. Si el valor de tipo_objeto es USER, se excluye la definición del usuario, pero los objetos dentro del esquema del usuario serán exportados. Se usa el valor SCHEMA en tipo_objeto para excluir un usuario y todos los objetos que le pertenecen. Si el valor de tipo_objeto es GRANT, todos los objetos de concesión y permisos del sistema serán excluidos. Una segunda opción, INCLUDE, también está disponible. Cuando se usa INCLUDE, sólo aquellos objetos que forman parte del criterio son exportados; todos los demás objetos son excluidos. INCLUDE y EXCLUDE son mutuamente exclusivos. El formato de INCLUDE es: INCLUDE = tipo_objecto [ : cláusula ] [, ...]

Nota. No podemos especificar INCLUDE si especificamos CONTENT=DATA_ONLY.

Por ejemplo, para exportar dos tabla y todos los procedimientos, nuestro fichero de parámetros puede incluir estas dos líneas: INCLUDE=TABLE:"IN ('LIBROS','AUTORES')"

INCLUDE=PROCEDURE

¿Qué filas serán exportadas para los objetos que cumplen los criterios EXCLUDE o INCLUDE? Por defecto, todos los registros son exportados para cada tabla. Podemos usar la opción QUERY para limitar los registros que son retornados. El formato para la opción QUERY es: QUERY = [ esquema . ] [ nombre_tabla : ] cláusula

Si no especificamos valores para las variables esquema y nombre_tabla, la variable cláusula se aplicará a todas las tablas exportadas. Debido a que cláusula normalmente incluye nombres de columnas específicas, deberíamos

Page 210: Java y Oracle 11g

Oracle /210

ser muy cuidadosos cuando seleccionamos las tablas a incluir en la exportación. Podemos especificar un valor QUERY para una única tabla, tal como se muestra a continuación: QUERY=LIBROS:'"WHERE IdLibro > 2"'

Como resultado, el fichero de volcado sólo contendrá aquellos registros que cumplan con el criterio. Podemos aplicar también estas restricciones durante la subsiguiente Data Pump Import.

7.4. Opciones para «Data Pump Import».

Para importar un fichero de volcado exportado mediante Data Pump Export, se usa Data Pump Import. Al igual que el proceso de exportación, el proceso de importación se ejecuta como una tarea basada en servidor que podemos gestionar durante su ejecución. Podemos interactuar con Data Pump Import mediante la interfaz de línea de comandos, un fichero de parámetros y una interfaz interactiva.

Nota. El directorio para el fichero de volcado y el fichero de log debe existir previamente.

Al igual que con Data Pump Export, se soportan cinco modos: "Full", "Schema", "Table", "Tablespace" y "Transportable Tablespace". Si no se especifica el modo, Oracle intenta cargar todos el fichero de volcado. La siguiente tabla describe los parámetros de la interfaz de línea de comandos.

Parámetro Descripción

ATTACH Asocia el cliente a una sesión del servidor y sitúa el modo interactivo.

CONTENT Filtros que son importados: ALL, DATA_ONLY o METADATA_ONLY.

DIRECTORY Especifica la ubicación del fichero de volcado asignado al directorio destino para los ficheros de log y SQL.

DUMPFILE Especifica los nombres y, opcionalmente, los directorios para el fichero de volcado asignado.

ESTIMATE Determina el método usado para estimar el tamaño del fichero de volcado

(BLOCKS o STATISTICS).

EXCLUDE Excluye objetos y datos que están siendo importados.

FLASHBACK_SCN SCN de la base de datos para "flash back" durante la importación.

FLASHBACK_TIME Timestamp de la base de datos para "flash back" durante la importación.

FULL Valor Y/N usado para especificar si queremos importar el fichero de volcado completo.

HELP Muestra ayudas en línea para la importación.

INCLUDE Especifica el criterio para los objetos que serán importados.

JOB_NAME Especifica un nombre para la tarea; el valor por defecto es asignado por el sistema.

LOGFILE Nombre y nombre opcional del directorio para el fichero de log.

NETWORK_LINK Especifica el enlace de base de datos origen para una tarea Data Pump de importación a una base de datos remota.

NOLOGFILE Valor Y/N usando para suprimir la creación del fichero de log.

PARALLEL Asigna el número de trabajadores para la tarea de Data Pump Import.

PARFILE Nombre del fichero de parámetros, si se usa.

QUERY Filtros de registros para tablas durante la importación.

REMAP_DATAFILE Cambia el nombre del fichero de datos fuente para el fichero de datos

destino en los comandos CREATE LIBRARY, CREATE TABLESPACE y

CREATE DIRECTORY durante la importación.

REMAP_SCHEMA Importa datos exportados desde el esquema fuente dentro del esquema destino.

REMAP_TABLESPACE Importa datos exportados desde el tablespace fuente dentro del tablespace destino.

REUSE_DATAFILES Especifica si ficheros de datos existentes deberían reutilizarse por el

comando CREATE TABLESPACE durante el modo de importación "Full".

SCHEMAS Nombre del esquema a ser importado en el modo de importación "Schema".

SKIP_UNUSABLE_INDEXES Valor Y/N. Si es Y, la importación no carga datos dentro de tablas cuyos índices estén en el estado "Index Unusable".

SQLFILE Nombre del fichero en el cual el DDL para la importación será escrito. Los datos y metadatos no serán cargados dentro de la base de datos destino.

STATUS Muestra detalles del estado de la tarea Data Pump.

STREAMS_CONFIGURATION Valor Y/N usado para especificar si se debe importar la información de

Page 211: Java y Oracle 11g

Oracle /211

configuración de streams.

TABLE_EXISTS_ACTION Instruye cómo procesar la importación si la tabla que está siendo importada

ya existe. Puede tomar los valores SKIP, APPEND, TRUNCATE y REPLACE. El

valor por defecto es APPEND si CONTENT=DATA_ONLY; si no, el valor por

defecto es SKIP.

TABLES Lista de tablas para el modo de importación "Table".

TABLESPACES Lista de tablespaces para el modo de importación "Tablespace".

TRANSFORM Dirige cambios a los atributos de segmento o al almacenaje durante la importación.

TRANSPORT_DATAFILES Lista los ficheros de datos a ser importados durante el modo de importación "Transportable Tablespace".

TRANSPORT_FULL_CHECK Especifica si los tablespaces que son importados deben primero verificarse como autónomos.

TRANSPORT_TABLESPACES Lista los tablespaces que serán importados durante el modo de importación "Transportable Tablespace".

VERSION Especifica la versión de los objetos de base de datos que serán creados para que el fichero de volcado sea compatible con versiones de Oracle previas.

Las opciones son COMPATIBLE, LATEST y el número de versión (no menor

que 10.0.0). Sólo válido para NETWORK_LINK y SQLFILE.

La siguiente tabla describe los parámetros que son válidos en el modo interactivo de Data Pump Import.

Parámetro Descripción

CONTINUE_CLIENT Finaliza el modo interactivo y entra en modo de registro. La tarea será reiniciada si está parada.

EXIT_CLIENT Finaliza la sesión de cliente, pero continúa ejecutándose la tarea de servidor Data Pump Import.

HELP Muestra ayuda en línea para la importación.

KILL_JOB Mata la tarea actual y desasocia las sesiones cliente relacionadas.

PARALLEL Modifica el número de trabajadores para la tarea Data Pump Import.

START_JOB Reinicia la tarea asociada.

STATUS Muestra detalles de estado para la tarea Data Pump.

STOP_JOB Para la tarea para reiniciarla después.

Muchos de los parámetros de Data Pump Import son los mismos que los disponibles para Data Pump Export.

7.5. Iniciando una tarea de «Data Pump Import».

Podemos iniciar una tarea de Data Pump Import mediante el ejecutable IMPDP proporcionado con Oracle Database 11g. Se usan los parámetros de la línea de comandos para especificar el modo de importación y la localización de todos los ficheros. Podemos guardar estos valores de parámetros en un fichero de parámetro que podemos referenciar a través de la opción PARFILE. En el primer ejemplo de exportación, el fichero de parámetros llamado dp1.par contenía las siguientes entradas:

Archivo «dp1.par»

DIRECTORY=DTPUMP

DUMPFILE=metadataonly.dmp

CONTENT=METADATA_ONLY

La importación creará los objetos del esquema Empleado dentro de un esquema diferente. La opción REMAP_SCHEMA permite importar objetos dentro de un esquema diferente al usado para la exportación. Si queremos cambiar el tablespace asignado para los objetos al mismo tiempo, se usa la opción REMAP_TABLESPACE. El formato de REMAP_SCHEMA es: REMAP_SCHEMA= esquema_fuente : esquema_destino

Crearemos una nueva cuenta de usuario para guardar estos objetos: CREATE USER NuevoEmpleado IDENTIFIED BY contraseña;

GRANT CREATE SESSION TO NuevoEmpleado;

GRANT CONNECT, RESOURCE TO NuevoEmpleado;

GRANT IMP_FULL_DATABASE TO NuevoEmpleado;

Page 212: Java y Oracle 11g

Oracle /212

Podemos ahora añadir la línea REMAP_SCHEMA al fichero de parámetros:

Archivo «dp1.par»

DIRECTORY=dtpump

DUMPFILE=metadataonly.dmp

CONTENT=METADATA_ONLY

REMAP_SCHEMA=Empleado:NuevoEmpleado

Ahora podemos empezar la importación. Debido a que estamos cambiando el esquema propietario para los datos, debemos tener el permiso de sistema IMP_FULL_DATABASE. La tarea de Data Pump Import se inicia a través del ejecutable IMPDP. El siguiente listado muestra cómo crear la tarea de importación usando el fichero de parámetros dp1.par. IMPDP system/contraseña PARFILE=dp1.par

Nota. Deben especificarse todos los ficheros de volcado a la vez que se inicia la tarea.

Oracle comenzará entonces la importación y mostrará su progreso. Debido a que no fue especificada la opción NOLOGFILE el archivo de log para la importación será ubicado en el mismo directorio que el fichero de volcado, y tendrá el nombre import.log. Podemos comprobar el resultado de la tarea registrándonos dentro del esquema NuevoEmpleado. El esquema NuevoEmpleado debería tener una copia de todos los objetos válidos que fueron previamente creados en el esquema Empleado. ¿Qué ocurre si una tabla que está siendo importada ya existe? En este ejemplo, con la opción CONTENT asignada a METADATA_ONLY, la tabla debería ignorarse por defecto. Si la opción CONTENT estuviese asignada a DATA_ONLY, los nuevos datos serán añadidos a la tabla existente. Para modificar este comportamiento se usan la opción TABLE_EXISTS_ACTION. Valores válidos para este opción son SKIP (ignorar), APPEND (añadir), TRUNCATE (truncar) y REPLACE (reemplazar). 7.5.1. Parando y reiniciando las tareas. Después de iniciar una tarea Data Pump Import, podemos cerrar la ventana cliente que usamos para iniciar la tarea. Ya que está basada en servidor, la importación continuará ejecutándose. Podemos asociarnos a la tarea, comprobar su estado y modificarla. IMPDP system/contraseña PARFILE=dp1.par

Presionando las teclas CTRL-C regresamos a la pantalla de registro, y Data Pump regresará al indicador Import: Import>

Podemos salir del sistema mediante el comando EXIT_CLIENT: Import> EXIT_CLIENT

Podemos entonces reiniciar el cliente y asociarnos a la tarea que se ejecute actualmente bajo nuestro esquema: IMPDP system/contraseña ATTACH

Si hemos dado un nombre a la tarea, podemos usarlo como parte del parámetro ATTACH para asociarnos a la tarea. Cuando nos asociamos a una tarea, Data Pump muestra el estado de la tarea. Podemos entonces introducir el comando CONTINUE_CLIENT para ver las entradas de log generadas, o podemos modificar la tarea: Import> CONTINUE_CLIENT

Podemos parar una tarea mediante la opción STOP_JOB: Import> STOP_JOB

Mientras la tarea está parada, podemos incrementar su paralelismo mediante la opción PARALLEL. Y después podemos reiniciar la tarea: Import> START_JOB

7.5.2. «EXCLUDE», «INCLUDE» y «QUERY». Data Pump Import, como Data Pump Export, permite restringir el procesado de datos mediante el uso de las opciones EXCLUDE, INCLUDE y QUERY, tal como se ha descrito previamente. Por ejemplo, podemos elegir exportar una tabla entera pero sólo importar parte de la misma (los registros que cumplen el criterio de QUERY). Podemos elegir para exportar un esquema entero pero cuando recuperemos la base de datos mediante importación incluir sólo las tablas necesarias. EXCLUDE, INCLUDE y QUERY proporciona poderosas capacidades a los programadores y administradores de base de datos durante las tareas de exportación e importación. 7.5.3. Transformación de objetos importados. Además de cambiar o seleccionar esquemas, tablespaces, ficheros de datos y registros durante la importación, podemos cambiar los atributos de segmento y requerimientos de almacenaje durante la importación mediante

Page 213: Java y Oracle 11g

Oracle /213

la opción TRANSFORM. El formato de TRANSFORM es: TRANSFORM = nombre_transformación : valor [ : tipo_objeto ]

La variable nombre_transformación puede tomar los valores SEGMENT_ATTRIBUTES o STORAGE. Se puede usar el valor de la variable para incluir o excluir atributos de segmento (atributos físicos, atributos de almacenaje, tablespaces y logging). La variable tipo_objeto es opcional y, si se especifica, debe ser TABLE o INDEX. Por ejemplo, los requerimientos de almacenamiento de objetos puede cambiar durante la exportación/importación (se puede usar la opción QUERY para limitar las filas importadas, o podemos importar sólo los metadatos sin los datos de la tabla). Para eliminar las cláusulas de almacenamiento exportadas de las tablas importadas hay que añadir lo siguiente al fichero de parámetros: TRANSFORM=STORAGE:n:tabla

Para eliminar los tablespaces exportados y cláusulas de almacenamiento de todas las tablas e índices, se usa lo siguiente: TRANSFORM=SEGMENT_ATTRIBUTES:n

Cuando los objetos son importados, serán asignados a los tablespaces por defecto del usuario y usarán los parámetros de almacenamiento de estos tablespaces por defecto. 7.5.4. Generación de SQL. En vez de importar los datos y objetos, podemos generar el SQL para los objetos (no los datos) y almacenarlo en un fichero de nuestro sistema operativo. El fichero será escrito en el directorio especificado mediante la opción SQLFILE. El formato de la opción SQLFILE es: SQLFILE=[ objeto_directorio : ] nombre_fichero

Nota. Si no se especifica un valor para la variable objeto_directorio, el fichero será creado en el directorio del fichero de volcado.

Lo siguiente muestra un ejemplo de fichero de parámetros para una importación usando la opción SQLFILE. Nótese que la opción CONTENT no está especificada. La salida será escrita en el directorio dtpump. DIRECTORY=dtpump

DUMPFILE=metadataonly.dmp

SQLFILE=sql.txt

Podemos entonces ejecutar la importación para poblar el fichero sql.txt: IMPDP Empleado/contraseña PARFILE=dp1.par

En el fichero sql.txt que crea la importación, podemos ver entradas para cada tipo de objeto dentro del esquema. El formato del fichero de salida será similar al siguiente listado. (Para abreviar no se han incluido todas las entradas.) -- CONNECT Empleado

-- new object type path is: SCHEMA_EXPORT/

SE_PRE_SCHEMA_PROCOBJACT/PROCACT_SCHEMA

BEGIN

sys.dbms_logrep_imp.instantiate_schema(schema_name=>'Empleado',

export_db_name=>'ORCL', inst_scn=>'3377908');

COMMIT;

END;

/

-- new object type path is: SCHEMA_EXPORT/TYPE/TYPE_SPEC

CREATE TYPE "Empleado"."Direccion"

OID '48D49FA5EB6D447C8D4C1417D849D63A' as object

(Calle VARCHAR2(50),

Ciudad VARCHAR2(25),

Region CHAR(20),

CP NUMBER);

/

CREATE TYPE "Empleado"."Cliente"

OID '8C429A2DD41042228170643EF24BE75A' as object

(IdCliente NUMBER,

Nombre VARCHAR2(25),

Calle VARCHAR2(50),

Ciudad VARCHAR2(25),

Page 214: Java y Oracle 11g

Oracle /214

Region CHAR(20),

CP NUMBER);

/

CREATE TYPE "Empleado"."Persona"

OID '76270312D764478FAFDD47BF4533A5F8' as object

(Nombre VARCHAR2(25),

Direccion Direccion);

/

-- new object type path is: SCHEMA_EXPORT/TABLE/TABLE

CREATE TABLE "Empleado"."Cliente"

( "IdCliente" NUMBER,

"Nombre" VARCHAR2(25),

"Calle" VARCHAR2(50),

"Ciudad" VARCHAR2(25),

"Region" CHAR(20),

"CP" NUMBER

) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING

STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645

PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)

TABLESPACE "USERS" ;

. . .

La salida SQLFILE es un fichero de texto plano, así que podemos editarlo, usarlo dentro de SQL*Plus, o añadirlo como documentación de las estructuras de nuestra aplicación de base de datos. 7.5.5. Comparando Data Pump Export/Import con Export/Import. Las utilidades originales Export e Import están todavía disponibles mediante los ejecutables EXP e IMP. Como se ha visto, poseen muchas capacidades superiores respecto a las utilidades originales. La arquitectura basada en servidor de Data Pump conduce a beneficios de rendimiento y gestión mejorada. Sin embargo, Data Pump no soporta capacidades de confirmación incrementales ofrecidas por los parámetros COMMIT y BUFFER del Import original. Tampoco Data Pump combina automáticamente varias extensiones dentro de una única extensión durante el proceso Data Pump Export/Import; los originales Export/Import ofrecen esta funcionalidad mediante el parámetro COMPRESS. En Data Pump podemos usar la opción TRANSFORM para suprimir los atributos de almacenamiento durante la importación (una opción que muchos usuarios del original Export/Import puede preferir a la funcionalidad COMPRESS). Si estamos importando mediante Data Pump dentro de una tabla existente usando las variantes APPEND o TRUNCATE de la opción TABLE_EXISTS_ACTION y un registro viola una restricción activa, la carga se para y los datos no son cargados. En el Import original, la carga habría continuado.

8. Acceso a datos remotos

Como nuestras bases de datos aumentan en tamaño y número, es habitual la necesidad de compartir datos entre ellas. Compartir datos requiere un método para localizar y acceder a los datos. En Oracle, los datos remotos son accedidos mediante consultas y actualizaciones realizadas a través de enlaces de base de datos. Los enlaces de base de datos permiten a los usuarios tratar un grupo de base de datos distribuidas como si fuese una única e integrada base de datos.

8.1. Enlaces de base de datos.

Un enlace de base de datos le dice a Oracle cómo ir de una base de datos a otra. Podemos especificar la ruta de acceso de una manera ad hoc (usando el comando de copia de SQL*Plus). Si usamos frecuentemente la misma conexión a una base de datos remota, un enlace de base de datos es lo apropiado. 8.1.1. Cómo trabaja un enlace de base de datos. Un enlace de base de datos requiere que Oracle Net (previamente conocido como SQL*Net y Net8) se esté ejecutando sobre cada máquina (host) involucrada en el acceso remoto. Oracle Net es normalmente iniciado por el administrador de base de datos (DBA) o el administrador del sistema. Una arquitectura simple para un acceso remoto usando enlaces de base de datos se muestra en la siguiente figura:

Page 215: Java y Oracle 11g

Oracle /215

La figura muestra dos hosts, cada uno de los cuales ejecuta Oracle Net. Un enlace de base de datos establece una conexión desde la primera base de datos (llamada LOCAL en el host periférico) a la segunda base de datos (llamada REMOTA en el host central). Un enlace de base de datos especifica la siguiente información de conexión:

• El protocolo de comunicación (como TCP/IP) que se usa durante la conexión. • El host en el cual reside la base de datos remota. • El nombre de la base de datos en el host remoto. • El nombre de una cuenta válida en la base de datos remota. • La contraseña de la cuenta.

Cuando se usa, un enlace de base de datos se registra como un usuario en la base de datos remota y se desconecta cuando el acceso de datos remotos se completa. Un enlace de base de datos puede ser privado, propiedad de un único usuario, o público, en cuyo caso todos los usuarios de la base de datos LOCAL pueden usar el enlace. 8.1.2. Sintaxis para enlaces de base de datos. Podemos crear un enlace de base de datos con el siguiente comando: CREATE [SHARED] [PUBLIC] DATABASE LINK nombre_conexion_remota

CONNECT TO {CURRENT_USER | usuario IDENTIFIED BY contraseña [ cláusula de autentificación ]}

USING 'cadena de conexión' ;

La sintaxis específica para usar cuando creamos un enlace de base de datos depende de dos criterios: • El estado "público" o "privado" del enlace. • El uso de registros explícitos o por defecto de la base de datos remota.

Estos criterios y su sintaxis asociada se describen en las siguientes secciones.

Nota. Para crear un enlace privado de base de datos debemos tener el permiso de sistema CREATE

DATABASE LINK, y para crear un enlace público el permiso CREATE PUBLIC DATABASE LINK. La cuenta a la cual queramos conectarnos en la base de datos remota debe tener el permiso de sistema CREATE SESSION. Estos dos permisos se incluyen como parte del rol CONNECT de Oracle, aunque en Oracle Database 11g este rol sólo existe para compatibilidad.

Si el valor del parámetro de inicialización GLOBAL_NAMES es TRUE, el enlace de base de datos debe tener el mismo nombre que la base de datos a la que se conecta. Si el valor de este parámetro es FALSE y hemos cambiado el nombre global de la base de datos, podemos especificar el nombre global. Enlaces de base de datos públicos frente a privados. Un enlace de base de datos público está disponible para todos los usuarios de la base de datos. Por el contrario, un enlace privado sólo está disponible para el usuario que lo creó. No es posible para un usuario conceder permiso sobre un enlace privado a otro usuario. Un enlace debe ser público o bien privado. Para especificar que un enlace es público se usa la palabra clave PUBLIC en el comando CREATE DATABASE

LINK, tal como se muestra a continuación: CREATE PUBLIC DATABASE LINK Conexion_Remota

CONNECT TO Empleado IDENTIFIED BY contraseña

USING 'la cadena de conexión' ; Si no se especifica PUBLIC el enlace es por defecto privado.

Nota. Para crear un enlace de base de datos público debemos tener el permiso de sistema CREATE

PUBLIC DATABASE LINK. Este permiso está incluido en el rol DBA de Oracle, aunque en Oracle Database 11g este rol existe sólo por compatibilidad.

Registro por defecto frente a registro explícito. En lugar de la cláusula CONNECT TO … IDENTIFIED BY … podemos usar CONNECT TO CURRENT_USER cuando creamos un enlace. Si se usa la opción CURRENT_USER, entonces al usar el enlace se intentará abrir una sesión

BASE DE DATOS LOCAL

Enlace de base de datos

BASE DE DATOS REMOTA

Host Central Host Periférico

Page 216: Java y Oracle 11g

Oracle /216

en la base de datos remota que tenga el mismo nombre de usuario y contraseña que la cuenta de la base de datos local. Esto se denomina un registro por defecto. El siguiente listado muestra un ejemplo de un enlace creado con un registro por defecto: CREATE PUBLIC DATABASE LINK Conexion_Remota

CONNECT TO CURRENT_USER

USING 'HQ';

Nota. El registro por defecto se conecta a la base de datos remota como un usuario global.

Cuando se use este enlace se intentará registrarse en la base de datos remota identificada por el nombre de servicio 'HQ' usando el nombre de usuario y contraseña del usuario actual. Si el usuario actual no es válido en la base de datos remota, o si la contraseña es diferente, el intento de registro fallará. Este fallo provocará que el comando SQL que use el enlace falle. Un registro explícito debe especificar el nombre de usuario y contraseña que usará el enlace mientras se conecte a la base de datos remota. No importa qué cuenta local use el enlace, se usará la misma cuenta remota. El listado siguiente muestra la creación de un enlace con una conexión explícita CREATE PUBLIC DATABASE LINK Conexion_Remota

CONNECT TO Empleado IDENTIFIED BY contraseña

USING 'HQ';

Este ejemplo muestra un uso normal del registro explícito en un enlace de base de datos. En la base de datos remota debe existir un usuario llamado Empleado con la contraseña correspondiente. La cuenta Empleado debe tener concedido el acceso SELECT para las tablas especificadas, únicamente para ser usado por el enlace. El enlace Conexion_Remota entonces proporciona acceso a la cuenta remota para todos los usuarios locales. Sintaxis de la cadena de conexión. Oracle Net usa un nombre de servicio para identificar conexiones remotas. Los detalles de conexión de este nombre de servicio están contenidos en fichero que son distribuidos en cada host de la red. Cuando se encuentra un nombre de servicio, Oracle verifica el fichero de configuración local de Oracle Net (llamado tnsnames.ora) para determinar qué protocolo, nombre de host y nombre de base de datos se usará durante la conexión. Toda la información de conexión se encuentra en ficheros externos. Cuando se usa Oracle Net, debemos conocer el nombre del servicio que apunta a la base de datos remota. Por ejemplo, si el nombre del servicio HQ especifica los parámetros de conexión para la base de datos que necesitamos, entonces deberemos usar HQ como la cadena de conexión en el comando CREATE DATABASE

LINK. El siguiente ejemplo muestra un enlace de base de datos privado usando registro por defecto en un nombre de servicio de Oracle Net: CREATE DATABASE LINK Conexion_Remota

CONNECT TO CURRENT_USER

USING 'HQ';

Cuando se usa este enlace, Oracle comprueba el fichero tnsnames.ora en el host local para determinar a qué base de datos debe conectarse. Los ficheros tnsnames.ora de una red de base de datos deberían ser coordinados por los DBA's de las base de datos. Una entrada típica en el fichero tnsnames.ora (para una red que use el protocolo TCP/IP) se muestra a continuación: HQ =(DESCRIPTION=

(ADDRESS_LIST =

(ADDRESS = (PROTOCOL=TCP) (HOST=host1) (PORT=1521))

)

(CONNECT DATA=

(SERVICE_NAME = HQ.host1)

)

)

En este listado, el nombre de servicio HQ se mapea a un descriptor de conexión que le dice a la base de datos qué protocolo usar (TCP/IP) y a qué host (host1) y base de datos (HQ) conectarse. La información PORT se refiere al puerto a través del cual el host podrá ser conectado; este dato es específico para cada servidor (aunque Oracle Listener usa por defecto el puerto 1521). Cada protocolo incluye sus propias palabras clave, pero todos ellos contendrán el mismo tipo de información. La cadena de conexión puede también utilizar directamente la definición TNS (esto posibilita que no tengamos que modificar el fichero tnsnames.ora).

Page 217: Java y Oracle 11g

Oracle /217

CREATE DATABASE LINK nombre_del_enlace

CONNECT TO usuario IDENTIFIED BY contraseña

USING '(DESCRIPTION=

(ADDRESS=

(PROTOCOL=TCP)

(HOST=localhost)

(PORT=1521) )

(CONNECT_DATA=

(SID=base_de_datos) )

)';

Usando enlaces de base de datos compartidos. Si usamos la opción Shared Server para nuestras conexiones de base de datos y nuestra aplicación emplea muchas conexiones de enlaces concurrentes, podemos beneficiarnos de usar enlaces compartidos. Un enlace compartido usa conexiones de servidor estáticas para soportar las conexiones de los enlaces. Si tenemos varios accesos de enlaces concurrentes dentro de una base de datos remota, podemos usar enlaces compartidos para reducir el número de conexiones de servidor requeridas. Para crear un enlace compartido se usa la palabra clave SHARED en el comando CREATE DATABASE LINK. Como se muestra en el siguiente ejemplo, también necesitamos especificar un esquema y contraseña para la base de datos remota: CREATE SHARED PUBLIC DATABASE LINK HR_Enlace_Estatico

CONNECT TO CURRENT_USER

AUTHENTICATED BY hr IDENTIFIED BY contraseña

USING 'HQ';

El enlace HR_Enlace_Estatico se crea como público, y usa el nombre de usuario y contraseña actuales cuando accede a la base de datos 'HQ'. Para prevenir intentos no autorizados de usar el enlace compartido, este tipo de enlaces requieren la cláusula AUTHENTICATED BY. En este ejemplo, la cuenta usada para la autentificación es una cuenta de la aplicación, pero también podemos usar un esquema vacío para la autentificación. La cuenta de autentificación debe tener el permiso de sistema CREATE SESSION. Durante el uso del enlace HR_Enlace_Estatico los intentos de conexión incluirán autentificación a través de la cuenta enlazada hr. Si cambiamos la contraseña en la cuenta de autentificación, necesitaremos borrar y re-crear cada enlace que la referencie. Para simplificar el mantenimiento, lo mejor es crear una cuenta que sólo sea usada para la autentificación de conexiones de enlaces compartidos. La cuenta debería tener sólo el permiso de sistema CREATE SESSION y no debería tener ningún otro permiso sobre ninguna tabla de la aplicación. Si nuestra aplicación usa enlaces de base de datos infrecuentemente, deberíamos usar enlaces tradicionales sin la cláusula SHARED. Sin esta cláusula, cada conexión de enlace requerirá una conexión independiente a la base de datos remota. 8.1.3. Usando un enlace de base de datos para consultas remotas. Si somos un usuario de la base de datos LOCAL de la figura previa, podemos acceder a los objetos de la base de datos REMOTA a través del enlace de base de datos. Para hacer esto, simplemente se añade el nombre del enlace al nombre de cualquier tabla o vista que sea accesible en la cuenta remota. Cuando añadimos el nombre del enlace a un nombre de tabla o vista, debemos precederlo con el símbolo @. Para tablas locales referenciamos el nombre de la tabla en la cláusula FROM: SELECT * FROM Libro;

Como ejemplo, para tablas remotas usaremos el nombre de enlace CONEXIÓN_REMOTA. En la cláusula FROM referenciaremos el nombre de la tabla seguido de @CONEXION_REMOTA: SELECT * FROM Libro@CONEXION_REMOTA;

Cuando se usa el enlace de esta consulta, Oracle se registra en la base de datos remota usando el nombre de usuario y contraseña especificado en la creación del enlace. Entonces se consulta la tabla Libro en la cuenta remota y se retornan los datos al usuario que inició la consulta. Esto se muestra gráficamente en la siguiente figura:

SELECT * FROM Libro@CONEXION_REMOTA

Enlace de base de datos

SELECT * FROM Libro

Page 218: Java y Oracle 11g

Oracle /218

Nota. El número máximo de enlaces de base de datos que podemos usar en una única consulta se establece en el parámetro OPEN_LINKS dentro del fichero de inicialización de parámetros de la base de datos. Este parámetro tiene por defecto el valor 4.

Las consultas que usan enlaces de base de datos tienen algunas restricciones. Deberíamos evitar enlaces en consultas que usen las palabras claves CONNECT BY, START WITH y PRIOR. Algunas consultas que usen estas palabras trabajarán (por ejemplo, si PRIOR no se usa fuera de la cláusula CONNET BY, y START WITH no usa una subconsulta), pero para muchos usuarios de consultas jerárquicas fallará si se usan enlaces de base de datos. 8.1.4. Usando un enlace de base de datos para sinónimos y vistas. Podemos crear sinónimos y vistas locales que referencien objetos remotos. Para hacer esto se referencia el nombre del enlace de base de datos precedido del símbolo @, en cualquier parte donde se referencie una tabla remota. El siguiente ejemplo muestra cómo hacer esto para sinónimos. El comando CREATE SYNONYM de este ejemplo es ejecutado desde una cuenta de la base de datos local. CREATE SYNONYM Libro_Syn FOR Libro@CONEXION_REMOTA;

En este ejemplo, un sinónimo llamado Libro_Syn se crea para acceder a la tabla Libro a través del enlace CONEXION_REMOTA. Cada vez que se use este sinónimo es la cláusula FROM de una consulta, se consultará la base de datos remota. ¿Qué ocurre si la cuenta remota a la que accede el enlace de base de datos no es propietaria de la tabla referenciada? En este caso podemos usar cualquier sinónimo disponible en la cuenta remota (privado o público). Si no existen sinónimos para una tabla para la que cuenta remota tiene permisos de acceso, debemos especificar el nombre del propietario de la tabla en la consulta, tal como se muestra en el siguiente ejemplo: CREATE SYNONYM Libro_Syn FOR Empleado.Libro@CONEXION_REMOTA;

En este ejemplo, la cuenta remota usada por el enlace no es propietaria de la tabla Libro, y la cuenta remota no tiene un sinónimo llamado Libro. Sin embargo, tiene permisos sobre la tabla Libro, que es propiedad del usuario remoto Empleado en la base de datos remota. Así pues, debemos especificar el propietario y el nombre de la tabla; ambos son interpretados en la base de datos remota. Para usar un enlace en una vista simplemente se añade como un sufijo a los nombres de las tablas en el comando CREATE VIEW. El siguiente ejemplo crea una vista en la base de datos local de una tabla remota usando el enlace CONEXIÓN_REMOTA: CREATE VIEW Local_Libro_View AS

SELECT * FROM Libro@CONEXION_REMOTA WHERE Titulo >'M';

Esta vista puede ahora ser tratada de la misma manera que cualquier otra vista en la base de datos local. El acceso a esta vista puede concederse a otros usuarios, a condición de que estos usuarios tengan acceso al enlace CONEXIÓN_REMOTA. 8.1.5. Usando un enlace de base de datos para actualizaciones remotas. La sintaxis de enlaces de base de datos para actualizaciones remotas es la misma que para consultas remotas. Se añade el nombre del enlace al nombre de la tabla que se quiere actualizar. Por ejemplo, para cambiar la categoría de libros en la tabla remota Libro, podemos ejecutar el comando UPDATE tal como se muestra a continuación: UPDATE Libro@CONEXION_REMOTA

SET Categoria = 'JUVENIL'

WHERE Categoria = 'INFANTIL';

Podemos usar subconsultas en la parte SET del comando UPDATE. En las subconsultas podemos referenciar tanto tablas locales como remotas aplicando la sintaxis correspondiente. Se muestra un ejemplo a continuación: UPDATE Libro@CONEXION_REMOTA /* en base de datos remota */

SET Categoria =

(SELECT Categoria

FROM Libro@CONEXION_REMOTA /* en base de datos remota */

WHERE Titulo = 'Alicia')

WHERE Titulo = 'Pincho';

Si en la subconsulta no se usase el nombre de enlace, entonces se usaría una tabla Libro de la base de datos local.

8.2. Usando sinónimos para transparencia de localización.

Durante la vida útil de una aplicación, los datos con mucha probabilidad se moverán de una base de datos a

Page 219: Java y Oracle 11g

Oracle /219

otra, de un host a otro. Por lo tanto, simplificará el mantenimiento de la aplicación que la posición física exacta de un objeto de base de datos esté protegida del usuario (y de la aplicación). El mejor modo de implementar esta transparencia de localización es usando sinónimos. En vez de escribir aplicaciones (o informes de SQL*Plus) que contengan consultas especificando el propietario de las tablas, como la siguiente consulta: SELECT * FROM Empledo.Libro;

Podemos crear un sinónimo para esta tabla y entonces usar el sinónimo en la consulta, como se hace a continuación: CREATE PUBLIC SYNONYM Libro FOR Empleado.Libro;

/

SELECT * FROM Libro;

La lógica requerida para encontrar los datos de esta manera ha sido trasladada de la aplicación a la base de datos. Mover la lógica de localización de la tabla a la base de datos será un beneficio si alguna vez necesitamos mover la tabla de un esquema a otro. Además de ocultar el propietario de la tabla a la aplicación, podemos ocultar la localización física de los datos que usan los enlaces de base de datos y sinónimos. Usando sinónimos locales para tablas remotas creamos una capa de lógica entre la aplicación y la base de datos. Por ejemplo, el sinónimo local Libro se define a continuación para referenciar una tabla localizada en otra base de datos remota sobre un host diferente. Si la tabla se mueve en el futuro, sólo necesitaremos cambiar el enlace; el código de la aplicación, cuando usa sinónimos, no necesita ser cambiado. CREATE PUBLIC SYNONYM Libro

FOR Libro@CONEXION_REMOTA;

Si la cuenta remota usada por el enlace no es la propietaria del objeto referenciado, tenemos dos opciones. Primero, podemos referenciar un sinónimo disponible en la base de datos remota: CREATE PUBLIC SYNONYM Libro

FOR Libro_Syn@CONEXION_REMOTA;

Aquí, Libro_Syn, en la cuenta remota, es un sinónimo para la tabla Libro remota. Segundo, podemos incluir el nombre del propietario remoto cuando creamos el sinónimo local, tal como se muestra a continuación: CREATE PUBLIC SYNONYM Libro

FOR Empleado.Libro@CONEXION_REMOTA;

Estos dos ejemplo producirán la misma funcionalidad en nuestras consultas, pero hay una diferencia entre ellas. El segundo ejemplo, que incluye el nombre del propietario, es potencialmente más difícil de mantener ya que no usa un sinónimo en la base de datos remota, y si el objeto remoto se mueve posteriormente invalidará nuestro sinónimo local.

8.3. Usando la pseudo-columna «USER» en vistas.

La pseudo-columna USER es muy útil cuando usamos métodos de acceso remoto a datos. Por ejemplo, podemos querer que no todos los usuarios remotos vean todos los registros de una tabla. Para resolver este problema podemos pensar en los usuarios remotos como usuarios especiales de nuestra base de datos. Para forzar la restricción sobre los datos necesitamos crear una vista a la que puedan acceder cuentas remotas. Pero, ¿qué podemos usar en la cláusula WHERE para restringir apropiadamente los registros? La pseudo-columna USER, combinada con los nombres de usuario apropiados permite forzar estas restricciones. La pseudo-columna USER es una "columna" que retorna el usuario actual de Oracle cuando es seleccionada, pero no es una columna actual de la tabla. Así, si una columna de la tabla contiene nombres de usuarios, estos valores pueden compararse con el valor de la pseudo-columna USER para restringir los registros, tal como se muestra a continuación. En este ejemplo, se consulta la columna Nombre en cada registro. Si el valor de la primera parte de la columna Nombre es el mismo que el nombre del usuario que realizó la consulta, se retornarán estos registros. CREATE VIEW Mis_Libros AS

SELECT * FROM Libro WHERE SUBSTR(Nombre, 1, INSTR(Nombre,'') - 1) = USER;

Cuando restrinjamos el acceso remoto los registros de nuestra tabla, primero debemos considerar qué columnas serían las mejores para usar en la restricción. Normalmente hay divisiones lógicas en los datos de nuestra tabla, como un departamento o una región. Por cada división diferente crearemos una cuenta de usuario en nuestra base de datos local. Para este ejemplo, añadiremos la columna Region a la tabla Libro. Ahora habilitaremos la lista de libros para varias localizaciones distribuidas en una única tabla:

Page 220: Java y Oracle 11g

Oracle /220

ALTER TABLE Libro

ADD (Region VARCHAR2(10));

Supongamos que tenemos 4 regiones representadas en nuestra tabla Libro, y que hemos creado una cuenta de Oracle para cada región (asumamos que las regiones se llaman NORTE, SUR, ESTE, OESTE). Para cada una de estas regiones se crea una base de datos. Podemos entonces crear enlaces de base de datos para que sean usados por los usuarios específicos en nuestra base de datos local. Por ejemplo, los miembros del departamento SUR deberían usar el enlace mostrado a continuación: CREATE DATABASE LINK Enlace_Sur

CONNECT TO SUR IDENTIFIED BY contraseña

USING 'HQ';

Cuando los usuarios remotos consultan a través de los enlaces (como el enlace Enlace_Sur) son registrados dentro de la base de datos HQ con el nombre de la región. Por lo tanto, el valor de la columna USER para cualquier tabla que el usuario consulte será SUR. Ahora creamos una vista de nuestra tabla base, comparando la pseudo-columna USER al valor de la columna Region: CREATE OR REPLACE VIEW Libro_Restringido

AS SELECT * FROM Libro WHERE Region = USER;

Un usuario que se conecte a través del enlace Enlace_Sur (y por tanto se registra remotamente como el usuario SUR) debería sólo ser capar de ver los registros cuya región sea 'SUR'. Este tipo de restricción también puede realizarse en la base de datos remota en vez de en la base de datos donde reside la tabla. Los usuarios de la base de datos remota pueden crear vistas sobre sus bases de datos de la siguiente forma: CREATE OR REPLACE VIEW Sur_Libros

AS SELECT * FROM Libro@CONEXION_REMOTA WHERE Region = 'SUR';

En este caso, la restricción sobre Region está todavía vigente, pero es administrada localmente. La elección entre estos dos tipos de opciones de restricción (local o remota) se basa en el número de cuentas requeridas para la restricción. Para asegurar nuestra base de datos de producción, deberíamos limitar los permisos concedidos a las cuentas usadas en enlaces de base de datos. Concediendo estos permisos a través de roles, y usando vistas (con la cláusula READ ONLY o WITH CHECK OPTION) conseguimos limitar la capacidad de estas cuentas para realizar cambios no autorizados en los datos.

8.4. Enlaces dinámicos: usando el comando de copia de SQL*Plus.

El comando de copia de SQL*Plus permite que los datos sean copiados entre bases de datos (o dentro de la misma base de datos) a través de SQL*Plus. Aunque nos permite seleccionar las columnas a copiar, trabaja mejor cuando se eligen todas las columnas de la tabla. El gran beneficio de usar este comando es la habilidad de confirmar después de que cada conjunto de datos ha sido procesado. Esto a su vez genera transacciones que son de un tamaño manejable.

Nota. El comando de copia no ha cambiado desde Oracle8i. Este comando quedará obsoleto en futuras versiones.

Consideremos el caso de una tabla grande, como Libro. Supongamos que la tabla Libro tiene 100.000 registros que usan un espacio total de 100MB, y necesitamos hacer una copia de esta tabla en otra base de datos. La opción más sencilla consiste en crear un enlace de base de datos y entonces usar este enlace en un comando CREATE TABLE … AS SELECT, tal como se muestra a continuación: CREATE DATABASE LINK CONEXION_REMOTA

CONNECT TO Empleado IDENTIFIED BY contraseña

USING 'HQ';

/

CREATE TABLE Libro AS

SELECT * FROM Libro@CONEXION_REMOTA;

El primer comando crea el enlace de base de datos, y el segundo crea una nueva tabla basada en todos los datos de la tabla remota. Desafortunadamente, esta opción crea una transacción muy larga (todos los 100.000 registros serán insertados en la nueva tabla dentro de una única transacción), que pone una carga grande sobre las estructuras internas de Oracle llamada segmentos de rollback. Los segmentos de rollback y la funcionalidad de deshacer gestionada por el sistema almacenan la imagen previa de los datos hasta que los nuevos datos son

Page 221: Java y Oracle 11g

Oracle /221

confirmados en la base de datos. Ya que esta tabla está siendo poblada en una única operación de inserción, se genera una transacción larga que puede exceder el espacio disponible en los segmentos de rollback actuales. Este fallo provocará que la creación de la tabla falle. Para romper la transacción en entradas pequeñas se usa el comando COPY de SQL*Plus, el cual tiene la siguiente sintaxis: COPY FROM

[usuario_remoto/contraseña_remota@cadena_de_conexión]

[TO usuario/contraseña@cadena_de_conexión]

{APPEND|CREATE|INSERT|REPLACE}

nombre_tabla USING subconsulta;

Si la cuenta actual es la destinataria de los datos copiados, la palabra TO y el usuario, contraseña y cadena de conexión local no son necesarios. Si la cuenta actual es el origen de los datos copiados, la información de la conexión remota no es necesaria. Para asignar el tamaño de la transacción se usa el comando SET de SQL*Plus para dar un valor al parámetro ARRAYSIZE. Esto determina el número de registros que serán enviados en cada lote. El parámetro COPYCOMMIT le dice a SQL*Plus cuántos lotes deberían ser confirmados de cada vez. El siguiente script SQL*Plus realiza la misma copia de la tabla Libro; sin embargo, rompe la transacción simple en varias transacciones. En este ejemplo, los datos son confirmados después de cada 1.000 registros. Esto reduce el tamaño de los segmentos de rollback de la transacción de 100MB a 1MB. SET COPYCOMMIT 1

SET ARRAYSIZE 1000

COPY FROM Empleado/contraseña@HQ -

CREATE Libro -

USING -

SELECT * FROM Libro

Nota. Excepto para la última línea, cada línea del comando COPY debe terminar con un guión porque es un comando de SQL*Plus.

Hay varias opciones dentro del comando COPY:

Opción Explicación

APPEND Inserta los registros dentro de la tabla destino. Automáticamente crea la tabla si no existe previamente.

CREATE Crea la tabla y entonces inserta los registros.

INSERT Inserta los registros dentro de la tabla destino si existe; si no existe retorna un error. Cuando se usa

INSERT, deben especificarse todas las columnas en la subconsulta.

REPLACE Borra la tabla destino existente y la reemplaza con una nueva tabla con los datos copiados.

La interacción proporcionada por el comando COPY puede ser confusa al principio. Después de que se complete la última confirmación, la base de datos informa al usuario del número de registros que han sido confirmados en el último lote. No informa del número total de registros confirmados (a menos que se haya confirmado un único registro).

8.5. Conectándose a una base de datos remota.

Además de la conexión entre base de datos descrita previamente, podemos conectarnos directamente a una base de datos remota a través de la herramienta SQL*PLUS de Oracle. Por lo tanto, en vez de escribir: SQLPLUS usuario/contraseña

Y acceder a nuestra base de datos local, podemos ir directamente a una base de datos remota. Para hacer esto, escribiremos nuestro nombre de usuario y contraseña junto con la cadena de conexión de Oracle Net para bases de datos remotas: SQLPLUS usuario/contraseña@HQ

Este comando nos registrará directamente en la base de datos HQ. La configuración host para este tipo de registro se muestra en la siguiente figura.

Page 222: Java y Oracle 11g

Oracle /222

El host periférico tiene la herramientas de Oracle (como SQL*Plus) y está ejecutando Oracle Net, y el host central ejecuta Oracle Net y una base de datos de Oracle. Puede haber o no una base de datos en el host periférico; especificando la cadena de conexión a la base de datos remota se fuerza a Oracle a ignorar cualquier base de datos local.

9. Vistas materializadas.

Para aumentar el rendimiento de una aplicación, podemos hacer copias locales de tablas remotas que usan datos distribuidos, o crear tablas de resumen basadas en operaciones de agrupación. Oracle proporciona las vistas materializadas para almacenar copias de datos o agregaciones. Se pueden usar las vistas materializadas para replicar todo o parte de una única tabla, o para replicar el resultado de una consulta a través de varias tablas; el refresco de los datos replicados pueden hacerlo automáticamente la base de datos cada cierto intervalo de tiempo.

9.1. Funcionalidad.

Las vistas materializadas son copias (también conocidas como réplicas) de datos, basadas en consultas. En su forma más simple, una vista materializada puede ser obtenida a partir de una tabla creada por un comando como el siguiente: CREATE TABLE Local_Libros AS

SELECT * FROM Libros@REMOTE_CONNECT;

En este ejemplo, se crea una tabla llamada Local_Libros en la base de datos local y se puebla con los datos de una base de datos remota (definida por el enlace de base de datos llamado REMOTE_CONNECT). Una vez creada la tabla Local_Libros, los datos deberán ser sincronizados con la tabla maestra (Libros@REMOTE_CONNECT). También los usuarios locales deberán poder actualizar Local_Libros, sin tener que preocuparse de las complicaciones de sincronización con la tabla maestra. A pesar de estos problemas de sincronización, hay ventajas en replicar los datos de este modo. Al crear copias locales de datos remotos podemos mejorar el rendimiento de consultas distribuidas, particularmente si los datos de la tabla maestra no cambian frecuentemente. Podemos usar también el proceso de creación de la tabla local para restringir las filas retornadas, restringir las columnas retornadas o generar nuevas columnas. Ésta es una estrategia común en entornos de tomas de decisiones, en los cuales se usan consultas complejas para periódicamente reunir datos dentro de tablas resumen para usar durante análisis. Las vistas materializadas automatizan la replicación de datos y los procesos de refresco. Cuando se crea una vista materializada, se establece un intervalo de refresco para planificar refrescos y replicar datos. Se pueden evitar actualizaciones locales, y se pueden usar refrescos basados en transacciones. Este tipo de refrescos envían desde la base de datos maestra sólo aquellas filas que han cambiado.

9.2. Permisos requeridos.

Para crear una vista materializada debemos tener los permisos necesarios para crear los objetos subyacentes que se usarán. 9.2.1. Permisos de sistema. Debemos tener el permiso CREATE MATERIALIZED VIEW, así como los permisos CREATE TABLE o CREATE

ANY TABLE. Además debemos tener el permiso de sistema UNLIMITED TABLESPACE o una cuota de espacio suficiente en un tablespace local. Para crear una vista materializada refrescada-ante-confirmación debemos tener también el permiso de sistema ON COMMIT REFRESH sobre cualquier tabla involucrada de la que no seamos propietario, o el permiso de sistema ON COMMIT REFRESH. Las vistas materializadas de tablas remotas requieren consultas de tablas remotas; por lo tanto, debemos tener permisos para usar un enlace de base de datos que acceda a la base de datos remota. El enlace que usemos

Herramientas Oracle + Oracle Net SQLPULS usuario/contraseña@HQ

Oracle Database + Oracle Net

Base de datos HQ

SQL>

Host Central Host Periférico

Page 223: Java y Oracle 11g

Oracle /223

puede ser público o privado. Si el enlace es privado necesitamos tener el permiso de sistema CREATE

DATABASE LINK para crear el enlace de base de datos. Si estamos creando vistas materializadas para tener las ventajas de la funcionalidad de "rescritura de consulta" (en la cual el optimizador elige dinámicamente tomar los datos de la vista materializada en vez de la tabla subyacente), debemos tener el permiso QUERY REWRITE. Si las tablas están en otro esquema de usuario debemos tener el permiso GLOBAL QUERY REWRITE. Si la vista materializada es creada con la opción ON

COMMIT REFRESH, debemos tener el permiso de sistema ON COMMIT REFRESH o el permiso de objeto ON

COMMIT REFRESH sobre cada tabla fuera de nuestro esquema. 9.2.2. Permisos de tabla. Cuando se crean vistas materializadas podemos referenciar tablas en bases de datos remotas mediante enlaces de base de datos. La cuenta que usa el enlace en la base de datos remota debe tener acceso a las tablas y vistas usadas por el enlace. No podemos crear vistas materializadas basadas en objetos propiedad del usuario SYS. Dentro de una base de datos local, podemos conceder el permiso SELECT sobre una vista materializada a otros usuarios locales. Ya que las vistas materializadas son sólo para leer (aunque puedan ser actualizables), no son necesarios permisos adicionales. Si creamos una vista materializada actualizable, debemos conceder el permiso UPDATE tanto a la vista como a la tabla local subyacente.

9.3. Solo-lectura contra actualizable.

Una vista materializada de solo lectura no puede trasladar cambios en los datos a la tabla origen en la que está basada. Una vista materializada actualizable puede enviar cambios a la tabla origen. Aunque parece que la distinción está clara, las diferencias subyacentes entre estos dos tipos de vistas materializadas no son tan simples. Una vista materializada de solo lectura se implementa como un comando CREATE TABLE AS SELECT. Cuando ocurre una transacción dentro de la tabla origen, la transacción es enviada a la vista materializada de solo lectura. Así, el método por el cual las filas en la vista materializada cambian está controlado (las filas de la vista materializada sólo cambian después de un cambio en la tabla origen). En una vista materializada actualizable hay menos control sobre el método por el cual cambian las filas en la vista. Las filas pueden ser cambiadas según los cambios en la tabla origen, o se pueden cambiar las filas directamente en la vista. Por lo tanto, necesitamos enviar filas desde la tabla origen a la vista materializada y viceversa. Ya que existen varios métodos de cambios, existen varios orígenes (mencionado como configuración de multi-origen). Durante la transferencia de registros desde la vista a la tabla origen, necesitamos decidir cómo resolver conflictos. Por ejemplo, ¿qué pasa si un registro con ID=1 es eliminado en la vista materializada, mientras que en el sitio origen se crea un registro en otra tabla que referencia (mediante integridad referencial) el registro con ID=1? No podemos eliminar el registro con ID=1 de la tabla origen mientras exista un registro "hijo" relacionado. ¿Cómo planificar la resolución de estos conflictos? Las vistas materializadas de solo lectura evitan la necesidad de resolver este tipo de conflictos ya que fuerzan que todas las transacciones ocurran en la tabla origen. Esto puede limitar nuestra funcionalidad, pero es una solución apropiada para la mayoría de necesidades de replicación. Si necesitamos una replicación multi-origen véanse las reglas de la guía de replicación avanzada.

9.4. Sintaxis de creación de vistas materializadas.

La sintaxis básica de creación de vistas materializadas se muestra a continuación. CREATE MATERIALIZED VIEW [usuario.]nombre Cabecera

[ ORGANIZATION INDEX cláusula_iot]

[ { { cláusulas de atributos de segmento }

| CLUSTER cluster (columna [, columna] ...) }

[ {cláusula de particionado | cláusula paralelo | cláusula construcción } ]

| ON PREBUILT TABLE [ {WITH | WITHOUT} REDUCED PRECISION ] ]

[ USING INDEX

[ { cláusulas atributos físicos | cláusula tablespace }

[ cláusula atributos físicos | cláusula tablespace ]

| USING NO INDEX ]

Parámetros de almacenamiento

[ cláusula refresco ] Opciones de refresco

[ FOR UPDATE ] [{DISABLE | ENABLE} QUERY REWRITE]

AS subconsulta;

consulta

El comando CREATE MATERIALIZED VIEW tiene cuatro secciones mayores. La primera sección es la cabecera, en la cual se indica el nombre de la vista materializada:

Page 224: Java y Oracle 11g

Oracle /224

CREATE MATERIALIZED VIEW [usuario.]nombre

La vista será creada en nuestra cuenta (esquema) a menos que especifiquemos un nombre de usuario en la cabecera. En la segunda sección se especifican los parámetros de almacenamiento. Estos parámetros se aplican a la tabla que será creada en la base de datos local. Si los datos han sido ya replicados en una tabla local podemos usar la cláusula PREBUILT TABLE para decirle a Oracle que use esta tabla como una vista materializada.

Nota. Podemos especificar los parámetros de almacenamiento para ser usados por el índice que es automáticamente creado sobre la vista materializada.

En la tercera sección se asignan las opciones de refresco. La sintaxis para esta cláusula es la siguiente: { REFRESH

{ { FAST | COMPLETE | FORCE }

| ON { DEMAND | COMMIT }

| { START WITH | NEXT } DATE

| WITH { PRIMARY KEY | ROWID }

| USING

{ DEFAULT [ MASTER | LOCAL ] ROLLBACK SEGMENT

| [ MASTER | LOCAL ] ROLLBACK SEGMENT segmento_Rollback

}

[ DEFAULT [ MASTER | LOCAL ] ROLLBACK SEGMENT

| [ MASTER | LOCAL ] ROLLBACK SEGMENT segmento_Rollback

] . . .

}

[ { FAST | COMPLETE | FORCE }

| ON { DEMAND | COMMIT }

| { START WITH | NEXT } DATE

| WITH { PRIMARY KEY | ROWID }

| USING

{ DEFAULT [ MASTER | LOCAL ] ROLLBACK SEGMENT

| [ MASTER | LOCAL ] ROLLBACK SEGMENT segmento_Rollback

}

[ DEFAULT [ MASTER | LOCAL ] ROLLBACK SEGMENT

| [ MASTER | LOCAL ] ROLLBACK SEGMENT segmento_Rollback

] . . .

] . . .

| NEVER REFRESH

}

La opción REFRESH especifica el mecanismo que Oracle debe usar cuando se refresque la vista materializada. Las tres opciones disponibles son FAST, COMPLETE y FORCE. El refresco FAST está sólo disponible si Oracle puede hacer corresponder las filas de la vista directamente con las filas de la tabla(s) origen; Oracle usa tablas llamadas registros de vistas materiaplizadas para enviar filas específicas desde la tabla origen a la vista materializada. El refresco COMPLETE trucan los datos y re-ejecuta la consulta base de la vista materializada para repoblarla. La opción FORCE le dice a Oracle que use un refresco rápido si es posible; si no es posible usa un refresco completo. Si hemos creado una vista materializada simple pero queremos usar el refresco completo, debemos especificar REFRESH COMPLETE en el comando. Dentro de esta sección podemos especificar el mecanismo usado para relacionar valores en la vista materializada con la tabla origen (si deben usarse valores RowId o la clave primaria). Por defecto, se usan las claves primarias. Si la consulta origen para la vista referencia una combinación o una única tabla podemos usar la opción ON

COMMIT para controlar la replicación de los cambios. Si se usa ON COMMIT, los cambios serán enviados desde el origen a la réplica cuando los cambios son confirmados sobre la tabla origen. Si especificamos ON DEMAND, el refresco ocurrirá cuando ejecutemos manualmente un comando REFRESH. La cuarta sección es la consulta que usará la vista materializada: [ FOR UPDATE ] [{DISABLE | ENABLE} QUERY REWRITE]

AS subconsulta ;

Si especificamos FOR UPDATE, la vista será actualizable; si no, será de solo lectura. La mayoría de vistas materializadas son réplicas de sólo lectura de la tabla origen. Si usamos vistas actualizables debemos tener en

Page 225: Java y Oracle 11g

Oracle /225

cuenta las replicaciones en los dos sentidos y la resolución de conflictos en los datos.

Nota. La consulta que forma el origen de la vista materializada no debería usar las pseudo-columnas USER y SYSDATE.

El siguiente ejemplo crea una vista materializada de solo lectura llamada LOCAL_LIBRO en la base de datos local, basada en una tabla remota llamada LIBRO que es accesible mediante el enlace CONEXIÓN_REMOTA. La vista es ubicada en el tablespace USERS. CREATE MATERIALIZED VIEW LOCAL_LIBRO

TABLESPACE USERS

REFRESH FORCE

START WITH SYSDATE NEXT SYSDATE+7

WITH PRIMARY KEY

AS

SELECT * FROM LIBRO@CONEXION_REMOTA;

Oracle responde con Vista materializada creada.

El comando del ejemplo precedente creará una vista materializada de sólo lectura llamada LOCAL_LIBRO. La tabla subyacente será creada en el tablespace USERS. Podemos poner los registros de la vista materializada en tablespaces aparte de los que soportan la vista materializada. Se ha especificado la opción de refresco FORCE porque no existen registros de la vista materializada sobre la tabla origen; Oracle intentará usar un refresco rápido pero sólo usará un refresco completo hasta que se cree el registro de vista materializada. La consulta asociada a la vista especifica que toda la tabla LIBRO, sin modificaciones, será copiada a la base de datos local. A la vez que la vista materializada LOCAL_LIBRO es creada, su tabla subyacente es poblada con los datos de LIBRO. A partir de ese momento la vista materializada será refrescada cada 7 días. Los parámetros de almacenamiento no indicados serán aplicados por defecto según el tablespace USERS. El siguiente ejemplo crea una vista materializada llamada LOCAL_CATEGORIA_COUNT en una base de datos local, basada en una tabla remota llamada LIBRO en una base de datos accedida mediante el enlace CONEXION_REMOTA. CREATE MATERIALIZED VIEW LOCAL_CATEGORIA_COUNT

TABLESPACE USERS

REFRESH FORCE

START WITH SYSDATE NEXT SYSDATE+7

AS

SELECT NombreCategoria, COUNT(*) CountPorCat

FROM LIBRO@CONEXION_REMOTA

GROUP BY NombreCategoria;

La consulta de esta vista materializada cuenta el número de libros en cada categoría de la tabla remota LIBRO. Hay unos pocos importantes puntos a notar acerca de estos dos ejemplos previos:

• La consulta de agrupación usada en la vista materializada LOCAL_CATEGORIA_COUNT podría ser realizada en SQL*Plus usando la vista LOCAL_LIBRO. Esto es, la operación de agrupación puede hacerse fuera de la vista materializada. • Ya que LOCAL_CATEGORIA_COUNT usa una cláusula GROUP BY, es una vista compleja si sólo puede usar el refresco completo. LOCAL_LIBRO, como vista simple, puede usar refresco rápido.

Estas dos vistas materializadas de ejemplo referencian la misma tabla. Ya que una de las vista replica todas las columnas y filas de la tabla origen, la segunda vista puede parecer redundante. Sin embargo, algunas veces el segundo tipo de vista, la compleja, es el más utilizado de los dos. ¿Cómo es esto así? Primero, recordemos que estas vistas materializadas son usadas para servir consultas necesarias para usuarios locales. Si estos usuarios siempre realizan operaciones de agrupación en sus consultas, y las columnas de agrupación son fijas, entonces LOCAL_CATEGORIA_COUNT puede ser más útil. Segundo, si el volumen de transacción de la tabla origen LIBRO es muy alto, o la tabla LIBRO es muy pequeña, puede no haber diferencias en los tiempo de refresco rápido y completo. Las vistas materializadas más apropiadas son las que resultan más productivas para los usuarios. 9.4.1. Tipos de vistas materializadas. Las vistas materializadas mostradas en los ejemplos previos ilustran sobre dos tipos de vistas materializadas. En el primero, la vista materializada crea una copia local de datos remotos, sin agrupación. En el segundo, se aplica una agrupación. En ambas vistas podemos ampliar la consulta base para incluir combinaciones de

Page 226: Java y Oracle 11g

Oracle /226

tablas. Lo diferencia importante en este caso es el uso de agrupación en el segundo ejemplo. Un tercer tipo de vista materializada es una vista materializada anidada (una vista materializada cuya definición es la base para otra vista materializada). El tipo de vista materializada tendrá importancia en nuestra habilidad de realizar refrescos rápidos. Un refresco rápido actualizará la vista sólo con las filas que han cambiado en las tablas origen desde el último refresco. Si no podemos realizar un refresco rápido, tendremos que usar un refresco completo, el cual es normalmente más expansivo en términos de tiempo y recursos. Los refrescos rápidos requieren el uso de registros de vista materializada sobre todas las tabla referenciadas en la consulta base de la vista. Si la vista contiene una agrupación, un refresco rápido será posible si la consulta contiene todo el grupo por columnas y debe haber un COUNT(*) y COUNT(columna) sobre cualquier columna del GROUP BY. Si la vista materializada contiene sólo combinaciones pero no agrupaciones, el refresco rápido es posible después de cualquier inserción, actualización o borrado sobre las tablas base. Las columnas RowID para cada tabla debe estar presente en la lista del SELECT de la consulta base, y todas las tablas referenciadas deber tener registros de vista materializada. Debido a que hay que enviar los cambios incrementales desde las tablas referenciadas a la vista materializada, los refrescos rápidos normalmente representan el camino más rápido para actualizar los datos de nuestras vistas materializadas. 9.4.2. Vistas materializadas basadas en «RowID» y en clave primaria. Podemos basar las vistas materializadas tanto sobre los valores de la clave primaria como sobre los valores de RowID de las tablas base. Debemos decidir entre estas opciones según varios factores:

• Estabilidad del sistema. Si el sitio origen no es estable, entonces podemos necesitar realizar recuperaciones de base de datos que involucren a las tablas origen de la vista. Cuando se usan las utilidades de Oracle Data Pump Export e Import para realizar recuperaciones, los valores RowID de las filas cambiarán. Si el sistema requiere frecuentes exportaciones e importaciones, deberíamos usar vistas basadas en clave primaria. • Tamaño de la tabla de registro de vista materializada. Oracle permite almacenar los cambios de las tablas origen en tablas separadas llamadas registros de vista materializada. Si la clave primaria consiste de varias columnas, esta tabla de registro para una vista basada en clave primaria puede ser considerablemente más grande que la vista basada en RowID. • Integridad referencial. Para usar vistas basadas en clave primaria, debemos definir una clave primaria sobre la tabla origen. Si no podemos definir una clave primaria debemos usar vistas basadas en RowID.

9.4.3. Usando tablas predefinidas. Cuando creamos vistas materializadas podemos especificar BUILD IMMEDIATE para poblar la vista materializada inmediatamente o BUILD DEFERRED para poblar la vista más tarde (a través de un refresco). Si necesitamos gestionar con cuidado las transacciones que pueblan inicialmente la vista materializada, podemos crear una tabla que tenga la misma estructura que la vista materializada a poblar. Cuando la tabla está completamente cargada y apropiadamente indexada, se usa la cláusula ON PREBUILT TABLE del comando CREATE MATERIALIZED VIEW. La tabla y la vista materializada deben tener el mismo nombre, y la tabla debe tener las misma columnas y tipos de datos (podemos especificar precisiones reducidas para acomodar diferencias en precisión). La tabla puede contener columnas adicionales no gestionadas. Una vez que la tabla ha sido registrada como una vista materializada, podemos mantenerla mediante refrescos, y el optimizador puede usarla en operaciones de "rescritura de consultas". Para que la "rescritura de consulta" trabaje apropiadamente sobre una tabla predefinida, debemos asignar el parámetro de inicialización QUERY_REWRITE_INTEGRITY a STALE_TOLERATED o TRUSTED. 9.4.4. Indexando tablas de vistas materializadas. Cuando creamos una vista materializada, Oracle crea una tabla base local que contiene los datos que satisfacen la consulta base. Ya que los datos han sido replicados con algún propósito en mente (normalmente para mejorar el rendimiento en la base de datos o la red), es importante llevar a cabo a este objetivo después de que la vista materializada ha sido creada. La mejora del rendimiento para consultas se obtiene normalmente mediante el uso de índices. Las columnas que son frecuentemente usadas en cláusulas WHERE de consultas deberían se indexadas; si un conjunto de columnas es frecuentemente accedida en consultas, entonces se puede crear un índice concatenado sobre este conjunto de columnas. Oracle no crea automáticamente índices para vistas materializadas complejas sobre columnas en vez de la clave primaria. Necesitamos crear estos índices manualmente. Para crear índices en nuestra tabla base local se usa el comando CREATE INDEX. No se aconseja crear ninguna restricción sobre la tabla origen de la vista

Page 227: Java y Oracle 11g

Oracle /227

materializada; Oracle mantiene las relaciones basadas en restricciones sobre las tablas origen. Ya que no se crea ningún índice sobre las columnas que los usuarios consultan sobre la vista materializada, deberíamos crear índices sobre la tabla local base de la vista materializada.

9.5. Usando vistas materializadas para modificar rutas de ejecución de consultas.

Para una gran base de datos, una vista materializada puede ofrecer varios beneficios de rendimiento. Podemos usar vistas materializadas para influir en el optimizador a cambiar la ejecución de rutas para consultas. Esta funcionalidad, llamada "rescritura de consulta", permite al optimizador usar una vista materializada en vez de la tabla consultada por la vista materializada, aun si la vista materializada no es nombrada en la consulta. Por ejemplo, si tenemos una tabla grande VENTA, podemos crear una vista materializada que sume los datos de VENTA por región. Si un usuario consulta la tabla VENTA para obtener la suma de datos por región, Oracle puede redireccionar esta consulta para que use la vista materializada en vez de la tabla VENTA. Como resultado, podemos reducir el número de accesos a nuestras tablas grandes, mejorando así el rendimiento del sistema.

Nota. Debemos especificar ENABLE QUERY REWRITE en la definición de la vista materializada para que la vista sea usada como parte de una operación de rescritura de consulta.

Para usar la capacidad de rescritura de consulta efectivamente, debemos crear una dimensión que defina la jerarquía dentro de los datos de la tabla. Para ejecutar el comando CREATE DIMENSION necesitamos tener el permiso del sistema CREATE DIMENSION. Podemos crear una dimensión que soporte la jerarquía entre dos tablas de ejemplo PAIS y CONTINENTE: CREATE DIMENSION GEOGRAFIA

LEVEL IdPais IS PAIS.Pais

LEVEL IdContinente IS CONTINENTE.Continente

HIERARCHY Pais_Rollup (

IdPais CHILD OF IdContinente

JOIN KEY PAIS.Continente REFERENCES IdContinente);

Para habilitar una vista materializada para rescritura de consultas, todas las tablas origen de la vista deben estar en el esquema de la vista, y debemos tener el permiso de sistema QUERY REWRITE. Si la vista y las tablas están en esquemas separados, debemos tener el permiso de sistema GLOBAL QUERY REWRITE. En general, debemos crear vistas materializada en el mismo esquema que las tablas origen; si no es así, necesitaremos gestionar los permisos y concesiones requeridas para crear y mantener la vista materializada. Nota. Podemos habilitar o deshabilitar rescritura de consultas en el nivel de la sentencia SQL mediante los modificadores REWRITE y NOREWRITE. Cuando usamos el modificador REWRITE podemos especificar vistas materializadas que debería considerar el optimizador.

Nota. Las decisiones de rescritura de consultas están basadas en el coste de las diferentes rutas de ejecución, así que nuestras estadísticas deberían mantenerse.

Para que la rescritura de consultas sea posible, debemos asignar los siguientes parámetros de inicialización: • OPTIMIZER_MODE = ALL_ROWS o FIRST_ROWS • QUERY_REWRITE_ENABLED = TRUE • QUERY_REWRITE_INTEGRITY = STALE_TOLERATED, TRUSTED, o ENFORCED

Por defecto, QUERY_REWRITE_INTEGRITY es asignado a ENFORCED; en este modo todas las restricciones deben ser validadas. El optimizador sólo usar refrescos de datos desde vistas materializadas y sólo usar estas relaciones que están basadas en restricciones ENABLED VALIDATED primarias, únicas, o de clave foránea. En modo TRUSTED, el optimizador garantiza que los datos en la vista materializada sean frescos y las relaciones declaradas en dimensiones y restricciones sean correctas. En modo STALE_TOLERATED, el optimizador usa vistas materializadas que son válidas pero contienen datos antiguos, así como aquellas que contienen datos frescos. Si asignamos QUERY_REWRITE_ENABLED a FORCE, el optimizador rescribirá consultas para usar vistas materializadas cuando el coste estimado de la consulta original sea bajo. Si ocurre la rescritura de consultas, el plan estimado para la consulta catalogará la vista materializada como una de los objetos accedidos, con una operación catalogada como "MAT_VIEWREWRITE ACCESS". Podemos usar el procedimiento DBMS_MVIEW.EXPLAIN_REWRITE para ver si la rescritura es posible para una consulta, y qué vistas materializadas estarían involucradas. Si la consulta no puede ser rescrita, el procedimiento documentará las razones. EXPLAIN_REWRITE tiene tres parámetros de entrada: la consulta, el

Page 228: Java y Oracle 11g

Oracle /228

nombre de una vista materializada (opcional), y una sentencia identificadora (opcional). La salida de este procedimiento puede ser almacenada en una tabla. Oracle proporciona el comando CREATE TABLE para la tabla de salida en un script llamado utlxrw.sql en el directorio /rdbms/admin bajo el directorio base de Oracle. El script utlxrw.sql crea una tabla llamada REWRITE_TABLE. Se puede consultar en esta tabla el coste original, el coste de rescritura y la decisión del optimizador. La columna Message mostrará las razones de la decisión del optimizador.

9.6. Usando «DBMS_ADVISOR».

Desde Oracle Database 10g, podemos usar el SQL Access Advisor (Consejero de Acceso SQL) para generar recomendaciones para la creación de índices (y tipos de índices) para mejorar el rendimiento de consultas de combinación y otras. El SQL Access Advisor puede generar recomendaciones para modificar una vista materializada y que así soporte rescritura de consultas y refrescos rápidos. Podemos ejecutar el SQL Access Advisor desde el Administrador de Oracle Enterprise o mediante el paquete DBMS_ADVISOR.

Nota. Para los mejores resultados del paquete DBMS_ADVISOR, deberíamos reunir estadísticas sobre todas las tablas, índices y columnas de join para generar recomendaciones.

Para usar el SQL Access Advisor, tanto desde el Administrador de Oracle Enterprise como con DBMS_ADVISOR, debemos seguir los siguientes pasos:

1. Crear una tarea. 2. Definir la carga de trabajo. 3. Generar recomendaciones 4. Ver e implementar recomendaciones.

Podemos crear una tarea de dos formas: ejecutando el procedimiento DBMS_ADVISOR.CREATE_TASK o usando el procedimiento DBMS_ADVISOR.QUICK_TUNE. La carga de trabajo consiste de una o más sentencias SQL más las estadísticas y atributos que se relacionan con la sentencia. La carga de trabajo puede incluir todas las sentencias SQL de una aplicación. El SQL Access Advisor organiza las entradas en la carga de trabajo de a cuerdo a las estadísticas e importancia en el negocio. La carga de trabajo se crea usando el procedimiento DBMS_ADVISOR.CREATE_SQLWKLD. Para asociar una carga de trabajo con una tarea del consejero se usa el procedimiento DBMS_ADVISOR.ADD_SQLWKLD_REF. Si no se proporciona una carga de trabajo, el SQL Access Advisor puede generar y usar una carga de trabajo hipotética basada en las dimensiones definidas en nuestro esquema. Una vez que existe una tarea y una carga de trabajo asociada con la tarea, podemos generar recomendaciones a través del procedimiento DBMS_ADVISOR.EXECUTE_TASK. El SQL Access Advisor considerará la carga de trabajo y las estadísticas del sistema e intentará generar recomendaciones para afinar la aplicación. Podemos ver estas recomendaciones ejecutando la función DBMS_ADVISOR.GET_TASK_SCRIPT o mediante las vistas del diccionario de datos. Cada recomendación puede ser vista mediante USER_ADVISOR_RECOMMENDATIONS (existen también versiones ALL_ y DBA_). Para relacionar recomendaciones con una sentencia SQL necesitamos usar la vista USER_ADVISOR_SQLA_WK_STMTS y USER_ADVISOR_ACTIONS. Cuando se ejecuta el procedimiento GET_TASK_SCRIPT, Oracle genera un fichero SQL ejecutable que contendrá los comandos necesarios para crear, modificar o borrar los objetos recomendados. Deberíamos revisar el script generado antes de ejecutarlo, particularmente en las especificaciones del tablespace. 9.6.1. Cómo realizar un afinado rápido. Para afinar una única sentencia SQL se usa el procedimiento DBMS_ADVISOR.QUICK_TUNE. El procedimiento QUICK_TUNE tiene dos parámetros de entrada: un nombre de tarea y una sentencia SQL. Usando QUICK_TUNE escudamos al usuario de los pasos involucrados en la creación de cargas de trabajo y tareas mediante DBMS_ADVISOR. Por ejemplo, el siguiente procedimiento evalúa una consulta: EXECUTE DBMS_ADVISOR.QUICK_TUNE(DBMS_ADVISOR.SQLACCESS_ADVISOR, -

'MV_TUNE','SELECT Autor FROM Libro');

Nota. El usuario que ejecute este comando necesita el permiso de sistema ADVISOR.

Las recomendaciones generadas por QUICK_TUNE pueden verse a través de la vista USER_ADVISOR_ACTIONS, pero es más fácil leerlas usando el procedimiento DBMS_ADVISOR para generar un fichero de script. La recomendación es que una vista materializada será creada para soportar esta consulta. Ya que sólo se proporcionó una consulta, no se consideran otros aspectos de la base de datos o la aplicación.

Page 229: Java y Oracle 11g

Oracle /229

Podemos usar el procedimiento CREATE_FILE para automatizar la generación de un fichero que contenga el script necesario para implementar las recomendaciones. Primero, crear un objeto DIRECTORY para contener el fichero: CREATE DIRECTORY SCRIPTS AS 'E:\SCRIPTS';

GRANT READ ON DIRECTORY SCRIPTS TO PUBLIC;

GRANT WRITE ON DIRECTORY SCRIPTS TO PUBLIC;

A continuación, ejecutar el procedimiento CREATE_FILE. Tiene tres variables de entrada: el script (generado mediante GET_TASK_SCRIPT, al cual se pasa el nombre de la tarea), el directorio de salida, y el nombre del fichero que será creado. EXECUTE DBMS_ADVISOR.CREATE_FILE(DBMS_ADVISOR.GET_TASK_SCRIPT('MV_TUNE'), -

'SCRIPTS', 'MV_TUNE.sql');

El fichero MV_TUNE.sql creado por el procedimiento CREATE_FILE contendrá comandos similares a los mostrados a continuación. Dependiendo de la versión específica de Oracle las recomendaciones pueden diferir. Rem Username: EMPLEADO

Rem Task: MV_TUNE

Rem

set feedback 1

set linesize 80

set trimspool on

set tab off

set pagesize 60

whenever sqlerror CONTINUE

CREATE MATERIALIZED VIEW "EMPLEADO"."MV$$_021F0001"

REFRESH FORCE WITH ROWID

ENABLE QUERY REWRITE

AS SELECT PRACTICE.BOOKSHELF.ROWID C1,

"EMPLEADO"."LIBRO"."AUTOR" M1

FROM EMPLEADO.LIBRO;

begin

dbms_stats.gather_table_stats('"PRACTICE"',

'"MV$$_021F0001"',NULL,dbms_stats.auto_sample_size);

end;

/

whenever sqlerror EXIT SQL.SQLCODE

begin

dbms_advisor.mark_recommendation('MV_TUNE',1,'IMPLEMENTED');

end;

/

El procedimiento MARK_RECOMMENDATION permite anotar la recomendación así que puede saltarse durante la generación del script. Acciones válidas para MARK_RECOMMENDATION incluyen ACCEPT, IGNORE, IMPLEMENTED y REJECT. Podemos usar el procedimiento DBMS_ADVISOR.TUNE_MVIEW para generar recomendaciones para la reconfiguración de nuestras vistas materializadas. TUNE_VIEW genera dos conjuntos de resultados de salida: para la creación de nuevas vistas materializadas, y para remover vistas materializadas creadas previamente. El resultado final será un conjunto de vistas materializadas que pueden ser refrescadas rápidamente, reemplazando vistas materializadas que no pueden ser refrescadas rápidamente. Podemos ver la salida de TUNE_MVIEW mediante la vista del diccionario de datos USER_TUNE_MVIEW, o podemos generar su script mediante los procedimientos GET_TASK_SCRIPT y CREATE_FILE.

9.7. Refrescando vista materializadas.

Los datos en una vista materializada puede ser replicados una vez (cuando la se crea la vista) o a intervalos. El comando CREATE MATERIALIZED VIEW permite asignar el intervalo de refresco, delegando la responsabilidad de la planificación y realización de los refrescos a la base de datos.

Page 230: Java y Oracle 11g

Oracle /230

9.7.1. ¿Qué cosas podemos cambiar en los refrescos? Para ver qué cosas de los refrescos y capacidades de rescritura son posibles en nuestras vistas materializadas, podemos consultar la tabla MV_CAPABILITIES_TABLE. Las capacidades pueden cambiar entre versiones, así que deberíamos reevaluar nuestras capacidades de refresco con cada actualización de Oracle. Para crear esta tabla, hay que ejecutar el script utlxmv.sql localizado en el directorio /rdbms/admin bajo el directorio base de Oracle. Las columnas de MV_CAPABILITIES_TABLE son: DESC MV_CAPABILITIES_TABLE

Name Null? Type

----------------------------------------- -------- ----------------

STATEMENT_ID VARCHAR2(30)

MVOWNER VARCHAR2(30)

MVNAME VARCHAR2(30)

CAPABILITY_NAME VARCHAR2(30)

POSSIBLE CHAR(1)

RELATED_TEXT VARCHAR2(2000)

RELATED_NUM NUMBER

MSGNO NUMBER(38)

MSGTXT VARCHAR2(2000)

SEQ NUMBER

Para poblar la tabla MV_CAPABILITIES_TABLE se ejecuta el procedimiento DBMS_MVIEW.EXPLAIN_MVIEW, usando el nombre de la vista materializada como valor de entrada como se muestra a continuación: EXECUTE DBMS_MVIEW.EXPLAIN_MVIEW('local_category_count');

El script de utlxmv.sql proporciona guías de interpretación de los valores de columna, tal como se muestra a continuación: CREATE TABLE MV_CAPABILITIES_TABLE

(STATEMENT_ID VARCHAR(30), -- Client-supplied unique statement identifier

MVOWNER VARCHAR(30), -- NULL for SELECT based EXPLAIN_MVIEW

MVNAME VARCHAR(30), -- NULL for SELECT based EXPLAIN_MVIEW

CAPABILITY_NAME VARCHAR(30), -- A descriptive name of the particular

-- capability:

-- REWRITE

-- Can do at least full text match

-- rewrite

-- REWRITE_PARTIAL_TEXT_MATCH

-- Can do at least full and partial

-- text match rewrite

-- REWRITE_GENERAL

-- Can do all forms of rewrite

-- REFRESH

-- Can do at least complete refresh

-- REFRESH_FROM_LOG_AFTER_INSERT

-- Can do fast refresh from an mv log

-- or change capture table at least

-- when update operations are

-- restricted to INSERT

-- REFRESH_FROM_LOG_AFTER_ANY

-- can do fast refresh from an mv log

-- or change capture table after any

-- combination of updates

-- PCT

-- Can do Enhanced Update Tracking on

-- the table named in the RELATED_NAME

-- column. EUT is needed for fast

-- refresh after partitioned

-- maintenance operations on the table

-- named in the RELATED_NAME column

Page 231: Java y Oracle 11g

Oracle /231

-- and to do non-stale tolerated

-- rewrite when the mv is partially

-- stale with respect to the table

-- named in the RELATED_NAME column.

-- EUT can also sometimes enable fast

-- refresh of updates to the table

-- named in the RELATED_NAME column

-- when fast refresh from an mv log

-- or change capture table is not

-- possible.

POSSIBLE CHARACTER(1), -- T = capability is possible

-- F = capability is not possible

RELATED_TEXT VARCHAR(2000), -- Owner.table.column, alias name, etc.

-- related to this message. The

-- specific meaning of this column

-- depends on the MSGNO column. See

-- the documentation for

-- DBMS_MVIEW.EXPLAIN_MVIEW() for details

RELATED_NUM NUMBER, -- When there is a numeric value

-- associated with a row, it goes here.

-- The specific meaning of this column

-- depends on the MSGNO column. See

-- the documentation for

-- DBMS_MVIEW.EXPLAIN_MVIEW() for details

MSGNO INTEGER, -- When available, QSM message #

-- explaining why not possible or more

-- details when enabled.

MSGTXT VARCHAR(2000), -- Text associated with MSGNO.

SEQ NUMBER); -- Useful in ORDER BY clause when

-- selecting from this table.

Una vez ejecutado el procedimiento EXPLAIN_MVIEW podemos consultar MV_CAPABILITIES_TABLE para determinar nuestras opciones. SELECT Capability_Name, Msgtxt

FROM MV_CAPABILITIES_TABLE

WHERE Msgtxt IS NOT NULL;

Para la vista materializada LOCAL_LIBRO la consulta retornará:

CAPABILITY_NAME MSGTXT

---------------------------------------------- ---------------------------------------------------------------------------

PCT_TABLE relation is not a partitioned table

REFRESH_FAST_AFTER_INSERT the detail table does not have a materialized view log

REFRESH_FAST_AFTER_ONETAB_DML see the reason why REFRESH_FAST_AFTER_INSERT is disabled

REFRESH_FAST_AFTER_ANY_DML see the reason why REFRESH_FAST_AFTER_ONETAB_DML is

disabled

REFRESH_FAST_PCT PCT is not possible on any of the detail tables in the materialized

view

REWRITE_FULL_TEXT_MATCH query rewrite is disabled on the materialized view

REWRITE_PARTIAL_TEXT_MATCH query rewrite is disabled on the materialized view

REWRITE_GENERAL query rewrite is disabled on the materialized view

REWRITE_PCT general rewrite is not possible or PCT is not possible on any of the

detail tables

PCT_TABLE_REWRITE relation is not a partitioned table

Ya que no se especificó la cláusula QUERY REWRITE durante la creación de la vista materializada, las capacidades de rescritura de consultas están desactivadas para LOCAL_LIBRO. Las capacidades de refresco rápido no están soportadas porque la tabla base no tiene un registro de vista materializada. Si cambiamos nuestra vista materializada o la tabla base deberíamos regenerar los datos en MV_CAPABILITIES_TABLE para ver las nuevas capacidades. Además de la falta de registro de vista materializada hay otras restricciones que limitan nuestra habilidad de

Page 232: Java y Oracle 11g

Oracle /232

usar refrescos rápidos: • La vista materializada no debe contener referencias a expresiones no repetitivas como SysDate y RowNum. • La vista materializada no debe contener referencias a tipos de datos RAW o LONG RAW. • Para vistas materializadas basadas en joins, los RowID's de todas las tablas deben ser parte de la lista del SELECT. • Si hay joins externos, todos los joins deben estar conectados por AND's, la cláusula WHERE no debe tener selecciones, y debe existir una restricción UNIQUE sobre las columnas del join de la tabla del INNER JOIN. • Para vistas materializadas basadas en agrupaciones, los registros de vista materializada deben contener todas las columnas de las tablas referenciadas, deben especificar el RowID y cláusulas INCLUDING NEW

VALUES, y deben especificar la cláusula SEQUENCE.

Nota. Podemos especificar una cláusula ORDER BY en el comando CREATE MATERIALIZED VIEW. La cláusula ORDER BY sólo afectará a la creación inicial de la vista; y no afectará a cualquier refresco.

9.7.2. Refrescos automáticos. Consideremos la vista materializada LOCAL_LIBRO descrita previamente. La opción de refresco, definida al crear la vista, se muestra a continuación: CREATE MATERIALIZED VIEW LOCAL_LIBRO

TABLESPACE USERS

REFRESH FORCE

START WITH SYSDATE NEXT SYSDATE+7

WITH PRIMARY KEY

AS

SELECT * FROM LIBRO@CONEXION_REMOTA;

La planificación de refresco tiene tres componentes. Primero, el tipo de refresco (FAST, COMPLETE, NEVER, o FORCE). Refrescos rápidos usan registros de vista materializada para enviar cambios de filas desde la tabla origen a la vista. Refrescos completos borran todas las filas de la vista y la repueblan. La opción FORCE le dice a Oracle que use refresco rápido si es posible, sino se usará refresco completo. La cláusula START WITH le dice a la base de datos cuándo realizar la primera replicación de la tabla origen a la tabla base local. Debemos evaluarla a un punto del tiempo en el futuro. Si no especificamos un tiempo para START WITH pero especificamos un valor NEXT, Oracle usará la cláusula NEXT para determinar el tiempo inicial. Para mantener control sobre nuestra planificación de replicación debemos especificar un valor para la cláusula START WITH. La cláusula NEXT le dice a Oracle cuánto tiempo debe esperar entre cada refresco. Ya que será aplicada a diferentes bases de tiempo en cada refresco, la cláusula NEXT especifica una expresión de fecha en vez de una fecha fija. En el ejemplo precedente la expresión es NEXT SysDate+7

Cada vez que la vista materializada es refrescada, el siguiente refresco se planificará para 7 días después. Aunque la planificación de refresco en este ejemplo es muy simple, podemos usar muchas funciones de fechas de Oracle para personalizar la planificación de refresco. Por ejemplo, si queremos refrescar cada Domingo a partir de la fecha actual, podemos asignar la cláusula NEXT a NEXT NEXT_DAY(TRUNC(SysDate), 'DOMINGO')+12/24

Para que ocurran los refrescos automáticos debemos tener al menos un proceso de refresco de fondo ejecutándose en nuestra base de datos. El proceso de refresco se ejecuta periódicamente y mira si cualquier vista materializada de la base de datos necesita ser refrescada. El número de procesos que se ejecutan en nuestra base de datos se determina en un parámetro de inicialización llamado JOB_QUEUE_PROCESSES. Este parámetro debe asignarse (en nuestro fichero de parámetros de inicialización) a un valor más grande que cero; para la mayoría de los casos, un valor de 1 debería ser suficiente. Un proceso coordinador comienza los procesos de la cola de tareas cuando es necesario. Si la base de datos no está ejecutando los procesos de la cola de tareas necesitamos usar métodos de refresco manual. 9.7.3. Refrescos manuales. Además de los refrescos automáticos, podemos realizar refrescos manuales de vistas materializadas. Esto rescribe la planificación normal de los refrescos; el nuevo valor de START WITH deberá estar basado en el momento de nuestro refresco manual. Para refrescar una única vista materializada se usa el procedimiento DBMS_MVIEW.REFRESH. Tiene dos

Page 233: Java y Oracle 11g

Oracle /233

parámetros principales: el nombre de la vista materializada y el método a usar. En este método podemos especificar 'c' para un refresco completo, 'f' para un refresco rápido, 'p' para un refresco rápido usando Partition Change Tracking (PCT) y '?' para forzar refresco. Por ejemplo: EXECUTE DBMS_MVIEW.REFRESH('LOCAL_LIBRO','c');

Nota. Partition Change Tracking (PCT) ocurre cuando operaciones de mantenimiento de partición se han realizado sobre las tablas referenciadas por la vista materializada. En PCT, Oracle realiza refrescos re-computando las filas en la vista afectadas por particiones cambiadas en tablas de detalle, evitando la necesidad de refrescos completos.

Si refrescamos varias vistas materializadas mediante una única ejecución de DBMS_MVIEW.REFRESH, debemos listar los nombres de todas las vistas en el primer parámetro, y sus métodos de refresco en el segundo parámetro, tal como se muestra a continuación: EXECUTE DBMS_MVIEW.REFRESH( 'LOCAL_LIBRO,LOCAL_CATEGORIA', '?c' );

Podemos usar un procedimiento independiente en el paquete DBMS_MVIEW para refrescar todas las vistas materializadas que están planificadas para refrescarse automáticamente. Este procedimiento, llamado REFRESH_ALL, refrescará cada vista materializada independientemente. No acepta ningún parámetro: EXECUTE DBMS_MVIEW.REFRESH_ALL_MVIEWS;

Ya que las vistas serán refrescadas mediante REFRESH_ALL consecutivamente, no serán refrescadas al mismo tiempo. Por lo tanto, una base de datos o servidor que falle durante la ejecución de este procedimiento puede causar que vistas materializadas locales no estén sincronizadas con otras. Si esto ocurre, simplemente hay que volver a ejecutar este procedimiento después de que se recupere la base de datos. Como alternativa, podemos crear grupos de refresco (ver siguiente sección). Otro procedimiento, DBMS_MVIEWS.REFRESH_ALL_MVIEWS, refresca todas las vistas materializadas que tengan las siguientes propiedades:

• La vista no ha sido refrescada desde los cambios más recientes a la tabla origen o vistas origen de la cuales depende. • La vista y todas las tablas o vistas origen de las cuales depende son locales. • La vista está en la vista DBA_MVIEWS.

Si creamos vistas materializadas anidadas, para asegurarnos de que sean refrescadas, podemos usar el procedimiento DBMS_MVIEW.REFRESH_DEPENDENT.

9.8. Sintaxis para crear registros de vista materializada.

Un registro de vista materializada en una tabla que registra los cambios en las filas de la tabla origen y el historial de replicación de vistas materializadas. El registro de cambios en las filas puede entonces usarse durante refrescos para enviar a las vistas materializadas sólo las filas que han cambiado en el origen. Varias vistas materializadas basadas en la misma tabla pueden usar el mismo registro de vista materializada. La sintaxis resumida para el comando CREATE MATERIALIZED VIEW LOG se muestra a continuación: CREATE MATERIALIZED VIEW LOG ON [esquema .] tabla

[{ cláusula de atributos físicos

| TABLESPACE tablespace

| { LOGGING | NOLOGGING }

| { CACHE | NOCACHE }

}

[ cláusula de atributos físicos

| TABLESPACE TABLESPACE

| { LOGGING | NOLOGGING }

| { CACHE | NOCACHE }

] . . .

]

[ cláusula paralelo ] [ cláusula particionado ]

[WITH

{ OBJECT ID | PRIMARY KEY | ROWID | SEQUENCE | ( columna [, columna ] . . . ) }

[, { OBJECT ID | PRIMARY KEY | ROWID | SEQUENCE | (columna [,columna ] . . . ) }] . . .

]

[{ INCLUDING | EXCLUDING } NEW VALUES] ;

El comando CREATE MATERIALIZED VIEW LOG se ejecuta en la base de datos de la tabla origen, normalmente por el propietario de la tabla origen. Los registros de vista materializada no deben crearse para tablas que sólo

Page 234: Java y Oracle 11g

Oracle /234

están involucradas con vistas complejas. No se especifica un nombre para el registro de vista materializada. Un registro de vista materializada para la tabla LIBRO puede crearse mediante el siguiente comando, ejecutado dentro de la cuenta del propietario de la tabla: CREATE MATERIALIZED VIEW LOG ON LIBRO

WITH SEQUENCE, ROWID

(Titulo, Autor, Categoria)

INCLUDING NEW VALUES;

La cláusula WITH SEQUENCE es necesaria para soportar la replicación de operaciones mixtas DML a través de varias tablas base. Debido a que los registros de vista materializada pueden aumentar impredeciblemente a lo largo del tiempo en las bases de datos de producción, deberíamos considerar almacenar sus objetos asociados en tablespaces dedicados para los registros de vista materializada. 9.8.1. Permisos de sistema requeridos. Para crear el registro de vista materializada debemos tener los permisos del sistema CREATE TABLE y CREATE

TRIGGER. Si estamos creando el registro de vista materializada para una cuenta de usuario que no es propietaria de la tabla origen, necesitamos tener los permisos de sistema CREATE ANY TABLE, COMMENT ANY TABLE y CREATE ANY TRIGGER, así como el permiso SELECT sobre la tabla origen de la vista.

9.9. Modificando vistas materializadas y registros.

Podemos modificar los parámetros de almacenamiento, opciones de refresco, y planificación de refresco para vistas materializadas existentes. Si no estamos seguros de las opciones actuales para una instantánea, podemos consultar la vista USER_MVIEWS del diccionario de datos. El siguiente ejemplo muestra cómo modificar la opción de refresco para la vista materializada LOCAL_LIBRO usando el comando ALTER MATERIALIZED VIEW. ALTER MATERIALIZED VIEW LOCAL_LIBRO

REFRESH COMPLETE;

Todos los futuros refrescos de LOCAL_LIBRO refrescarán la tabla base local por completo. Para modificar una vista materializada debemos ser propietarios de la vista o tener el permiso de sistema ALTER ANY MATERIALIZED VIEW. Para modificar un registro de vista materializada debemos ser propietarios de la tabla, tener el permiso ALTER para la tabla, o tener el permiso de sistema ALTER ANY TABLE. Si creamos una vista materializada sin las cláusulas RowID o SEQUENCE, podemos añadirlas después mediante el comando ALTER MATERIALIZED VIEW.

9.10. Eliminando vistas materializadas y registros.

Para borrar una vista materializada debemos tener los permisos de sistema requeridos para borrar tanto la vista como todos los objetos relacionados. Necesitamos tener el permiso DROP MATERIALIZED VIEW si los objetos están en nuestro esquema, o el permiso DROP ANY MATERIALIZED VIEW si la vista no está en nuestro esquema. El siguiente comando borra la vista materializada LOCAL_CATEGORIA_COUNT: DROP MATERIALIZED VIEW LOCAL_CATEGORIA_COUNT;

Nota. Cuando borramos una vista materializada que fue creada sobre una tabla predefinida, la tabla todavía existirá pero la vista será borrada.

Los registros de vista materializada pueden ser borrados mediante el comando DROP MATERIALIZED VIEW

LOG. Una vez que el registro de vista materializada es borrado de una tabla origen, ya no se harán refrescos rápidos para las vistas materializadas simples basadas en la tabla. Un registro de vista materializada debería ser borrado cuando no haya vistas materializadas simples basadas en la tabla origen. El siguiente comando borra el registro de vista materializada que fue creado para la tabla LIBRO previamente: DROP MATERIALIZED VIEW LOG ON LIBRO;

Para borrar el registro de vista materializada debemos tener la habilidad de borrar tanto el registro de vista como sus objetos relacionados. Si somos el propietario del registro, debemos tener los permisos de sistema DROP TABLE y DROP TRIGGER. Si no somos propietarios del registro necesitamos los permisos DROP ANY

TABLE y DROP ANY TRIGGER para ejecutar este comando.

Page 235: Java y Oracle 11g

Oracle /235

10. Oracle Text

Como la cantidad de texto en nuestras bases de datos aumenta, se hacen más complejas las consultas sobre texto de la base de datos. En vez de realizar búsquedas de strings por comparación podemos usar las nuevas funcionalidades de búsqueda de texto (cosas como dar peso a términos en una búsqueda de varios términos, o clasificar los resultados de una búsqueda de texto). Podemos usar Oracle Text para realizar búsquedas basadas en texto. Las capacidades de búsqueda de texto incluyen caracteres comodín, búsquedas difusas, clasificación por importancia, búsquedas por proximidad, términos con peso, y expansión de palabras.

10.1. Añadiendo texto a la base de datos.

Podemos añadir texto a una base de datos bien almacenando físicamente el texto en una tabla o bien almacenando punteros a ficheros externos de la base de datos. Esto es, para los libros de una estantería, podemos almacenar trozos en la base de datos o en archivos externos. Si almacenamos los trozos en archivos externos, entonces podemos almacenar los nombres del archivo en la base de datos Para almacenar trozos de libros en nuestra base de datos, podemos crear la tabla TROZO_LIBRO. Para los ejemplos de este capítulo crearemos dos tablas: TROZO_LIBRO_CONTEXT y TROZO_LIBRO_CTXCAT. Ambas tablas cargarán los mismos datos y cada una usará un tipo de índice distinto. CREATE TABLE TROZO_LIBRO_CONTEXT (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(25),

Fecha_Libro DATE,

Trozo VARCHAR2(4000)

);

/

INSERT INTO TROZO_LIBRO_CONTEXT VALUES (

'La montaña de luz',

'Emilio Salgari',

'01-MAY-02',

'Una muy calurosa tarde de julio de 1843, un elefante de estatura gigantesca, trepaba fatigosamente

los últimos escalones del altiplano de Pannah, uno de los más salvajes y al mismo tiempo más pintorescos

de la India central.'

);

/

CREATE TABLE TROZO_LIBRO_CTXCAT (

Titulo VARCHAR2(100) PRIMARY KEY,

Autor VARCHAR2(25),

Fecha_Libro DATE,

Trozo VARCHAR2(4000)

);

/

INSERT INTO TROZO_LIBRO_CTXCAT VALUES (

'La montaña de luz',

'Emilio Salgari',

'01-MAY-02',

'Una muy calurosa tarde de julio de 1843, un elefante de estatura gigantesca, trepaba fatigosamente

los últimos escalones del altiplano de Pannah, uno de los más salvajes y al mismo tiempo más pintorescos

de la India central.'

);

La columna Trozo de las tablas has sido definida como VARCHAR2(4000). Para valores grandes podemos considerar el uso del tipo CLOB. Podemos seleccionar el texto del trozo desde la base de datos con la siguiente consulta: SELECT Trozo

FROM TROZO_LIBRO_CONTEXT

WHERE Titulo = 'La montaña de luz';

10.2. Consultas de texto e índices de texto.

Las consultas de texto son diferentes de las consultas de datos porque las palabras tienen significado,

Page 236: Java y Oracle 11g

Oracle /236

relaciones con otras palabra y opuestos. Podemos querer buscar palabras que estén cerca de otras, o palabras que estén relacionadas con otras. Estas consultas deberían ser difíciles de hacer con los operadores relacionales estándar. Al extender SQL para incluir índices de texto, Oracle Text permite realizar consultas muy complejas sobre el texto. Para usar Oracle Text, necesitamos crear un índice de texto sobre la columna en la cual se almacena el texto. Un índice de texto es un término ligeramente confuso (actualmente es una colección de tablas e índices que almacenan información acerca del texto guardado en la columna). Veremos cómo crear dos tipos de índices de texto: CONTEXT y CTXCAT. Podemos usar también un tercer tipo de índice, CTXRULE, para crear una aplicación de clasificación de documentos basada en contenido.

Nota. Antes de crear un índice de texto sobre una tabla debemos crear una clave primaria para la tabla, sin es que aún no existe.

Podemos crear un índice de texto mediante una versión especial del comando CRATE INDEX. Para un índice CONTEXT se especifica el tipo CTXSYS.CONTEXT en la cláusula INDEXTYPE, tal como se muestra a continuación: CREATE INDEX Trozo_Context_Index

ON TROZO_LIBRO_CONTEXT(Trozo)

INDEXTYPE IS CTXSYS.CONTEXT;

Cuando se crea el índice de texto, Oracle crea varios índices y tablas en nuestro esquema para soportar las consultas de texto. Podemos reconstruir nuestro índice de texto mediante el comando ALTER INDEX, tal como se hace con otros índices. Podemos usar un índice CTXCAT en lugar del tipo CONTEXT: CREATE INDEX Trozo_Ctxcat_Index

ON TROZO_LIBRO_CTXCAT(Trozo)

INDEXTYPE IS CTXSYS.CTXCAT;

El tipo de índice CTXCAT soporta sincronización transaccional de datos entre la tabla base (TROZO_LIBRO_CTXCAT) y sus índices de texto. Con índices CONTEXT necesitamos actualizar manualmente el índice de texto después de cada cambio en la tabla base. Los índices de tipo CTXCAT no generan valores de "puntuación" durante las consultas de texto (como sí lo hacen los índices CONTEXT), pero la sintaxis de consulta es más larga. 10.2.1. Consultas de texto. Una vez creado un índice de texto sobre la columna Trozo de la tabla TROZO_LIBRO_CONTEXT, las capacidades de búsqueda de texto se incrementa dramáticamente. Ahora podemos mirar por cualquier libro que contenga la palabra 'elefante': SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'elefante') > 0;

Nota. En esta búsqueda no se tienen en cuenta las mayúsculas y minúsculas de la palabra buscada.

La función CONTAINS tiene dos parámetros (el nombre de la columna y el string de búsqueda) y comprueba el índice de texto para la columna Trozo. Si la palabra "elefante" es encontrada en el texto de la columna Trozo, entonces la base de datos retorna un valor de puntuación mayor que 0. La puntuación es una valoración de cómo los registros retornados casan con el criterio especificado por la función CONTAINS. Si creamos un índice CTXCAT, debemos usar la función CATSEARCH en vez de CONTAINS. La función CATSEARCH tiene tres parámetros: el nombre de columna, el string de búsqueda, y el nombre del conjunto de índice. (Los conjuntos de índices son descritos posteriormente en este capítulo.) En este ejemplo no hay ningún conjunto de índice, así que el parámetro se asignará a NULL: SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'elefante', NULL) > 0;

CATSEARCH no computa puntuaciones, pero usa la sintaxis >0 por una cuestión de compatibilidad con la función CONTAINS. Cuando una función como CONTAINS o CATSEARCH se usa en una consulta, la porción de texto de la consulta es procesada por Oracle Text. El resto de la consulta es procesado de forma regular. Los resultados de la consulta de texto y el procesamiento de la consulta normal son combinados para retorna un único conjunto de registros.

Page 237: Java y Oracle 11g

Oracle /237

10.2.2. Expresiones de consultas de texto disponibles. Oracle Text sería muy limitado si sólo se dedicara a buscar coincidencia de palabras. Oracle Text ofrece muchas capacidades de búsqueda que podemos usar para personalizar nuestras consultas. Muchas de estas capacidades se aplican mediante las funciones CONTAINS y CATSEARCH, las cuales sólo operan en la cláusula WHERE de una consulta SELECT, y nunca en comandos INSERT, UPDATE o DELETE. Los operadores dentro de CONTAINS y CATSEARCH permiten realizar las siguientes búsquedas de texto:

• Coincidencia exacta de palabas o frases. • Coincidencia exacta de varias palabras, usando lógica booleana para combinar búsquedas. • Búsquedas basadas en cómo unas palabras están cerca de otras. • Búsqueda de palabras que tienen la misma palabra "raíz". • Coincidencias difusas de palabras. • Búsquedas de palabras que suenan como otras.

10.2.3. Buscando la coincidencia exacta de una palabra. La siguiente consulta sobre las tablas TROZO_LIBRO retorna el título de todos los trozos que incluyen la palabra 'India': REM Método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India') > 0;

REM Método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'India', NULL) > 0;

Dentro de las llamadas a la función, el operador > es denominado un operador de umbral. La consulta precedente puede ser leída de la siguiente manera: Selecciona todos los valores de la columna Titulo

Dentro de la tabla TROZO_LIBRO_CONTEXT

Donde la puntuación para la búsqueda de texto de la columna Trozo

Para una búsqueda exacta de la palabra 'India' exceda un valor de umbral cero

El análisis de umbral compara la puntuación (la puntuación interna calculada por Oracle cuando se realiza la búsqueda de texto) con el valor de umbral. Los valores de umbral para búsquedas individuales están en el rango 0 a 10. Para índices CONTEXT podemos mostrar esta puntuación como parte de la consulta. Para mostrar la puntuación del texto buscado, se usa la función SCORE, la cual tiene un único parámetro (una etiqueta que asignamos a la puntuación dentro del texto buscado): SELECT Titulo, SCORE(10)

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India', 10) > 0 ;

TITULO SCORE(10)

------------------------------ --------------

La montaña de luz 3

En esta consulta, los parámetros de la función CONTAINS se modifican para incluir una etiqueta (10) para realizar la operación de búsqueda. La función SCORE muestra la puntuación de la búsqueda de texto asociada con la etiqueta. La puntuación es un cálculo interno basado en cómo el texto indexado coincide con el criterio. En índices CONTEXT podemos usar la función SCORE en la lista del SELECT o en un GROUP BY o en un ORDER

BY. 10.2.4. Buscando por coincidencia exacta en varias palabras. Para buscar por varias palabras podemos usar lógica booleana (con operadores AND y OR) para combinar los resultados de varia búsquedas de texto en una única consulta. Podemos también buscar por varios términos en la misma llamada a la función y dejar que Oracle resuelva el resultado de la búsqueda. Por ejemplo, si queremos buscar por libros que tengan las palabras "India" y "elefante" en el texto del trozo, podemos usar la siguiente consulta: REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

Page 238: Java y Oracle 11g

Oracle /238

WHERE CONTAINS(Trozo, 'India AND elefante') > 0;

REM método CATSEARCH para índices CTXCAT:

SELECT TITLE

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'India AND elefante', NULL) > 0;

Nota. Esta búsqueda no busca por la frase "India and elefante" sino que busca por cada palabra individual del texto.

En vez de usar AND en el índice CONTEXT, podríamos usar un ampersand (&). Antes de usar este método en SQL*Plus, deberíamos asignar SET DEFINE OFF para que el caracter & no sea visto como parte del nombre de variable: SET DEFINE OFF

REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India & elefante') > 0;

Para el índice CTXCAT, la palabra AND puede ser obviada: REM método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'India elefante', NULL) > 0;

Usar el caracter & o la palabra AND denotas una operación AND (así que la función CONTAINS retornará un fila sólo si el texto revisado incluye ambas palabra "India" y "elefante". Cada búsqueda debe pasar los criterios de umbral definidos por las puntuaciones de búsqueda. Si queremos buscar por más de dos términos basta con añadirlos a la función CONTAINS o CATSEARCH: REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India AND elefante AND julio') > 0;

REM método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(REVIEW_TEXT, 'India elefante julio', NULL) > 0;

Además del operador AND, podemos usar el operador OR. El símbolo de OR para Oracle Text es la barra horizontal ( | ), así que las dos siguientes consultas son procesadas idénticamente: REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India OR elefante') > 0;

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India | elefante') > 0;

El operador ACCUM proporciona otro método para combinar búsquedas. ACCUM suma las puntuaciones de búsquedas individuales y compara las puntuaciones acumuladas con el valor de umbral. El símbolo para ACCUM es una coman (,). Por lo tanto estas dos consultas son equivalentes: REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India ACCUM elefante') > 0;

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India , elefante') > 0;

La sintaxis ACCUM es soportada por la función CATSEARCH pero debería no ser usada porque CATSEARCH no calcula una puntuación para comparar el valor de umbral. Podemos usar Oracle Text para restar las puntuaciones de varias búsquedas antes de comparar el resultado a la puntuación de umbral. El operador MINUS en CONTAINS resta la puntuación de la búsqueda del segundo

Page 239: Java y Oracle 11g

Oracle /239

término de la puntuación de la resta del primer término. La consulta siguiente determinará la puntuación de búsqueda de 'India' y lo restará de la puntuación de búsqueda de 'casa' y entonces comparará la diferencia con la puntuación de umbral. En este ejemplo, el segundo término ('casa') no se encuentra en el texto indexado. Si el segundo término está en el texto (por ejemplo 'elefante'), entonces no retornará ninguna fila. REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India MINUS casa') > 0;

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'India - casa') > 0;

Podemos usar el símbolo menos (–) en lugar del operador MINUS, tal como se ha mostrado en el ejemplo previo. Para índices CONTEXT el operador – reduce la puntuación cuando comparamos la puntuación de búsqueda total con el valor de umbral, pero no elimina la fila a considerar. Para eliminar filas basadas sobre términos de búsqueda en índices CONTEXT, se usa el caracter ~ como el operador NOT. Para índices CTXCAT, el símbolo – tiene un significado diferente al de los índices CONTEXT. Para índices CTXCAT, el signo – le dice a Oracle Text que no retorne la fila si el término de búsqueda después del guión se encuentra (como ~ en índices CONTEXT). Si el segundo término es encontrado, CATSEARCH no retornará la fila. Para consultas CATSEARCH podemos reemplazar el guion por la palabra NOT. REM método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'India - elefante', NULL) > 0;

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'Indica NOT elefante', NULL) > 0;

Podemos usar paréntesis para aclarar la lógica dentro de nuestro criterio de búsqueda. Si nuestra búsqueda usa tanto AND como OR, deberíamos usar paréntesis para aclarar el camino en el cual las filas deben ser procesadas. Por ejemplo, la siguiente consulta retorna una fila si el texto de búsqueda contiene la palabra 'casa' o las palabras 'India' y 'elefante' a la vez. REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(REVIEW_TEXT, 'casa OR (India AND elefante)') > 0;

La función CATSEARCH no requiere la palabra AND entre 'India' y 'elefante': REM método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(REVIEW_TEXT, 'casa | (India elefante)', NULL) > 0;

Cuando evaluamos las puntuaciones de varias búsquedas mediante índices CONTEXT, podemos decirle a Oracle Text el peso de las puntuaciones de algunas búsquedas más importantes de otras. Por ejemplo, si queremos que la puntuación para 'India' sea el doble cuando comparamos el puntuación de umbral, podemos usar el asterisco (*) para indicar el factor por el cual la puntuación de búsqueda se multiplicará. La siguiente consulta doblará la puntuación de búsqueda para 'India' cuando lo evaluemos en una condición OR: SELECT Titulo, SCORE(10)

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(REVIEW_TEXT, 'India*2 OR elefante*1', 10) > 5;

Nota. El operador EQUIV trata dos términos de búsqueda como el mismo durante la puntuación de búsqueda. EQUIV (el cual puede ser reemplazado por =) puede ser útil en combinaciones con los operadores que comparan puntuaciones de búsqueda.

Page 240: Java y Oracle 11g

Oracle /240

10.2.5. Búsqueda por coincidencia de una frase. Cuando buscamos por coincidencia en una frase, debemos especificar la frase como parte del string de búsqueda. Si la frase incluye una palabra reservada (como "and", "or", o "minus"), necesitamos usar los caracteres de escape mostrados en esta sección para que la búsqueda sea ejecutada apropiadamente. Si la frase buscada incluye una palabra reservada de Oracle Text, entonces debemos usar las llaves { y } para encerrarla. La siguiente consulta busca por cualquier párrafo que incluya la frase "muy calurosa tarde". REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'muy calurosa tarde') > 0;

REM método CATSEARCH para índices CTXCAT:

SELECT TITLE

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(REVIEW_TEXT, '"muy calurosa tarde"', NULL) > 0;

La siguiente consulta busca la frase "transactions and finances". La palabra "and" es encerrada entre llaves. REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'transactions {and} finances') > 0;

REM método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, '"transactions {and} finances"', NULL) > 0;

Podemos encerrar toda la frase entre llaves, en cuyo caso cualquier palabra reservada dentro de la frase será tratada como parte del criterio de búsqueda, tal como muestra el siguiente ejemplo: SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, '{transactions and finances}') > 0;

10.2.6. Búsqueda de palabras que están cerca de otras. Podemos usar capacidades de búsqueda por proximidad para realizar una búsqueda basada en cómo términos cerrados están cerca dentro del mismo documento. Una búsqueda de proximidad retorna una puntuación alta para palabras que están cercas de las otras, y retorna una puntuación baja para palabras que están apartadas. Si las palabras continúan una a la otra, la búsqueda de proximidad retorna una puntuación de 100. Para usar búsqueda de proximidad a través de índices CONTEXT se usa la palabra NEAR, como en el siguiente ejemplo: SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'elefante NEAR estatura')>0;

Podemos reemplazar el operador NEAR por el símbolo de punto y coma (;). En las consultas con índice CONTEXT podemos especificar el máximo número de palabras entre los términos buscados. Por ejemplo, para palabras dentro de una separación de 10 palabras de otra, podemos usar la cadena de búsqueda 'NEAR((India, elefante),10)'. 10.2.7. Usando caracteres comodín durante búsquedas. En los ejemplos previos de este capítulo, las consultas seleccionan valores de texto que coinciden exactamente con los criterios especificados. Podemos usar caracteres comodines para ampliar la lista de términos de búsqueda válidos durante nuestra consulta. Al igual que con las expresiones regulares usadas con el operador LIKE, hay dos caracteres comodines disponibles:

% representa cualquier cadena de texto de cero o más caracteres de cualquier longitud. _ representa un carácter.

La siguiente consulta busca por todas las coincidencias de texto para palabras que comienzan con los caracteres "escalo": SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'escalo%') > 0;

La siguiente consulta limita la expansión del string de texto exactamente a tres caracteres. En lugar del signo

Page 241: Java y Oracle 11g

Oracle /241

% se usan tres guiones de subrayado (_ _ _). SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'escalo___') > 0;

Deberíamos usar caracteres comodines cuando conozcamos algunos caracteres pertenecientes al string de búsqueda. Si no estamos seguros del string de búsqueda, deberíamos usar uno de los métodos descritos en las siguientes secciones: palabras raíces, coincidencias difusas o coincidencias SOUNDEX. 10.2.8. Buscando palabras que tienen la misma raíz. En vez de usar caracteres comodines podemos usar las capacidades de expansión por raíz para ampliar la lista de strings de texto. Dada la raíz de una palabra, Oracle amplía la lista de palabras para buscar por todas las palabras que tengan la misma raíz. Unas ampliaciones simples se muestran a continuación:

Raíz Raíz ampliada casa casamentera, casanova, casado trabajo trabajador, trabajoso historia historiador

Como "trabajo" y "trabajador" tienen la misma raíz, una búsqueda de raíz-ampliada usando la palabra "trabajador" retornará el texto que contenga la palabra "trabajo". Para usar la ampliación de raíz con una consulta necesitamos usar el signo de dólar ($). Dentro de string de búsqueda, el $ debe preceder inmediatamente a la palabra que queremos ampliar. La siguiente consulta busca todos los trozos que contienen una palabra con la raíz "gigante": SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(REVIEW_TEXT, '$gigante') > 0;

Cuando esta consulta se ejecuta, Oracle expande la palabra "gigante" para incluir todas las palabras con la misma raíz. 10.2.9. Búsquedas por coincidencia difusa. Una búsqueda difusa amplia el término de búsqueda para incluir palabras que son fonéticamente similares pero que no necesariamente tiene la misma raíz. Las búsquedas difusas son más útiles cuando el texto contiene erratas. Las erratas pueden estar tanto en el texto de búsqueda como en el string buscado. Por ejemplo, la siguiente consulta no retornará el título "La montaña de luz" porque no contiene la palabra "elegante". SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'elegante') > 0;

Sin embargo, sí contiene la palabra "elefante", que es muy parecida. Una búsqueda difusa retornará trozos que contenga la palabra "elefante" aunque el término de búsqueda sea "elegante". Para usar una búsqueda difusa, hay que preceder el término de búsqueda con un signo de interrogación (?), sin ningún espacio entre ellos. El siguiente ejemplo ilustra el uso de esta capacidad de coincidencia: SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, '?elegante') > 0;

10.2.10. Búsqueda de palabras que suenan igual que otras palabras. Las búsquedas de raíz-ampliada amplían un término de búsqueda a varios términos basados en una raíz. Las búsquedas difusas amplían la búsqueda a términos basados en palabras similares en el índice de texto. Una tercera forma de ampliar términos de búsqueda, SOUNDEX, amplia la búsqueda según cómo suenan las palabras. El método de expansión SOUNDEX usa la misma lógica de coincidencia que la función SOUNDEX. Para usar la opción SOUNDEX, debemos preceder el término de búsqueda con una marca de exclamación (!). Durante la búsqueda, Oracle evalúa los valores de SOUNDEX para los términos en el índice de texto y busca por todas las palabras que tienen el mismo valor de SOUNDEX. Como muestra la siguiente consulta, podemos buscar por los trozos que incluyan la palabra "fatigosamente" usando la coincidencia fonética "fastidiosamente": SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Texto_Revidado, '!fastidiosamente') > 0;

Podemos también anidar operadores, permitiendo realizar ampliaciones por raíz sobre los términos

Page 242: Java y Oracle 11g

Oracle /242

retornados por una búsqueda difusa. En el siguiente ejemplo, se realiza una búsqueda difusa sobre la palabra "alto", y el término retornado se amplía usando raíz-ampliada: SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, '$?alto') > 0;

Las opciones principales de búsqueda para CONTAINS se resumen en la siguiente tabla.

Operador Descripción

OR Devuelve un registro si uno u otro término de búsqueda exceden el umbral.

| Equivalente a OR.

AND Devuelve un registro si ambos términos de búsqueda exceden el umbral.

& Equivalente a AND.

ACCUM Devuelve un registro si la suma de las puntuaciones de los términos de búsqueda exceden el umbral

MINUS Devuelve un registro si la puntuación de la primera búsqueda menos la puntuación de la segunda búsqueda exceden el umbral.

– Equivalente a MINUS.

* Asigna diferentes pesos a la puntuación de búsqueda.

NEAR La puntuación se basará en cómo de cerca los términos de búsqueda están uno junto al otro en el texto buscado.

; Equivalente a NEAR.

NOT Excluye el registro si el término después del NOT es encontrado.

~ Equivalente a NOT.

EQUIV Trata dos términos (term1 EQUIV term2) como el mismo durante la puntación de búsqueda.

= Equivalente a EQUIV.

{ } Encierra palabras reservadas como parte del término de búsqueda.

% Comodín para varios caracteres.

_ Comodín para un único caracter.

$ Realiza expansión por raíz.

? Realiza una búsqueda difusa.

! Realiza una búsqueda SOUNDEX.

( ) Especifica el orden en el cual el criterio de búsqueda es evaluado.

Las opciones de búsqueda para CATSEARCH se resumen en la siguiente tabla.

Operador Descripción

| Devuelve un registro si uno de los términos de búsqueda es encontrado.

AND Devuelve un registro si cada término de búsqueda es encontrado.

- Si hay espacios alrededor del guión, CATSEARCH retorna filas que contiene el término que precede al guión y que no contienen el término que sigue al guión. Un guión sin espacios es tratado como un caracter regular.

NOT Equivalente al guión.

"" Encierra frases.

( ) Especifica el orden en el cual el criterio de búsqueda es evaluado.

* Comodín para varios caracteres. Puede estar al final del término o en el medio de los caracteres.

10.2.11. Usando el operador «ABOUT». En Oracle Text podemos buscar sobre temas de documentos. La búsqueda temática está integrada con la búsqueda de términos de texto. Podemos usar el operador ABOUT para buscar términos que tienen que ver con el tema del documento en vez de términos específicos dentro del documento. Aquí hay un ejemplo: REM método CONTAINS para índices CONTEXT:

SELECT Titulo

FROM TROZO_LIBRO_CONTEXT

WHERE CONTAINS(Trozo, 'ABOUT(Medicina)') > 0;

REM método CATSEARCH para índices CTXCAT:

SELECT Titulo

FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH(Trozo, 'ABOUT(Medicina)', NULL) > 0;

Page 243: Java y Oracle 11g

Oracle /243

10.2.12. Sincronización de índice. Por defecto, cuando usamos índices CONTEXT, tenemos que gestionar los contenidos del índice de texto; los índices de texto no serán actualizados cuando la tabla base sea actualizada. En cuanto un valor del campo Trozo sea actualizado, el índice de texto dejará de estar sincronizado con la tabla base. Para sincronizar el índice hay que ejecutar el procedimiento CTX_DDL. SYNC_INDEX, tal como se muestra a continuación: EXECUTE CTX_DDL.SYNC_INDEX('Trozo_Context_Index');

Nota. Debemos conceder el permiso EXECUTE sobre el paquete CTX_DDL al usuario para soportar estas actividades de mantenimiento.

Desde Oracle Database 10g, los índices CONTEXT pueden mantenerse automáticamente, en el momento de confirmación de cambios, o en intervalos especificados. Como parte del comando CREATE INDEX para un índice CONTEXT, podemos usar la cláusula SYNC: [[METADATA] SYNC (MANUAL | EVERY "intervalo" | ON COMMIT)]

Aquí hay un ejemplo: DROP INDEX Trozo_Context_Index;

/

CREATE INDEX Trozo_Context_Index

ON TROZO_LIBRO_CONTEXT(Trozo)

INDEXTYPE IS CTXSYS.CONTEXT

PARAMETERS ('SYNC (ON COMMIT)');

10.3. Conjuntos de índices.

Históricamente, los problemas con consultas de índices de texto han ocurrido cuando otros criterios son usados sobre el texto de búsqueda como parte de la cláusula WHERE. Por ejemplo, ¿cuándo se aplican cláusulas WHERE sobre columnas no de texto?, ¿antes o después de que la búsqueda de texto se complete?, y ¿cómo se ordena los resultados apropiadamente? Para mejorar las capacidades de mezclar consultas podemos usar conjuntos de índices. Los índices dentro del conjunto pueden ser sobre columnas relacionales estructuradas o columnas de texto. Para crear un conjunto de índices se usa el paquete CTX_DDL para crear el conjunto de índices y añadirle índices. Cuando creamos un índice de texto podemos entonces especificar el conjunto de índices. Por ejemplo, para crear un conjunto de índices llamado Trozos, se usa el procedimiento CREATE_INDEX_SET: EXECUTE CTX_DDL.CREATE_INDEX_SET('Trozos');

Ahora podemos añadir índices al conjunto mediante el procedimiento ADD_INDEX. Primero añadiremos unos estándar, índices no de texto: EXECUTE CTX_DDL.ADD_INDEX('Trozos', 'Autor');

EXECUTE CTX_DDL.ADD_INDEX('Trozos', 'Fecha_Libro');

Ahora crearemos un índice de texto CTXCAT. Se especifica CTXSYS.CTXCAT como tipo de índice, y el conjunto de índices en la cláusula PARAMETERS: CREATE INDEX Trozo_CtxCat_Indice

ON TROZO_LIBRO_CTXCAT(Trozo)

INDEXTYPE IS CTXSYS.CTXCAT

PARAMETERS ('INDEX SET Trozos');

Ahora podemos ordenar nuestros resultados por el resultado de la búsqueda del conjunto de índices combinados: SELECT * FROM TROZO_LIBRO_CTXCAT

WHERE CATSEARCH( Trozo, 'julio',

'Autor=''Emilio Salgari''

ORDER BY Fecha_Libro DESC') > 0;

Para resolver esta consulta, Oracle Text usará el conjunto de índices, permitiendo ordenar los resultados apropiadamente, por fecha. Nótese que se han utilizado dos comillas seguidas para encapsular 'Emilio Salgari', para que de esta forma sean convertidas a una comilla simple cuando la consulta se ejecute. Los conjuntos de índices pueden contener hasta 99 índices de tipos NUMBER, DATE, CHAR y VARCHAR2. Las columnas en un índice del conjunto no pueden exceder de 30 bytes (así que en este ejemplo, la columna Titulo no puede ser indexada como parte del conjunto de índices). Las columnas de los índices no pueden contener valores nulos. Desde Oracle Database 10g, Oracle Text incluye una facilidad de trazado para ayudar a identificar cuellos de botella en la indexación y consultas. Se usa el procedimiento CTX_OUTPUT.ADD_TRACE para habilitar el

Page 244: Java y Oracle 11g

Oracle /244

trazado. Las trazas disponibles incluyen el tiempo gastado sobre los diferentes componentes de la búsqueda, el número de octetos leídos, y el número de filas procesadas.

11. Uso de tablas externas

Podemos usar la funcionalidad de tablas externas para acceder a ficheros externos como si fuesen tablas dentro de la base de datos. Cuando creamos una tabla externa podemos definir su estructura y localización dentro de Oracle. Cuando consultamos la tabla, Oracle lee la tabla externa y retorna los resultados tal como si los datos estuviesen almacenados dentro de la base de datos. Lo mejor es que, aunque los datos están fuera de la base de datos, no necesitamos conocer el proceso de carga dentro de la base de datos. Las tablas externas tiene límites (no podemos actualizar o borrar sus registros dentro de Oracle, y no podemos indexarlas). Debido a que son parte de nuestra aplicación de base de datos, tendremos que incluirlas como parte de nuestros procesos de copias de respaldo y recuperación. A pesar de estas complicaciones, las tablas externas pueden ser una potente adición a nuestra arquitectura de base de datos. Desde Oracle Database 10g, podemos usar tablas externas para descargar datos desde tablas a ficheros externos mediante dispositivos de acceso ORACLE_DATAPUMP.

11.1. Accediendo a datos externos.

Para acceder a ficheros externos dentro de Oracle debemos primero usar el comando CREATE DIRECTORY para definir un objeto DIRECTORY que apunte a la ubicación de los ficheros externos. Los usuarios que accedan a los ficheros externos deben tener el permiso READ sobre el directorio.

Nota. Antes de empezar, debemos verificar que el directorio externo existe y que el usuario que publicará el comando CREATE DIRECTORY tenga el permiso de sistema CREATE ANY DIRECTORY.

El siguiente ejemplo crea un directorio llamado LIBRO_DIR y concede accesos READ y WRITE al esquema Empleado: CREATE DIRECTORY LIBRO_DIR AS 'e:\oracle\external';

GRANT READ ON DIRECTORY LIBRO_DIR TO Empleado;

GRANT WRITE ON DIRECTORY LIBRO_DIR TO Empleado;

El usuario Empleado puede ahora leer ficheros del directorio e:\oracle\external como si estuviera dentro de la base de datos. Al tener permiso de escritura, el usuario puede crear ficheros de log, descartes y "malos" dentro del directorio. La siguiente lista genera dos ficheros para datos de ejemplo (uno para LIBRO y otro para AUTOR). Nótese que el comando SPOOL no usa el nombre de directorio creado, sino que hay que especificar la ruta física del directorio. CONNECT Empleado/contraseña

SET PAGESIZE 0 NEWPAGE 0 FEEDBACK OFF

SPOOL e:\oracle\external\libro_dump.lst

SELECT Titulo || '~' || Autor || '~' || Categoria || '~' || Valoracion || '~'

FROM LIBRO

ORDER BY Titulo;

SPOOL OFF

SPOOL e:\oracle\external\autor_dump.lst

SELECT Titulo || '~' || Autor || '~'

FROM AUTOR

ORDER BY Titulo;

SPOOL OFF

Nota. El comando SPOOL de SQL*Plus permite redirigir la salida a un fichero de texto. Los datos no se materializarán en el fichero de texto hasta que se ejecute SPOOL OFF.

Además de los datos, los ficheros de salida contendrán la consulta en las líneas iniciales y una línea final con "SQL> SPOOL OFF". Para simplificar los ejemplos, deberíamos manualmente editar los ficheros al nivel del sistema operativo y eliminar estas líneas extra. Si otro usuario debe acceder a los datos de los ficheros libro_dump.lst y autor_dump.lst, debemos concederle el permiso READ sobre el directorio LIBRO_DIR: GRANT READ ON DIRECTORY LIBRO_DIR TO otroUsuario;

También los ficheros mismos deber ser legibles por el usuario de Oracle al nivel del sistema operativo.

Page 245: Java y Oracle 11g

Oracle /245

11.2. Creando una tabla externa.

Ahora que hay datos externos disponibles y accesibles, podemos crear una estructura de tabla que acceda a ellos. Para hacer esto necesitamos usar la cláusula ORGANIZATION EXTERNAL del comando CREATE TABLE. Dentro de esta cláusula podemos especificar la estructura de datos, tal como se hace en el fichero de control de SQL*Loader. 11.2.1. Usando el controlador ORACLE_LOADER. El siguiente listado muestra la creación de la tabla LIBRO_EXT, basada en los datos del fichero libro_dump.lst creado previamente. SET FEEDBACK ON HEADING ON NEWPAGE 1 PAGESIZE 60

CREATE TABLE LIBRO_EXT (

Titulo VARCHAR2(100),

Autor VARCHAR2(20),

Categoria VARCHAR2(20),

Valoracion NUMBER(2)

) ORGANIZATION EXTERNAL (

TYPE ORACLE_LOADER

DEFAULT DIRECTORY LIBRO_DIR

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

FIELDS TERMINATED BY "~" (

Titulo CHAR(100),

Autor CHAR(20),

Categoria CHAR(20),

Valoracion INTEGER EXTERNAL (2)

)

)

LOCATION ('libro_dump.lst')

);

Oracle responderá con: Tabla creada.

Aunque ningún dato ha sido creado dentro de la base de datos de Oracle. De forma similar podemos crear una tabla basada en el fichero autor_dump.lst: CREATE TABLE AUTOR_EXT (

Nombre VARCHAR2(20),

Nacionalidad VARCHAR2(50)

) ORGANIZATION EXTERNAL (

TYPE ORACLE_LOADER

DEFAULT DIRECTORY LIBRO_DIR

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

FIELDS TERMINATED BY "~" (

Nombre CHAR(20),

Nacionalidad CHAR(50)

)

)

LOCATION ('autor_dump.lst')

);

Oracle realizará sólo validación superficial cuando la tabla externa es creada. No veremos la mayor parte de los errores hasta que intentemos consultar la tabla. La sintaxis para los parámetros de acceso es muy específica, y errores menores en la definición de acceso (incluyendo el orden de la cláusula) pueden evitar que todas las filas sean accedidas. Podemos verificar el contenido de las tablas externas consultándolas y comparándolas con las tablas fuentes: SELECT Titulo FROM LIBRO;

SELECT Titulo FROM LIBRO_EXT;

Podemos combinar la tabla "interna" AUTOR con su contrapartida externa, AUTOR_EXT, para verificar que no hay registros perdidos o añadidos: SELECT *

Page 246: Java y Oracle 11g

Oracle /246

FROM AUTOR A

WHERE NOT EXISTS

(SELECT 'X' FROM AUTOR_EXT AE WHERE A.Nombre = AE.Nombre);

Como resultado, esta consulta no retornará ningún registro. La tabla AUTOR_EXT apunta al fichero autor_dump.lst. Si modificamos los datos en el fichero, los datos consultados sobre AUTOR_EXT cambiarán. Podemos consultar la vista USER_EXTERNAL_TABLES del diccionario de datos para informarnos sobre nuestras tablas externas, incluyendo el directorio por defecto y definiciones de acceso. DESC USER_EXTERNAL_TABLES

Name Null? Type

----------------------------------------- -------- --------------------

TABLE_NAME NOT NULL VARCHAR2(30)

TYPE_OWNER CHAR(3)

TYPE_NAME NOT NULL VARCHAR2(30)

DEFAULT_DIRECTORY_OWNER CHAR(3)

DEFAULT_DIRECTORY_NAME NOT NULL VARCHAR2(30)

REJECT_LIMIT VARCHAR2(40)

ACCESS_TYPE VARCHAR2(7)

ACCESS_PARAMETERS VARCHAR2(4000)

PROPERTY VARCHAR2(10)

Por ejemplo, la tabla AUTOR_EXT usa LIBRO_DIR como directorio por defecto como mostraría la siguiente consulta: SELECT DEFAULT_DIRECTORY_NAME, ACCESS_PARAMETERS

FROM USER_EXTERNAL_TABLES

WHERE TABLE_NAME = 'AUTOR_EXT';

DEFAULT_DIRECTORY_NAME ACCESS_PARAMETERS

------------------------------------ ---------------------------------------------

LIBRO_DIR RECORDS DELIMITED BY NEWLINE

FIELDS TERMINATED BY "~"

(Nombre CHAR(20),

Nacionalidad CHAR(50))

La vista USER_EXTERNAL_TABLES no muestra el nombre del fichero externo (o ficheros) que la tabla referencia. Para ver esta información podemos consultar la vista USER_EXTERNAL_LOCATIONS: SELECT * FROM USER_EXTERNAL_LOCATIONS;

TABLE_NAME LOCATION DIR DIRECTORY_NAME

------------------- --------------------------- ------ ------------------------

LIBRO_EXT libro_dump.lst SYS LIBRO_DIR

AUTOR_EXT autor_dump.lst SYS LIBRO_DIR

11.2.2. Usando el controlador ORACLE_DATAPUMP. Podemos descargar datos desde tablas de la base de datos a ficheros externos mediante dispositivos de acceso ORACLE_DATAPUMP. La sintaxis para utilizar el controlador de acceso ORACLE_DATAPUMP es la siguiente: CREATE TABLE Tabla_externa

ORGANIZATION EXTERNAL (

TYPE ORACLE_DATAPUMP

DEFAULT DIRECTORY objeto_directory

LOCATION ( 'fichero_externo' )

)

AS

SELECT id, nombre, direccion FROM Cliente ;

Con esta sintaxis no estamos creando realmente una tabla en la base de datos. En realidad, estamos creando metadatos en el diccionario de datos que se pueden utilizar para acceder a datos externos. Dentro del directorio referenciado por objeto_directory se crea un archivo con el nombre fichero_externo que contendrá los datos procedentes de la consulta utilizada para crear la tabla. Esta tabla puede ser consultada de la forma habitual: SELECT * FROM Tabla_externa;

Page 247: Java y Oracle 11g

Oracle /247

Podemos mover el archivo fichero_externo a otro sistema y crear una tabla externa para leer los datos: CREATE TABLE Import_Tabla_externa (

id number,

nombre VARCHAR2(50),

direccion VARCHAR2(150)

)

ORGANIZATION EXTERNAL (

TYPE ORACLE_DATAPUMP

DEFAULT DIRECTORY objeto_directory

LOCATION ('fichero_externo')

) ;

11.2.3. Opciones de creación de tablas externas. Dentro de la cláusula ORGANIZATION EXTERNAL hay cuatro sub-cláusulas: TYPE, DEFAULT DIRECTORY, ACCESS PARAMETERS, y LOCATION. Cuando creamos una tabla externa podemos estas cláusulas para personalizar el modo cómo Oracle ve los datos externos. La cláusula «TYPE». La sintaxis para el componente TYPE es: ( [TYPE tipo_dispositivo_acceso ] propiedades_datos_externos )

[REJECT LIMIT { entero | UNLIMITED }]

Para las tablas externas, el dispositivo de acceso es la API usada para transformar los datos externos. Se usa el tipo ORACLE_LOADER para tablas externas de solo lectura, u ORACLE_DATAPUMP si queremos usar Data Pump para tablas externas de lectura/escritura. Debemos especificar el dispositivo de acceso ORACLE_DATAPUMP si usamos la cláusula AS subconsulta para descargar datos desde una base de datos y entonces los recargamos. El tipo de dispositivo de acceso ORACLE_LOADER es el de por defecto.

Nota. Ya que el dispositivo de acceso es parte del software de Oracle, sólo los ficheros accesibles por la base de datos pueden ser accedidos como tablas externas.

Después de la declaración de TYPE, podemos asignar un valor de límite de rechazo (REJECT LIMIT). Por defecto, ninguna fila puede ser rechazada (cualquier problema con una fila causará que el comando SELECT retorne un error). Generaremos otra copia de los datos de LIBRO en otro fichero: SET PAGESIZE 0 NEWPAGE 0 FEEDBACK OFF

SPOOL e:\oracle\external\libro_dump_2.lst

SELECT Titulo || '~' || Autor || '~' || Categoria || '~' || Valoracion || '~' FROM LIBRO

/

SPOOL OFF

Ahora crearemos una nueva tabla que referencie este fichero, diciéndole a Oracle que se salte las dos primeras filas (SKIP 2) y que permita un error (REJECT LIMIT 1). Esto permitirá saltarse las dos primeras líneas (con el comando SELECT y la barra /) y evitar que la última línea (SQL> SPOOL OFF) provoque un error: SET FEEDBACK ON HEADING ON NEWPAGE 1 PAGESIZE 60

CREATE TABLE LIBRO_EXT_2 (

Titulo VARCHAR2(100),

Autor VARCHAR2(20),

Categoria VARCHAR2(20),

Valoracion NUMBER(2)

) ORGANIZATION EXTERNAL (

TYPE ORACLE_LOADER

DEFAULT DIRECTORY LIBRO_DIR

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

SKIP 2

FIELDS TERMINATED BY "~" (

Titulo CHAR(100),

Autor CHAR(20),

Categoria CHAR(20),

Valoracion INTEGER EXTERNAL (2)

)

)

Page 248: Java y Oracle 11g

Oracle /248

LOCATION ('libro_dump_2.lst')

) REJECT LIMIT 1 ;

Podemos ahora verificar el número de filas de la tabla: SET FEEDBACK ON HEADING ON NEWPAGE 1 PAGESIZE 60

SELECT COUNT(*) FROM LIBRO_EXT_2;

La cláusula «DEFAULT DIRECTORY». La cláusula DEFAULT DIRECTORY especifica el objeto directorio que será usado para todos los ficheros de datos para los cuales no se especifica otro directorio. Si usamos varios ficheros externos localizados en varios directorios podemos nombrar uno de ellos como el directorio por defecto y especificar los otros mediante su nombre en la cláusula LOCATION. Debemos usar nombres de objetos DIRECTORY (como LIBRO_DIR) en la cláusula LOCATION, no la ruta completa del directorio. Parámetros de acceso. La cláusula ACCESS PARAMETERS le dice a Oracle cómo mapear las filas del fichero con la tabla. Su sintaxis se muestra a continuación:

Dentro de la cláusula ACCESS PARAMETERS primero le decimos a Oracle cómo crear un registro (si su longitud es fija o variable, y cómo están delimitadas las filas). Si tenemos varias filas en una única línea, podemos usar una cadena como un separador entre fila. Debido a que los datos externos pueden venir de bases de datos diferentes de Oracle, Oracle soporta varios juegos de caracteres y tamaños de strings. Como con SQL*Loader, podemos especificar una cláusula WHEN para limitar qué registros son seleccionados. En el siguiente listado se crea la tabla LIBRO_EXT_3, con una cláusula WHEN para limitar sólo a los libros con categoría 'ADULTO'. CREATE TABLE LIBRO_EXT_3 (

Titulo VARCHAR2(100),

Autor VARCHAR2(20),

Categoria VARCHAR2(20),

Valoracion NUMBER(2)

) ORGANIZATION EXTERNAL (

Page 249: Java y Oracle 11g

Oracle /249

TYPE ORACLE_LOADER

DEFAULT DIRECTORY LIBRO_DIR

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

LOAD WHEN Categoria = 'ADULTO'

SKIP 2

FIELDS TERMINATED BY "~" (

Titulo CHAR(100),

Autor CHAR(20),

Categoria CHAR(20),

Valoracion INTEGER EXTERNAL(2)

)

)

LOCATION ('libro_dump_2.lst')

) REJECT LIMIT 1 ;

LIBRO_EXT_3 accede al mismo fichero que LIBRO_EXT_2, pero sólo mostrará los registros de categoría 'ADULTO'. Al igual que con SQL*Loader, podemos crear un fichero de log, un fichero "malo" y un fichero de descarte. Las filas que fallen la condición WHEN serán escritos en el archivo de descartes. Las filas que fallen las condiciones de los parámetros de acceso serán escritos en el fichero "malo", y los detalles de carga serán escritos en el fichero de log. Para todos los tres tipos de ficheros podemos especificar un objeto DIRECTORY con el nombre de archivo y así podemos escribir la salida a otro directorio distinto del directorio de entrada. Podemos especificar NODISCARDFILE, NOBADFILE y NOLOGFILE para evitar que estos ficheros sean creados. Dentro de la cláusula ACCESS PARAMETERS podemos especificar las definiciones de campo y delimitadores como: FIELDS TERMINATED BY "~" (

Titulo CHAR(100),

Autor CHAR(20),

Categoria CHAR(20),

Valoracion INTEGER EXTERNAL(2)

)

Podemos usar la cláusula MISSING FIELD VALUES ARE NULL para asignar valores para columnas nulas, pero debemos tener cuidado cuando usemos esta opción. Por ejemplo, la tabla AUTOR tiene valores nulos en su columna Nacionalidad. La creación de la tabla externa para AUTOR_EXT se muestra a continuación: SET PAGESIZE 0 NEWPAGE 0 FEEDBACK OFF

SPOOL e:\oracle\external\autor_dump.lst

SELECT Nombre || '~' || Nacionalidad || '~' FROM AUTOR ORDER BY Nombre

/

SPOOL OFF

SET FEEDBACK ON HEADING ON NEWPAGE 1 PAGESIZE 60

CREATE TABLE AUTOR_EXT (

Nombre VARCHAR2(50),

Nacionalidad VARCHAR2(100)

) ORGANIZATION EXTERNAL (

TYPE ORACLE_LOADER

DEFAULT DIRECTORY LIBRO_DIR

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

SKIP 2

FIELDS TERMINATED BY "~"

MISSING FIELD VALUES ARE NULL (

Nombre CHAR(50),

Nacionalidad CHAR(100)

)

)

LOCATION ('autor_dump.lst')

) REJECT LIMIT 1 ;

Page 250: Java y Oracle 11g

Oracle /250

Pero esto no es correcto. Si seleccionamos los valores de Nombre de la tabla AUTOR_EXT, veremos que los valores incluyen lo siguiente: SELECT Nombre FROM AUTOR_EXT WHERE Nombre LIKE 'S%';

Nombre

------------------------------------

Sax Rohmer

Sheridan Le Fanu

SQL> SPOOL OFF

Debido a la cláusula MISSING FIELD VALUES ARE NULL, la línea "SQL> SPOOL OFF" del final del fichero fue leída como un valor de nombre de autor, con la nacionalidad a valor NULL. Esto ilustra el problema con la codificación de excepciones en nuestras definiciones de cargado. En muchos casos, nuestra integridad de datos mejorará forzando que fallen filas (siendo enviadas al fichero "malo" o de descartes) y evaluando las filas falladas aparte. La cláusula «Location». En la cláusula LOCATION podemos especificar el fichero de datos usado como origen de datos para la tabla. Podemos nombrar varios ficheros en esta cláusula si todos existen en objetos DIRECTORY para los cuales el usuario tiene permisos READ. El siguiente ejemplo combina dos ficheros independientes de LIBRO para ilustrar la habilidad de combinar varios ficheros en una única tabla externa. CREATE TABLE LIBRO_EXT_4 (

Titulo VARCHAR2(100),

Autor VARCHAR2(20),

Categoria VARCHAR2(20),

Valoracion NUMBER(2)

) ORGANIZATION EXTERNAL (

TYPE ORACLE_LOADER

DEFAULT DIRECTORY LIBRO_DIR

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

SKIP 2

FIELDS TERMINATED BY "~" (

Titulo CHAR(100),

Autor CHAR(20),

Categoria CHAR(20),

Valoracion INTEGER EXTERNAL(2)

)

)

LOCATION ('libro_dump_2.lst', 'libro_dump.lst')

) REJECT LIMIT 1 ;

El orden de los ficheros es importante; SKIP 2 se aplica al primer fichero, no al segundo.

11.3. Modificación de tablas externas.

Podemos modificar las definiciones de una tabla externa para cambiar el modo en cómo Oracle interpreta el fichero plano. Las opciones disponibles se detallan a continuación. Parámetros de acceso. Podemos cambiar los parámetros de acceso sin borrar y recrear la definición de la tabla externa, preservando permisos, definición del fichero y así. Por ejemplo, a continuación se muestra cómo incrementar el número de registros a saltarse en la tabla LIBRO_EXT_4: ALTER TABLE LIBRO_EXT_4

ACCESS PARAMETERS (

RECORDS DELIMITED BY NEWLINE

SKIP 10

FIELDS TERMINATED BY "~" (

Titulo CHAR(100),

Autor CHAR(20),

Categoria CHAR(20),

Valoracion INTEGER EXTERNAL(2)

)

Page 251: Java y Oracle 11g

Oracle /251

) ;

Añadir columna. Podemos usar la cláusula ADD COLUMN del comando ALTER TABLE para añadir una columna a la tabla externa, usando la misma sintaxis usada para tablas estándar. Directorio por defecto. Podemos usar la cláusula DEFAULT DIRECTORY del comando ALTER TABLE para cambiar el directorio por defecto para los ficheros externos accedidos por la tabla. El directorio debe ser creado mediante el comando CREATE DIRECTORY. Quitar columna. Podemos usar la cláusula DROP COLUMN del comando ALTER TABLE para quitar columnas de la tabla externa, usando la misma sintaxis que la usada para tablas estándar. Los datos del fichero permanecerán sin cambiar. Localización. Podemos cambiar los ficheros accedidos por la tabla externa mediante la cláusula LOCATION del comando ALTER TABLE. Podemos usar esta opción para añadir nuevos ficheros a la lista o cambiar el orden en el cual son accedidos. Modificar columna. Podemos usar la cláusula MODIFY COLUMN del comando ALTER TABLE para modificar una columna de la tabla externa, usando la misma sintaxis que la usada para tablas estándar. Paralelismo. Podemos usar la cláusula PARALLEL del comando ALTER TABLE para cambiar el grado de paralelismo de la tabla externa, usando la misma sintaxis que la usada para tablas estándar. Proyectar columna. La cláusula PROJECT COLUMN del comando ALTER TABLE le dice a los dispositivos de acceso cómo validar filas en consultar consecutivas. Si usamos la opción PROJECT COLUMN REFERENCED, el dispositivo de acceso sólo procesará las columnas seleccionadas por la consulta. Si entonces consultamos un conjunto diferente de columnas de la tabla externa, el resultado puede ser inconsistente con los resultados de la primera consulta. Si se usa la opción PROJECT COLUMN ALL, el dispositivo de acceso procesará todas las columnas definidas sobre la tabla externa, resultando en un conjunto consistente de resultados. La opción PROJECT COLUMN

REFERENCED es la de por defecto. Límite de rechazo. Podemos usar la cláusula REJECT LIMIT del comando ALTER TABLE para cambiar el número permitido de filas rechazadas por la tabla externa. Aquí hay un ejemplo: ALTER TABLE LIBRO_EXT_3 REJECT LIMIT 5;

Renombrar. Podemos usar la cláusula RENAME TO del comando ALTER TABLE para cambiar el nombre de la tabla externa, usando la misma sintaxis que para las tablas estándar. Aquí hay un ejemplo: ALTER TABLE LIBRO_EXT_3 RENAME TO BE3;

11.4. Limitaciones, beneficios y usos potenciales de las tablas externas.

Las tablas externas tienen limitaciones que pueden hacerlas inapropiadas para algunas aplicaciones que procesan transacciones en línea. No podemos realizar ninguna operación de actualización o borrado sobre las tablas externas. Cuanto más dinámica es la tabla, menos apropiados son los ficheros externos. Podemos cambiar los ficheros dinámicamente con operaciones al nivel del sistema operativo. Si nuestra aplicación genera sólo inserciones, podemos escribir estos registros insertados dentro de un fichero externo en vez de una tabla de base de datos. No podemos indexar tablas externas. La falta de índices sobre tablas externas no tiene por qué ser un factor negativo en el funcionamiento de una aplicación. Las consultas a tablas externas se completan muy rápidamente, aun cuando requieran una exploración completa de la tabla con cada acceso.

Nota. Para analizar tablas externas, se usa el paquete DBMS_STATS. No podemos analizar tablas externas con el comando ANALYZE.

No podemos especificar restricciones sobre una tabla externa. Incluso crear una restricción NOT NULL o de clave foránea falla. Al margen de estas limitaciones, las tablas externas ofreces muchas funcionalidades útiles. Podemos combinar tablas externas (con otras, o con tablas estándar). Podemos usar etiquetas para forzar al optimizador a elegir

Page 252: Java y Oracle 11g

Oracle /252

varias formas de join. Como una alternativa a la carga de datos, las tablas externas ofrecen a los DBA's y programadores de aplicaciones la posibilidad de acceder a datos sin tener que soportar programas de cargas de larga duración. Ya que los ficheros pueden ser editados al nivel del sistema operativo, podemos rápidamente reemplazar los datos rápidamente sin preocuparnos de transacciones que modifiquen las tablas. Por ejemplo, podemos usar esta capacidad para crear varias tablas externas y crear una vista UNION ALL con ellas, creando una vista particionada a través de varios ficheros. Podemos entonces gestionar los datos de cada tabla separadamente al nivel del sistema operativo, reemplazando su contenido si es necesario. Ya que la tabla externa puede se consultada, podemos usar la tabla externa como origen de datos para un comando INSERT AS SELECT. Durante esta operación, Oracle intentará cargar los ficheros externos en paralelo, potencialmente mejorando el rendimiento. Para mejorar el rendimiento de las operaciones INSERT

AS SELECT, deberíamos usar la etiqueta APPEND para forzar inserciones a nivel de bloque. Cuando especificamos el grado de paralelismo para las operaciones INSERT AS SELECT, Oracle inicia varios dispositivos de acceso ORACLE_LOADER para procesar los datos en paralelo. Para mejorar el funcionamiento de la carga, hay que evitar usar campos de longitud variable, campos delimitados, conversión de juego de caracteres, NULLIF, DEFAULTIF, y operaciones de conversión de tipos de datos. Desactivando la generación del fichero "malo" (con NOBADFILE) eliminamos el coste asociado con la creación del fichero y el mantenimiento el contexto de filas original. Durante la INSERT AS SELECT podemos aplicar funciones sobre los datos que se procesan. Podemos aplicar estas funciones en la sintaxis del comando INSERT AS SELECT o en la definición de la tabla externa. Esto ofrece importantes beneficios a las tablas externas (podemos centralizar la representación y requerimientos de proceso de nuestros datos, creando rutinas de traslación dentro de la definición de nuestras tablas). Durante las consultas, las tablas externas permiten seleccionar conjuntos de datos específicos (mediante la cláusula LOAD WHEN). Si tenemos varios orígenes de datos para cargas de datos de repositorio, podemos elegir qué datos estarán disponibles aún mientras los datos están fuera de la base de datos. Podemos usar esta característica para mantener aplicaciones disponibles durante cargas de datos. Estas cargas pueden ocurrir en paralelo si los ficheros externos tiene un formato de fichero FIXED. La característica de límites de acceso también permite forzar reglas de seguridad complejas concernientes al acceso de datos. Por ejemplo, podemos tomar datos sensibles desde fuera de la base de datos, en un directorio seguro. Usuarios con acceso READ al directorio deberían ser capaces de usar la tabla externa y combinarla con otras tablas; usuarios sin este acceso deberían estar limitados para insertar datos dentro de las tablas públicamente accesibles. Datos altamente seguros, o datos dinámicos poco accedidos, no tienen que ser insertados en la base de datos hasta que sea necesario. Si usamos tablas externas en nuestra arquitectura de base de datos, debemos asegurarnos de planificar copias de respaldo y recuperación para estos ficheros al igual que para el resto de la base de datos. Si los ficheros externos cambian más rápidamente que los ficheros de base de datos, podemos necesitar recuperarlos más frecuentemente para tomar todas las ventajas de las capacidades de recuperación de Oracle.

12. Consultas flashback

Como parte de su modelo consistente de lectura, Oracle muestra datos que han sido confirmados en la base de datos. Desde Oracle9i, podemos consultar datos previos a una transacción que ha sido confirmada. Si accidentalmente confirmamos una actualización podemos usar esta capacidad (llamada consultas flashback) para ver los datos en el estado previo a la confirmación. Podemos usar los resultados de la consulta flashback para restaurar los datos. Para soportar consultas flashback nuestra base de datos debería usar deshacer administrado por el sistema, una funcionalidad introducida en Oracle9i para automáticamente administrar segmentos de rollback. El DBA debe crear una tablespace de deshacer, habilitar Automatic Undo Management, y establecer una ventana de tiempo de retención de deshacer. Las consultas flashback pueden ejecutarse a través de bases de datos remotas. Oracle intentará mantener cada información de deshacer en el tablespace de deshacer para soportar consultas flashback durante el periodo de retención de los datos. El tiempo de retención asignado y la cantidad de espacio disponible en el tablespace de deshacer pueden impactar significativamente nuestra habilidad para ejecutar consultas flashback.

Page 253: Java y Oracle 11g

Oracle /253

Nota. Oracle usa deshacer (undo) para dar marcha atrás a transacciones y soportar consultas flashback. Oracle usa rehacer (redo), mediante ficheros de log de rehacer en línea, para aplicar transacciones durante recuperaciones de base de datos.

Las consultas flashback son herramientas muy importantes mediante los esfuerzos de recuperaciones parciales. En general, no deberíamos confiar en consultas flashback como parte del diseño de nuestras aplicación debido a su dependencia de elementos del sistema fuera del control de los programadores de la aplicación (como el número de transacciones durante un período de tiempo y el tamaño de tablespace de deshacer). Más bien deberíamos tratarlas como una opción durante los periodos de pruebas, soporte y recuperación de datos. Por ejemplo, podríamos usarlas para crear copias de tablas y puntos en el tiempo que serán usados para reconstruir los datos cambiados.

Nota. Para usar algunas características de las consultas flashback, debemos tener el permiso EXECUTE sobre el paquete DBMS_FLASHBACK. La mayoría de usuarios no necesitarán permisos sobre este paquete.

12.1. Ejemplo de consulta flashback basada en el tiempo.

La tabla PEDIDO_LIBRO tiene seis registros, tal como se muestra en el siguiente listado: COLUMN Titulo FORMAT A30

SELECT * FROM PEDIDO_LIBRO;

TITULO EDITOR CATEGORIA

-------------------------- -------------------- ----------------

Joe descalzo Mariner Adulto-fic

Evangelio Picador Adulto-fic

Algo tan fuerte Pandoras Adulto-nf

La hija de Galileo Penguin Adulto-nf

Longitud Penguin Adulto-nf

Una vez quitado Sancturay Pub Adulto-nf

Llega un envío, y los registros antiguos de PEDIDO_LIBRO son suprimidos y se confirma el borrado. Lamentablemente, no todos los libros estaban en el envío, y por tanto la supresión fue inadecuada: DELETE FROM PEDIDO_LIBRO;

COMMIT;

¿Cómo podemos reconstruir los registros de libros no recibidos de la tabla PEDIDO_LIBRO? Podemos realizar una recuperación de la base de datos usando Data Pump Import para restaurar la tabla, o podemos usar una recuperación física de la base de datos para recuperarla a un punto anterior a la supresión. Sin embargo, con consultas flashback podemos evitar la necesidad de realizar operaciones de recuperación. Primero debemos consultar los datos antiguos a la base de datos. Desde Oracle9i Release 2, podemos usar las cláusulas AS OF TIMESTAMP y AS OF SCN del comando SELECT para especificar cómo de lejos debería llegar Oracle para recuperar datos.

Nota. En la liberación 9.0.1 de Oracle9, esta sintaxis no está soportada; es necesario ejecutar procedimientos del paquete DBMS_FLASHBACK y escribir bucles PL/SQL para realizar cualquier transacción basada en los datos.

SELECT COUNT(*) FROM PEDIDO_LIBRO;

COUNT(*)

-------------

0

SELECT COUNT(*) FROM PEDIDO_LIBRO

AS OF TIMESTAMP (SYSDATE – 5/1440);

COUNT(*)

-------------

6

Cuando ejecutamos una consulta flashback, sólo es cambiado el estado de los datos. Se usa el tiempo actual del sistema (el valor de SYSDATE), y el diccionario de datos actual. Si la estructura de la tabla ha cambiado, la consulta fallará.

Page 254: Java y Oracle 11g

Oracle /254

12.2. Guardando los datos.

Como se muestra en el ejemplo de PEDIDO_LIBRO, las consultas flashback son sencillas de implementar, a condición de que el administrador de deshacer de la base de datos esté correctamente configurado y la información deshacer esté disponible. ¿Pero cómo trabajar con los datos flashback? El método más simple consiste en guardar los datos en otra tabla. Hay 1440 minutos en un día, así "SYSDATE

– 5/1440" dirigirá a la base de datos al estado que tenía hace 5 minutos. Nótese entonces que la consulta puede no retornar registros si han pasado más de 5 minutos desde la confirmación. CREATE TABLE PEDIDO_LIBRO_OLD

AS SELECT * FROM PEDIDO_LIBRO AS OF TIMESTAMP (SYSDATE – 5/1440);

/

SELECT COUNT(*) FROM PEDIDO_LIBRO_OLD;

COUNT(*)

-------------

6

Podemos verificar que los datos recuperados son correctos consultando la tabla PEDIDO_LIBRO_OLD. Y entonces podemos trabajar con estos datos para restaurar los datos perdidos, realizar actualizaciones selectivas, insertar sólo registros incorrectamente borrados, o cualquier otra operación necesaria. La nueva tabla, PEDIDO_LIBRO_OLD, no tiene índices ni restricciones de integridad referencial. Si necesitamos combinarla con otras tablas, podemos necesitar crear copias flashback de varias tablas para mantener la integridad referencial de los datos. También podemos notar que cada consulta es ejecutada en diferentes momentos del tiempo (y los tiempos relativos usados en la cláusula AS OF TIMESTAMP, por lo tanto, pueden conducir a confusión o resultados incoherentes). Podemos trabajar directamente con datos antiguos (sin embargo, confiamos en que los datos viejos estén disponibles para nuestra transacción). Es generalmente seguro crear una tabla y almacenar los datos viejos temporalmente mientras trabajamos con ellos. 12.2.1. Limitaciones en las consultas flashback basadas en el tiempo. El ejemplo precedente muestra cómo usar el tiempo del sistema durante nuestras consultas flashback. Como se indicó, este método proporciona un soporte limitados para consultas flashback multi-tablas. Para realizar consultas flashback complejas correctamente deberíamos usar el número de cambio del sistema (SCN). Oracle usa internamente el SCN, no el tiempo del sistema, para generar datos flashback, aunque especifiquemos la cláusula AS OF TIMESTAMP. El tiempo del sistema se mapea a los valores SCN cada cinco minutos; la tabla SYS.SMON_SCN_TIME registra las últimas 1440 coincidencias de SCN al tiempo del sistema. Cuando volvemos a un punto del tiempo, Oracle usa los 5 minutos más recientes que preceden al tiempo consultado. Si SMON_SCN_TIME tiene un registro para las 1:00 P.M. y otro para las 1:05 P.M., una consulta flashback para las 1:04 P.M. retornará los datos desde las 1:00 P.M. SMON_SCN_TIME sólo mantiene las más recientes 1440 entradas, así que las consultas flashback que van más allá de los últimos cinco días deben usar un acercamiento basado en SCN. Si cerramos nuestra base de datos de vez en cuando (por ejemplo, para hacer respaldos fuera de línea), los 1440 registros de SMON_SCN_TIME incluirán más de cinco días. Para ver las entradas más recientes de SYS.SMON_SCN_TIME, podemos ejecutar la siguiente consulta como SYS, conectados como SYSDBA: SELECT TO_CHAR(TIME_DP, 'DD-MON-YYYY HH24:MI:SS')

FROM SYS.SMON_SCN_TIME

ORDER BY TIME_DP DESC ;

12.3. Ejemplo de consulta flashback basada en SCN.

Cuando realizamos consultas flashback basadas en el tiempo, realmente estamos haciendo consultas basadas en SCN; simplemente confiamos en Oracle para encontrar un SCN cerca del tiempo especificado. Si conocemos el SCN exacto podemos realizar una consulta flashback con una gran precisión. Para comenzar una consulta flashback basada en SCN, primero debemos conocer el SCN de nuestra transacción. Para obtener el último número de cambio debemos aplicar un COMMIT y entonces usar la cláusula AS OF SCN del comando SELECT. Podemos encontrar el SCN actual ejecutando la función GET_SYSTEM_CHANGE_NUMBER del paquete DBMS_FLASHBACK antes de la ejecución de nuestra transacción.

Page 255: Java y Oracle 11g

Oracle /255

Nota. Antes de ejecutar el siguiente ejemplo, tenemos que tener concedido el permiso EXECUTE sobre el paquete DBMS_FLASHBACK.

El siguiente ejemplo muestra este proceso como parte de una transacción a través de la tabla PEDIDO_LIBRO_OLD creada y poblada previamente. Primero, el SCN actual se asigna a una variable llamada scn_flash y se muestra mediante el comando PRINT de SQL*Plus: COMMIT;

VARIABLE scn_flash NUMBER;

EXECUTE :scn_flash :=DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER;

PRINT scn_flash

scn_flash

-------------

529732

A continuación, se realiza un borrado y el resultado es confirmado: DELETE FROM PEDIDO_LIBRO_OLD;

COMMIT;

Ahora podemos consultar los datos flashback. Aunque conocemos el valor SCN, podemos continuar usando la variable scn_flash dentro de la misma sesión: SELECT COUNT(*) FROM PEDIDO_LIBRO_OLD

AS OF SCN (:scn_flash);

COUNT(*)

-------------

6

Podemos usar los datos flashback de PEDIDO_LIBRO_OLD, accedidos por la cláusula AS OF SCN, para poblarla con los valores antiguos: INSERT INTO PEDIDO_LIBRO_OLD

SELECT * FROM PEDIDO_LIBRO_OLD AS OF SCN (:scn_flash);

COMMIT;

Nota. Las operaciones DDL que modifican la estructura de la tabla invalidan los datos de deshacer para la tabla, y las capacidades flashback se limitan a antes de que el DDL fue ejecutado.

12.4. ¿Qué ocurre si falla una consulta flashback?

Si no hay suficiente espacio en el tablespace de deshacer para mantener todos los datos necesarios para la consulta de flashback, la consulta fallará. Aunque el DBA cree un tablespace de deshacer grande, es posible que una serie de transacciones largas usen todo el espacio disponible. Una porción de cada consulta fallida será escrita en el registro de avisos de la base de datos. Desde la perspectiva de un usuario que intenta recuperar datos antiguos, deberíamos intentar recuperar los datos que sean tan correctos y oportunos como sea posible. A menudo podemos tener que ejecutar varias consultas flashback para determinar como de lejos podemos consultar sucesivamente los datos, y luego guardar los datos más viejos a los que podemos tener acceso y los datos más cercanos al punto en el cual el problema ocurrió. Una vez que los datos más viejos se han ido del tablespace de deshacer, no podemos usar consultas flashback para recuperarlo. Si no es posible recuperar datos que son bastante viejos para nuestras necesidades, tendremos que realizar algún tipo de la recuperación de la base de datos (recuperando la base de datos entera o recuperando tablas y tablespaces específicos mediante los métodos tradicionales de un DBA). Si este problema es habitual, deberíamos aumentar el tiempo de retención de deshacer, aumentar el espacio asignado para el tablespace deshacer, y la aplicación debería examinarse para determinar por qué se repiten transacciones cuestionables.

12.5. ¿Qué SCN está asociado con cada registro?

Desde Oracle Database 10g, podemos ver el más reciente SCN asociado con cada registro de la bases de datos. Comenzaremos repoblando nuestra tabla PEDIDO_LIBRO con los datos que fueron restaurados en la tabla PEDIDO_LIBRO_OLD: DELETE FROM PEDIDO_LIBRO;

INSERT INTO PEDIDO_LIBRO

SELECT * FROM PEDIDO_LIBRO_OLD;

Page 256: Java y Oracle 11g

Oracle /256

COMMIT;

Ahora usaremos la pseudo-columna ORA_ROWSCN introducida en Oracle Database 10g para ver el SCN asociado con cada registro: SELECT Titulo, ORA_ROWSCN FROM PEDIDO_LIBRO;

TITULO ORA_ROWSCN

-------------------------- ------------------

Joe descalzo 553531

Evangelio 553531

Algo tan fuerte 553531

La hija de Galileo 553531

Longitud 553531

Una vez quitado 553531

Todos los registros son parte de la misma transacción y por tanto tienen el mismo SCN. Ahora, después de que se han realizado otras transacciones en la base de datos, insertaremos un nuevo registro: INSERT INTO PEDIDO_LIBRO

VALUES ('Innumeracy','Vintage Books','Adulto-nf');

COMMIT;

Ahora que ha sido confirmado un nuevo cambio, podemos usar el SCN asociado con él: SELECT Titulo, ORA_ROWSCN FROM PEDIDO_LIBRO;

TITULO ORA_ROWSCN

-------------------------- ------------------

Joe descalzo 553531

Evangelio 553531

Algo tan fuerte 553531

La hija de Galileo 553531

Longitud 553531

Una vez quitado 553531

Innumeracy 553853

¿A qué hora se hace el mapeado SCN? Podemos usar la función de SCN_TO_TIMESTAMP para mostrar la fecha en la cual se hizo el cambio: SELECT SCN_TO_TIMESTAMP(555853) FROM DUAL;

SCN_TO_TIMESTAMP(555853)

-------------------------------------------

20-FEB-2011 03.11.28.000000000 PM

Podemos integrar estas dos consultas par ver los tiempos de la última transacción para cada registro: SELECT Titulo, SCN_TO_TIMESTAMP(ORA_ROWSCN)

FROM PEDIDO_LIBRO;

12.6. Consultas de versión flashback.

Desde Oracle Database 10g, podemos mostrar varias versiones de las filas que existen durante intervalos especificados. Como con los ejemplos mostrados previamente, los cambios son dependientes de SCN, así que sólo estos que son confirmados se mostrarán.

Nota. Las consultas de versión flashback requieren que el DBA tenga asignado un valor distinto de cero para el parámetro de inicialización UNDO_RETENTION. Si el valor de UNDO_RETENTION es demasiado pequeño obtendremos un error ORA-30052.

Para los siguientes ejemplos, eliminaremos los registros antiguos de la tabla PEDIDO_LIBRO: DELETE FROM PEDIDO_LIBRO;

Ahora repoblaremos la tabla PEDIDO_LIBRO: SELECT SYSTIMESTAMP FROM DUAL;

INSERT INTO PEDIDO_LIBRO

SELECT * FROM PEDIDO_LIBRO_OLD;

SELECT SYSTIMESTAMP FROM DUAL;

Ahora esperamos unos pocos minutos y actualizamos todos los registros: SELECT SYSTIMESTAMP FROM DUAL;

UPDATE PEDIDO_LIBRO SET Categoria = 'Adulto-f';

Page 257: Java y Oracle 11g

Oracle /257

SELECT SYSTIMESTAMP FROM DUAL;

Para ejecutar una consulta de versión flashback se usa la cláusula VERSIONS BETWEEN en el comando SELECT. Podemos especificar la fecha o el SCN. En este ejemplo, el formato para la cláusula TIMESTAMP está basado en el formato estándar de Oracle. SELECT *

FROM PEDIDO_LIBRO

VERSIONS BETWEEN TIMESTAMP

TO_TIMESTAMP('20-FEB-04 16.00.20','DD-MON-YY HH24.MI.SS')

AND

TO_TIMESTAMP('20-FEB-04 16.06.20','DD-MON-YY HH24.MI.SS') ;

Cuando ejecutemos esta consulta, Oracle retornará una fila por cada versión de cada registro entre las fechas indicadas. Para las filas retornadas, podemos consultar pseudo-columnas adicionales:

Pseudo-columna Descripción

VERSIONS_STARTSCN El SCN cuando los primeros datos tenían los valores reflejados. Si NULL, entonces la fila fue creada antes del límite inferior de la consulta.

VERSIONS_STARTTIME La fecha cuando los primeros datos tenían los valores reflejados. Si NULL, entonces la fila fue creada antes del límite inferior de la consulta.

VERSIONS_ENDSCN El SCN cuando la versión de la fila expira. Si NULL, entonces la fila es actual o ha sido borrada.

VERSIONS_ENDTIME La fecha cuando la versión del la fila expira. Si NULL, entonces la fila es actual o ha sido borrada.

VERSIONS_XID El identificador de la transacción que creó la versión de la fila.

VERSIONS_OPERATION La operación que realizó la transacción (I para INSERT, U para UPDATE, D para

DELETE).

El siguiente ejemplo muestra el uso de consultas de versión flashback. Los valores de Versions_StartSCN son NULL para las filas actuales; las filas antiguas han sido actualizadas. La fecha será diferente según la plataforma. SELECT Titulo, VERSIONS_STARTSCN, VERSIONS_OPERATION

FROM PEDIDO_LIBRO

VERSIONS BETWEEN TIMESTAMP

TO_TIMESTAMP('20—FEB-04 16.00.20','DD-MON-YY HH24.MI.SS')

AND

TO_TIMESTAMP('20—FEB-04 16.06.20','DD-MON-YY HH24.MI.SS') ;

TITULO ORA_ROWSCN V

-------------------------- ------------------ ----

Una vez quitado 568127 U

Longitud 568127 U

La hija de Galileo 568127 U

Algo tan fuerte 568127 U

Evangelio 568127 U

Joe descalzo 568127 U

Joe descalzo

Evangelio

Algo tan fuerte

La hija de Galileo

Longitud

Una vez quitado

La cláusula VERSIONS BETWEEN puede usarse en subconsultas de comandos DML y DDL. Desde Oracle Database 10g, podemos usar la vista FLASHBACK_TRANSACTION_QUERY del diccionario de datos para seguir los cambios hechos por cada transacción. Para una transacción dada, FLASHBACK_TRANSACTION_QUERY muestra el nombre del usuario que las ejecutó, la operación realizada, la tabla sobre la que se aplicó la transacción, el SCN y fechas iniciales y finales, y el SQL necesario para deshacer la transacción.

12.7. Planificación de las consultas flashback.

Par los DBA's, las consultas flashback pueden servir para realizar operaciones de recuperaciones parciales rápidamente.

Page 258: Java y Oracle 11g

Oracle /258

Si los datos pueden ser reconstruidos mediante consultas flashback, y si el volumen de datos no es aplastante, podemos ser capaces de guardar los resultados de varias consultas flashback en tablas independientes. Podemos entonces comparar los datos en las tablas mediante las opciones SQL mostradas previamente (subconsultas correlacionadas, EXISTS, NOT EXISTS, MINUS, y demás). Si no podemos evitar la necesidad de una operación de recuperación, deberíamos señalar el período de tiempo para ser usado en una operación de recuperación basada en el tiempo. Como se verá en el siguiente capítulo, Oracle Database 11g introduce opciones adicionales (los comandos FLASHBACK DATABASE y FLASHBACK TABLE). Para los programadores y administradores de aplicaciones, las consultas flashback proporcionan una herramienta importante para reconstruir datos. Las consultas flashback pueden ser particularmente críticas durante operaciones de pruebas y soporte.

13. Tablas y bases de datos flashback

Desde Oracle Database 10g, podemos usar los comandos FLASHBACK TABLE y FLASHBACK DATABASE para simplificar nuestros esfuerzos de recuperación de datos. El comando FLASHBACK TABLE automatiza el proceso de restaurar una tabla entera a un estado previo. El comando FLASHBACK DATABASE recupera una base de datos entera, y necesita modificaciones sobre el estado y registro de la base de datos.

13.1. El comando «FLASHBACK TABLE».

El comando FLASHBACK TABLE recupera un estado anterior de una tabla en el evento de error humano o de aplicación. Oracle no puede restaurar una tabla a un estado previo durante operaciones DDL que cambien la estructura de la tabla.

Nota. La base de datos debería usar Automatic Undo Management (AUM) para trabajar con FLASHBACK TABLE. La habilidad de recuperar datos viejos está limitada a la cantidad de datos de deshacer retenidos en el tablespace de deshacer y el parámetro de inicialización UNDO_RETENTION.

No se puede dar marcha atrás a un comando FLASHBACK TABLE. Sin embargo, podemos aplicar otro comando FLASHBACK TABLE y especificar un tiempo anterior al actual.

Nota. Registre el SCN actual antes de la ejecución de un comando FLASHBACK TABLE.

13.1.1. Permisos requeridos. Debemos tener el permiso de objeto FLASHBACK sobre la tabla o el permiso de sistema FLASHBACK ANY

TABLE. Debemos también tener los permisos de objeto SELECT, INSERT, DELETE y ALTER sobre la tabla. Debemos habilitar el movimiento de fila para todas las tablas de la lista flashback. Para dar marcha atrás una tabla antes de una operación DROP sobre tabla, sólo necesitamos tener los permisos necesarios para borrar la tabla. 13.1.2. Recuperación de tablas borradas. Consideremos la tabla AUTOR: DESCRIBE AUTOR

Name Null? Type

------------------ -------- ----------------------------

NombreAutor NOT NULL VARCHAR2(50)

Comentarios VARCHAR2(100)

Ahora, asumamos que la tabla es borrada accidentalmente. Esto ocurre normalmente cuando un usuario con permisos intenta borrar una tabla en el entorno de desarrollo/pruebas pero pensando en la base de datos de producción cuando ejecuta el comando: DROP TABLE AUTOR CASCADE CONSTRAINTS;

¿Cómo podemos recuperar la tabla? Desde Oracle Database 10g, una tabla borrada no desaparece completamente. Sus bloques todavía se mantienen en el tablespace, y eso todavía perjudica la cuota de espacio. Podemos ver los objetos borrados consultando la vista RECYCLEBIN del diccionario de datos. Nótese que el formato de la columna Object_Name puede diferir entre versiones. SELECT Object_Name, Original_Name, Operation, Type, Ts_Name, CreateTime FROM RECYCLEBIN;

OBJECT_NAME ORIGINAL_NAME OPERATION TYPE TS_NAME CREATETIME

--------------------------- ---------------------- --------------- --------- ------------- -------------------------

RB$$48448$TABLE$0 AUTOR DROP TABLE USERS 2004-02-23:16:10:58

Page 259: Java y Oracle 11g

Oracle /259

RB$$48449$INDEX$0 SYS_C004828 DROP INDEX USERS 2004-02-23:16:10:58

SELECT DropTime, DropScn, Partition_Name, Can_Undrop, Can_Purge, Related FROM RECYCLEBIN;

DROPTIME DROPSCN PARTITION_NAME CAN CAN RELATED

------------------------- -------------- ------------------------ ------- ------ -------------

2004-02-25:14:30:23 720519 YES YES 48448

2004-02-25:14:30:23 720516 NO YES 48448

SELECT Base_Object, Purge_Object, Space FROM RECYCLEBIN;

BASE_OBJECT PURGE_OBJECT SPACE

------------------ ---------------------- ----------

48448 48448 8

48448 48449 8

RECYCLEBIN es un sinónimo público de la vista USER_RECYCLEBIN del diccionario de datos, que muestra las entradas del reciclador para el usuario actual. Los DBA's pueden ver todos los objetos borrados mediante la vista DBA_RECYCLEBIN del diccionario de datos. Como se muestra en el listado precedente, Oracle ha borrado la tabla AUTOR y su índice de clave primaria asociado. Aunque hayan sido borrados todavía están disponibles para recuperación. Nótese que el listado muestra el SCN para el comando DROP usado para borrar el objeto base. Podemos usar el comando FLASHBACK TABLE TO BEFORE DROP para recuperar la tabla desde el reciclador: FLASHBACK TABLE AUTOR TO BEFORE DROP;

La tabla es restaurada, junto con sus filas, índices y estadísticas. La sintaxis completa de este comando es la siguiente:

¿Qué ocurre si borramos la tabla AUTOR, la recreamos y entonces la borramos otra vez? El reciclador contendrá ambas tablas. Cada entrada en el reciclador se identificará mediante su SCN y la fecha de borrado.

Nota. El comando FLASHBACK TABLE TO BEFORE DROP no recupera restricciones referenciales.

Para purgar entradas antiguas del reciclador se usa el comando PURGE. Podemos purgar todos nuestros objetos borrados, todos los objetos borrados en la base de datos (si somos un DBA), todos los objetos de un tablespace específico, o todos los objetos de un usuario determinado en un tablespace específico. La sintaxis completa del comando PURGE se muestra a continuación:

Podemos usar la cláusula RENAME del comando FLASHBACK TABLE para renombrar la tabla cuando se recupera.

Page 260: Java y Oracle 11g

Oracle /260

13.1.3. Cómo recuperar a una fecha o SCN. Como se vio en el capítulo previo, podemos guardar los resultados de una consulta flashback a otra tabla mientras la tabla principal permanece inalterada. También podemos actualizar las filas de una tabla según los resultados de una consulta flashback. Podemos usar el comando FLASHBACK TABLE para transformar una tabla a una versión anterior (borrando los cambios hechos desde el punto de retroceso especificado). Durante la operación de recuperación de tabla, Oracle adquiere bloqueos DML exclusivos sobre todas las tablas especificadas. El comando es ejecutado como una única transacción a través de todas las tablas; si alguna de ellas falla, todo el comando falla. Podemos entonces recuperar una tabla a un SCN o tiempo específicos.

Nota. FLASHBACK TABLE TO SCN o TO TIMESTAMP no preservan los RowID's.

El siguiente comando de actualización intenta actualizar el comentario para el autor Clement Hurd. Sin embargo, la cláusula WHERE no es especificada; todos los registros serán actualizados. UPDATE AUTOR

SET COMMENTS = 'Ilustrador de libros para niños';

COMMIT;

En este caso, sabemos que la mayoría de la tabla tiene datos incorrectos, y sabemos que la transacción fue ejecutada incorrectamente. Para recuperar los datos correctos, podemos dar marcha atrás a la base de datos y entonces aplicar cualquier nuevo comando necesario para actualizar los datos. Primero, nos aseguraremos de conocer el SCN actual para retornar a este punto: COMMIT;

VARIABLE scn_flash NUMBER;

EXECUTE :scn_flash :=DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER;

PRINT scn_flash

SCN_FLASH

------------------

720880

Ahora, recuperaremos la tabla al tiempo anterior a la actualización. Primero, habilitaremos el movimiento de filas para la tabla: ALTER TABLE AUTOR ENABLE ROW MOVEMENT;

Entonces podemos recuperar la tabla: FLASHBACK TABLE AUTOR TO TIMESTAMP (SYSTIMESTAMP – 5/1440);

Podemos usar la cláusula TO SCN si queremos especificar un SCN en vez de una fecha. 13.1.4. Índices y estadísticas. Cuando una tabla es recuperada, sus estadísticas no son recuperadas. Los índices que existen sobre la tabla son revertidos y reflejan el estado de la tabla al punto de recuperación. Los índices borrados desde el punto de recuperación no son restaurados. Los índices creados desde el punto de recuperación continuarán existiendo y serán actualizados para reflejar los viejos datos.

13.2. El comando «FLASHBACK DATABASE».

El comando FLASHBACK DATABASE retorna la base de datos a un tiempo o SCN pasado, proporcionando una alternativa rápida para realizar recuperaciones incompletas de la base de datos. Después de una operación de retroceso de base de datos, para tener acceso de escritura a la base de datos recuperada debemos volver a abrirla con un comando ALTER DATABASE OPEN RESETLOGS. Debemos tener el permiso de sistema SYSDBA para usar el comando FLASHBACK DATABASE.

Nota. La base de datos debe haber sido puesta en modo flashback con el comando ALTER

DATABASE FLASHBACK ON. La base de datos debe ser montada en modo exclusivo pero no estar abierta cuando este comando es ejecutado.

La sintaxis para el comando FLASHBACK DATABASE es la siguiente:

Page 261: Java y Oracle 11g

Oracle /261

Podemos usar tanto la cláusula TO SCN como TO TIMESTAMP para asignar el punto al cual queremos retornar toda la base de datos. Con la cláusula TO BEFORE podemos regresar a un punto crítico (como una transacción que produce una alteración no intencionada de varias tablas). Se usa la pseudo-columna ORA_ROWSCN para ver el SCN de las transacciones que más recientemente modificaron las filas. Para usar FLASHBACK DATABASE, debemos primer modificar la base de datos mientras está montada pero no abierta. Si no ocurre esto, necesitaremos cerrar nuestra base de datos y habilitar flashback durante el proceso de inicio: STARTUP MOUNT EXCLUSIVE;

ALTER DATABASE ARCHIVELOG;

ALTER DATABASE FLASHBACK ON;

ALTER DATABASE OPEN;

Nota. Debemos tener habilitado recuperación de medios mediante el comando ALTER DATABASE

ARCHIVELOG antes de ejecutar el comando ALTER DATABASE FLASHBACK ON.

Dos parámetros de inicialización controlan cómo los datos flashback son retenidos en la base de datos. El parámetro DB_FLASHBACK_RETENTION_TARGET asigna el límite superior (en minutos) para determinar qué lejos en el tiempo podemos recuperar la base de datos. El parámetro DB_RECOVERY_FILE_DEST asigna el tamaño del área de recuperación. Nótese que el comando FLASHBACK TABLE usa el tablespace de deshacer, mientras que el comando FLASHBACK DATABASE confía en registros de recuperación almacenados en un área de recuperación. Podemos determinar cuán lejos podemos recuperar la base de datos consultando la vista V$FLASHBACK_DATABASE_LOG. La cantidad de datos flashback retenidos en la base de datos es controlada por el parámetro de inicialización y el tamaño del área de recuperación. El siguiente listado muestra las columnas disponibles en V$FLASHBACK_DATABASE_LOG y contenidos de ejemplo: DESC V$FLASHBACK_DATABASE_LOG

Name Null? Type

----------------------------------------- -------- ------------

OLDEST_FLASHBACK_SCN NUMBER

OLDEST_FLASHBACK_TIME DATE

RETENTION_TARGET NUMBER

FLASHBACK_SIZE NUMBER

ESTIMATED_FLASHBACK_SIZE NUMBER

SELECT * FROM V$FLASHBACK_DATABASE_LOG;

OLDEST_FL OLDEST_FL RETENTION_TARGET FLASHBACK_SIZE ESTIMATED_FL

--------------- --------------- --------------------------- ----------------------- ---------------------

722689 25-FEB-04 1440 8192000 0

Podemos verificar el estado de recuperación de la base de datos consultando V$DATABASE; la columna Flashback_On tendrá el valor YES si la recuperación está habilitada para la base de datos. SELECT CURRENT_SCN, FLASHBACK_ON FROM V$DATABASE;

CURRENT_SCN FLASHBACK_ON

--------------------- ----------------------

723649 YES

Con la base de datos abierta durante más de una hora, verificamos que los datos flashback están disponibles y entonces los recuperamos (perderemos todas las transacciones que ocurrieron durante este tiempo): SHUTDOWN;

STARTUP MOUNT EXCLUSIVE;

FLASHBACK DATABASE TO TIMESTAMP SYSDATE-1/24;

Nótese que el comando FLASHBACK DATABASE requiere que la base de datos esté montada en modo

Page 262: Java y Oracle 11g

Oracle /262

exclusivo. Cuando ejecutamos el comando FLASHBACK DATABASE, Oracle comprueba que todo el archivado requerido y los ficheros de registro de deshacer en línea están disponibles. Si los registros están disponibles, los ficheros de datos en línea se revierten al tiempo o SCN especificado. Si no hay suficientes datos en línea en los registros de archivado y áreas de recuperación, necesitaremos usar métodos tradicionales de recuperación de base de datos para recuperar los datos. Por ejemplo, podemos necesitar usar un método de recuperación de ficheros del sistema después de la marcha atrás de los datos. Una vez que la recuperación se ha completado, debemos abrir la base de datos usando la opción RESETLOGS para tener accesos de escritura sobre la base de datos: ALTER DATABASE OPEN RESETLOGS;

Para desactivar la opción de flashback de la base de datos hay que ejecutar el comando ALTER DATABASE

FLASHBACK OFF cuando la base de datos está montada pero no abierta: STARTUP MOUNT EXCLUSIVE;

ALTER DATABASE FLASHBACK OFF;

ALTER DATABASE OPEN;

Page 263: Java y Oracle 11g

Oracle /263

V. SOPORTE DE OBJETOS Y XML

Esta lección trata de las características y funcionalidades XML de Oracle Database en su versión 9i. Estas características no son sólo el almacenamiento de este tipo de datos como cadenas de caracteres sino el modelado de estos tipos, su clasificación e indexado en el sistema de modelado de datos de Oracle, permitiendo peticiones y manipulación avanzada de los mismos. La naturaleza de estructura no plana de los documentos XML obliga para su modelado hacer uso de las características objeto-relacionales de la base de datos. Por eso, la primera parte de esta lección habla del modelo objeto-relacional de Oracle. La segunda parte se centra en los documentos XML en la base de datos Oracle.

1. Modelo objeto-relacional de Oracle

Las bases de datos relacionales son una tecnología muy madura y eficiente, pero su principal defecto es la planaridad de su modelo. Esto ha dificultado el modelado de los tipos de datos basados en objetos, obligando a establecer capas de transformación del modelo orientado a objetos de la aplicación con el modelo relacional de la base de datos. Esto exigía una doble labor de diseño en ambos planos y el uso de código envolvente (wrapper) entre los dos modelos. Las bases de datos orientadas a objetos aparecen para cubrir esta problemática. Al ser el modelado de los elementos de este software también orientado a objetos, sólo es necesario un único diseño y el wrapper desaparece o se simplifica enormemente. Sin embargo, el nuevo modelo orientado a objetos es mucho más ineficiente y lento. La última opción en aparecer fue la objeto-relacional. Este modelo combina las ventajas de los dos anteriores: la base de datos es relacional (por lo que conserva su rapidez y eficiencia), pero permite hacer uso de nuevos elementos que modelan los objetos a esta base de datos relacional, con lo que el analista y diseñador ve un modelo orientado a objetos. Oracle Database pertenece a este último tipo desde la versión 8i. Los objetos en Oracle 9i se denominan tipos abstractos de datos y poseen funcionalidades de herencia y métodos implementados en SQL o Java. La problemática de la planaridad se resuelve con las tablas anidadas y los arrays variables (VARRAY). Las relaciones entre objetos se establecen mediante el tipo de referencia REF. El almacenamiento de conjuntos de datos no estructurado se realiza con los tipos de objetos grandes. Y la base de datos relacional se puede modelar como objetos mediante las vistas de objeto.

1.1. Tipos abstractos de datos (clases y objetos).

Oracle permite modelar los objetos en filas o columnas: • Un objeto modelado en filas es una tabla (una clase), en la cual cada fila es del tipo del objeto (una instancia), y los atributos nativos del objeto son modelados como columnas. • Un objeto modelado en columnas es una fila dentro de una tabla (o instancia del tipo de objeto), y se modela haciendo corresponder sus atributos con las columnas de la tabla o atributos del objeto respectivamente.

En el ejemplo siguiente se define un objeto Compra, que se modelará con su atributo id seguido de los atributos de un objeto persona: nombre y telefono, y de una referencia a la tabla anidada tabla_detalles. Además declara un método getValor, cuyo cuerpo se definirá posteriormente. -- Se define el objeto Persona con la información del comprador

CREATE TYPE Persona AS OBJECT (

nombre VARCHAR2(30),

telefono VARCHAR2(20)

);

/

-- Se define el objeto LineaDetalle, con la información del producto comprado

CREATE TYPE LineaDetalle AS OBJECT (

producto VARCHAR2(30),

cantidad NUMBER,

precioUnitario NUMBER(12,2)

);

/

-- Se define un nuevo tipo de array (tabla anidada) de líneas de detalle de compra

CREATE TYPE Tabla_detalles AS TABLE OF LineaDetalle;

Page 264: Java y Oracle 11g

Oracle /264

/

-- Se define el objeto Compra, con un ID, un comprador y un conjunto de líneas de detalle de compra

CREATE TYPE Compra AS OBJECT (

id NUMBER,

comprador Persona,

detalles Tabla_detalles,

MEMBER FUNCTION getValor RETURN NUMBER

);

1.1.1. Modificación de los atributos de un tipo de dato. Una vez creado un tipo de dato podemos modificar su lista de atributos mediante el comando ALTER TYPE, con la siguiente sintaxis: ALTER TYPE nombre_del_tipo ADD ATTRIBUTE (nombre_del_atributo tipo_de_dato) CASCADE;

Por ejemplo, supongamos el tipo Telefono_TA y el tipo Telefono_Lista_TA: CREATE OR REPLACE TYPE Telefono_TA AS OBJECT (

codigo CHAR(3),

numero CHAR(8)

) NOT FINAL;

/

CREATE OR REPLACE TYPE Telefono_Lista_TA AS TABLE OF Telefono_TA;

/

Si queremos añadir un nuevo atributo al tipo Telefono_TA, al usar el siguiente comando se producirá un error: CREATE OR REPLACE TYPE Telefono_TA AS OBJECT (

Pais CHAR(3),

codigo CHAR(3),

numero CHAR(8)

) NOT FINAL;

/

El error se debe a la dependencia del tipo Telefono_TA respecto al tipo Telefono_Lista_TA. Para añadir el nuevo atributo debemos utilizar el siguiente comando: ALTER TYPE Telefono_TA ADD ATTRIBUTE (pais CHAR(3)) CASCADE;

1.2. Seguridad para tipos de datos abstractos.

El ejemplo precedente asume que el mismo usuario es propietarios de los tipos de dato Persona, LineaDetalle y Compra. ¿Qué ocurriría si el propietario de Persona fuese diferente del propietario de Compra? Por ejemplo, supongamos que la cuenta llamada Dora es propietaria del tipo de dato Persona, y el usuario de la cuenta George intenta crear un tipo de dato Cliente_TA. George ejecuta el siguiente comando: REM Mientras está conectado como George:

CREATE TYPE Cliente_TA AS OBJECT (

Identidad Persona,

Direccion VARCHAR2(100)

);

Si George no es propietario del tipo abstracto Persona, Oracle responderá a este comando CREATE TYPE con el siguiente mensaje: Warning: Tipo creado con errores de compilación.

Los errores de compilación son causados por problemas al crear el método constructor (un método especial creado por Oracle para el tipo de dato). Oracle no puede resolver la referencia al tipo de dato Persona porque George no es propietario de un tipo de dato con ese nombre. George podría intentar ejecutar otra vez el comando CREATE TYPE para referenciar especialmente el tipo de dato Persona perteneciente a Dora: CREATE OR REPLACE TYPE Cliente_TA AS OBJECT (

Identidad Dora.Persona,

Direccion VARCHAR2(100)

);

/

Warning: Tipo creado con errores de compilación.

Para ver los errores asociados con la creación del tipo de dato podemos usar el comando SHOW ERRORS: SHOW ERRORS

Errores para el tipo CLIENTE:

LINE/COL ERROR

Page 265: Java y Oracle 11g

Oracle /265

------------ --------------------------------------------------------------------------

0/0 PL/SQL: Análisis de la unidad de compilación terminada

3/11 PLS-00201: Identificador 'DORA.PERSONA' debe ser declarado

George no será capaz de crear el tipo de dato Cliente_TA a menos que Dora primero le conceda el permiso EXECUTE sobre su tipo. Esto se muestra a continuación: REM Mientras está conectado como Dora:

GRANT EXECUTE ON Persona TO George;

Ahora George puede crear el tipo de datos basado en el tipo Persona, propiedad de Dora. Usar tipos de datos de otros usuarios no es trivial. Por ejemplo, durante operaciones de inserción, debemos especificar el propietario de cada tipo o usar un sinónimo. George puede crear una tabla basada sobre el tipo Cliente_TA (el cual incluye el tipo de datos Dora.Persona), tal como se muestra a continuación: CREATE TABLE CLIENTES (

Cliente_Id NUMBER,

Cliente Cliente_TA) ;

Una inserción en la tabla CLIENTES debería tener el siguiente formato: INSERT INTO CLIENTES VALUES

( 1, Cliente_TA( Persona('Juan Pérez', '111111111'), 'Calle Ronda – 25') );

Ya que George no es propietario del tipo Persona, este comando fallará. Durante la inserción, se usa el método constructor de Persona (y Dora es su propietario). Por lo tanto, debemos modificar el comando INSERT para especificar que Dora es el propietario del tipo Persona. El siguiente ejemplo muestra el comando INSERT correcto: INSERT INTO CLIENTES VALUES

( 1, Cliente_TA( Dora.Persona('Juan Pérez', '111111111'), 'Calle Ronda – 25') );

George puede simplificar el SQL creando un sinónimo llamado Persona: CREATE PUBLIC SYNONYM Persona FOR Dora.Persona;

Este sinónimo puede ser usado en consultas DML y DDL. Podemos usar un sinónimo para otros tipos de datos de usuario durante consultas e inserciones. Por ejemplo, George inserta clientes ahora que existe el sinónimo Persona: INSERT INTO CLIENTES VALUES

( 1, Cliente_TA( Persona('Juan Pérez', '111111111'), 'Calle Ronda – 25') );

Nota. Cuando creamos un sinónimo, Oracle no verifica la validez del objeto para el cual es creado el sinónimo. Cuando se usa CREATE SYNONYM X FOR Y, Oracle no verifica que Y sea un nombre de objeto válido o un tipo de objeto válido. La validez de este objeto es sólo verificada cuando el objeto es accedido a través del sinónimo.

En una implementación solo relacional de Oracle podemos conceder el permiso EXECUTE sobre objetos como procedimientos y paquetes. Dentro de la implementación objeto-relacional de Oracle, el permiso EXECUTE se extiende para cubrir tipos de datos abstractos. El permiso EXECUTE es apropiado porque los tipos de datos abstractos pueden incluir métodos (funciones PL/SQL y procedimientos que operan sobre el tipo de datos). Si concedemos algún permiso para usar nuestro tipo de datos, estamos concediendo al usuario el permiso de ejecutar los métodos definidos dentro del tipo de datos. Aunque Dora aún no definiera ningún método sobre el tipo Persona, Oracle crea automáticamente un método constructor para acceder a los datos. Cualquier objeto (como Cliente_TA) que usa el tipo Persona usa su método constructor asociado. Incluso si no hemos creado ningún método para nuestro tipo de dato abstracto, todavía tiene procedimientos asociados con él. Podemos describir la tabla CLIENTES: DESCRIBE CLIENTES;

Name Null? Type

------------------- -------- -----------------

CLIENTE_ID NUMBER

CLIENTE CLIENTE_TA

Podemos usar el comando SET DESCRIBE DEPTH en SQL*Plus para mostrar los atributos del tipo de dato para tablas que usan características objeto-relacional. Podemos especificar valores de profundidad de 1 a 50: SET DESCRIBE DEPTH 2

DESC CLIENTES

Page 266: Java y Oracle 11g

Oracle /266

Name Null? Type

------------------- -------- -----------------

CLIENTE_ID NUMBER

CLIENTE CLIENTE_TA

IDENTIDAD PERSONA

DIRECCION VARCHAR(100)

SET DESCRIBE DEPTH 3

DESC CLIENTES

Name Null? Type

------------------- -------- -----------------

CLIENTE_ID NUMBER

CLIENTE CLIENTE_TA

IDENTIDAD PERSONA

NOMBRE VARCHAR2(30)

TELEFONO VARCHAR2(20)

DIRECCION VARCHAR(100)

1.3. Herencia de clases.

Oracle permite la herencia simple de clases a través del modificador UNDER. Siguiendo el ejemplo del apartado anterior, añadimos una fecha a las compras creando una subclase: CREATE TYPE CompraConFecha UNDER Compra (

fecha DATE

) NOT FINAL;

La cláusula FINAL indica si se puede heredar la clase por un subtipo (NOT FINAL) o si es una clase final y por lo tanto no se puede extender (FINAL). En este ejemplo sí es heredable. Otra cláusula es INSTANTIABLE, para indicar que la clase no es abstracta y por tanto se puede instanciar, o NOT INSTANTIABLE, para indicar que la clase es abstracta y no se puede instanciar. La cláusula TREAT es el operador de moldeo de un tipo de objeto a subtipos del mismo. Si el moldeo no es posible se devuelve NULL. El siguiente ejemplo busca en la tabla relacional TRCompraConFecha, asociada al tipo CompraConFecha, y devuelve los registros de respuesta como objetos Compra. SELECT VALUE(TREAT(T AS Compra))

FROM TRCompraConFecha T

WHERE VALUE(T.comprador) = Persona('Juan','555');

El operador de predicados para saber si un objeto instanciado pertenece a una clase es IS OF. IF c1 IS OF (Compra) THEN

DBMS_OUTPUT.PUT_LINE('El objeto referenciado por la variabla c1 es de tipo Compra');

END IF;

1.4. Métodos.

Existen tres tipos de métodos a definir en objetos de Oracle: métodos propiamente dichos, métodos de comparación y constructores. 1.4.1. Administrando métodos. Podemos añadir nuevos métodos a un tipo de datos modificando el tipo de datos (mediante el comando ALTER TYPE). Por ejemplo, para añadir un método al tipo de dato Persona ya creado: ALTER TYPE Persona ADD

MEMBER FUNCTION ObtenNombreTelefono RETURN VARCHAR2 CASCADE;

Cuando se modifica un tipo de datos se listan todos sus métodos, tanto los viejos como los nuevos. Después de modificar el tipo de dato debemos modificar el cuerpo del tipo. CREATE OR REPLACE TYPE BODY Persona IS

MEMBER FUNCTION ObtenNombreTelefono RETURN VARCHAR2 CASCADE IS

BEGIN

RETURN Nombre || ' (' || telefono || ')';

END,

END;

No necesitamos tener el permiso EXECUTE sobre las funciones o procedimientos miembros de un tipo de dato abstracto. Si concedemos a otro usuario el permiso EXECUTE sobre el tipo Persona, este usuario adquiere automáticamente el permiso EXECUTE sobre los métodos que son parte del tipo de dato.

Page 267: Java y Oracle 11g

Oracle /267

Cuando creamos funciones miembro podemos especificar un método de comparación (o bien de tipo MAP o bien de tipo ORDER) o ninguno. 1.4.2. Definición de métodos. Los métodos (su firma) se indican en la definición del objeto, pero se implementan posteriormente mediante el comando CREATE TYPE BODY. El siguiente código muestra cómo implementar el cuerpo del método getValor() de la clase Compra para obtener el precio de venta de cada compra: CREATE TYPE BODY Compra AS

id NUMBER,

comprador Persona,

detalles Tabla_detalles,

MEMBER FUNCTION getValor RETURN NUMBER

IS

Total NUMBER := 0;

BEGIN

FOR I IN detalles.FIRST..detalles.LAST LOOP

Total := Total + ( detalles(I).cantidad * detalles(I).precioUnitario );

END LOOP;

RETURN Total;

END;

END;

1.4.3. Especificación de los métodos. La especificación de un método se hace junto a la creación de su tipo, y puede llevar asociada una directiva de compilación PRAGMA RESTRICT_REFERENCES(método, lista modificadores) para evitar que el método manipule la base de datos o las variables del paquete PL/SQL. Por ejemplo: CREATE TYPE Compra AS OBJECT (

id NUMBER,

comprador Persona,

detalles Tabla_detalles,

MEMBER FUNCTION getValor RETURN NUMBER,

PRAGMA RESTRICT_REFERENCES(getValor, WNDS, RNDS)

);

Los modificadores de la directiva pueden ser alguno de los siguientes: • WNDS: no se permite al método modificar las tablas de la base de datos • WNPS: no se permite al método modificar las variables del paquete PL/SQL • RNDS: no se permite al método leer las tablas de la base de datos • RNPS: no se permite al método leer las variables del paquete PL/SQL

Los métodos se pueden ejecutar sobre los objetos de su mismo tipo. Si c es una variable PL/SQL que almacena un objeto del tipo Compra, entonces c.getValor() retorna el valor total de la compra. 1.4.4. Constructores. Los métodos constructores, que son implícitamente creados por la base de datos, tienen como objetivo crear instancias de objetos a partir del estado definido por sus atributos. El constructor implícito creado por la base de datos tiene como nombre el mismo que la clase y como parámetros sus atributos en el mismo orden en que se definieron en la clase. CREATE TYPE Cliente_t AS OBJECT (

id NUMBER,

nombre VARCHAR2(20),

telefono VARCHAR2(30)

);

/

DECLARE

cust1 Cliente_t:= Cliente_t(103, 'Ravi', '1-800-555-1212');

cust2 Cliente_t := NEW Cliente_t(104, 'Ronn', 1-700-444-1212');

Aún así, es posible definir constructores propios en que se soliciten otros atributos y el proceso de construcción no sea simplemente la asignación de valores a sus atributos. CREATE TYPE Cliente_t AS OBJECT (

id NUMBER,

Page 268: Java y Oracle 11g

Oracle /268

nombre VARCHAR2(20),

telefono VARCHAR2(30),

CONSTRUCTOR FUNCTION Cliente_t(id NUMBER, nombre VARCHAR2)

RETURN SELF AS RESULT

);

/

CREATE TYPE BODY Cliente_t AS

CONSTRUCTOR FUNCTION Cliente_t(id NUMBER, nombre VARCHAR2)

RETURN SELF AS RESULT

AS

BEGIN

SELF.id := id;

SELF.nombre := nombre;

RETURN;

END;

END;

/

DECLARE

Cliente_t := NEW Cliente_t(103, 'Ravi');

También se pueden crear métodos estáticos que actúen como constructores: CREATE TYPE Cliente_t AS OBJECT (

id NUMBER,

nombre VARCHAR2(20),

telefono VARCHAR2(30),

STATIC FUNCTION CrearCliente(id NUMBER, nombre VARCHAR2) RETURN Cliente_t

);

/

CREATE TYPE BODY Cliente_t AS

STATIC FUNCTION CrearCliente(id NUMBER, nombre VARCHAR2) RETURN Cliente_t

IS

BEGIN

RETURN Cliente_t(id, nombre, NULL);

END;

END;

/

DECLARE

Cliente_t:= Cliente_t.CrearCliente(103, 'Ravi');

1.4.5. Métodos de comparación. Para comparar los objetos de cierto tipo es necesario indicar a Oracle cuál es el criterio de comparación. Para ello hay que escoger entre un método MAP u ORDER, debiéndose definir sólo uno de estos métodos por cada tipo de objeto que necesite ser comparado. La diferencia entre ambos tipos de métodos es la siguiente:

• Un método MAP sirve para indicar cuál de los atributos del tipo se utilizará para ordenar los objetos del tipo, y por tanto se puede utilizar para comparar los objetos de ese tipo por medio de los operadores de comparación aritméticos (<, >). Por ejemplo, la siguiente declaración permite establecer que los objetos Persona se van a comparar por su atributo nombre sin tener en cuenta mayúsculas y minúsculas:

CREATE TYPE Persona AS OBJECT (

nombre VARCHAR2(30),

telefono VARCHAR2(20),

MAP MEMBER FUNCTION RetornaNombre RETURN VARCHAR2

);

/

CREATE TYPE BODY Persona AS

MAP MEMBER FUNCTION RetornaNombre RETURN VARCHAR2

IS

BEGIN

RETURN UPPER(nombre);

END;

END;

Page 269: Java y Oracle 11g

Oracle /269

• Un método ORDER utiliza los atributos del objeto sobre el que se ejecuta para realizar un cálculo y compararlo con otro objeto del mismo tipo pasado como argumento de entrada. Este método devolverá un valor negativo si el parámetro de entrada es mayor que el atributo, un valor positivo si ocurre lo contrario y un cero si ambos son iguales. El siguiente ejemplo define un orden para el tipo Persona

diferente al anterior. CREATE TYPE Persona AS OBJECT (

nombre VARCHAR2(30),

telefono VARCHAR2(20),

ORDER MEMBER FUNCTION Compara( P Persona) RETURN INTEGER

);

/

CREATE TYPE BODY Persona AS

ORDER MEMBER FUNCTION Compara( P Persona) RETURN INTEGER

IS

BEGIN

IF (SELF.nombre = P.nombre) THEN

RETURN 0;

ELSE

IF (SELF.nombre < P.nombre) THEN

RETURN -1;

ELSE

RETURN 1;

END IF;

END IF;

END;

END;

Ahora ya podemos comparar dos objetos Persona: DECLARE

p1 Persona := Persona('Juan', '111111');

p2 Persona := Persona('LUIS', '222222');

BEGIN

IF p1 < p2 THEN

-- p1 es menor que p2

ELSE

-- p1 es mayor o igual que p2

END IF;

END;

Sólo una de estas definiciones (MAP u ORDER) puede ser válida en un tiempo dado. Si un tipo de objeto no tiene definido ninguno de estos métodos, Oracle es incapaz de deducir cuándo un objeto es mayor o menor que otro. Sin embargo, sí puede determinar cuándo dos objetos del mismo tipo son iguales. Para ello, el sistema compara el valor de los atributos de los objetos uno a uno:

• Si todos los atributos son no nulos e iguales, Oracle indica que ambos objetos son iguales. • Si alguno de los atributos no nulos es distinto en los dos objetos, entonces Oracle dice que son diferentes. • En otro caso, Oracle dice que no puede comparar ambos objetos.

Estas funciones son muy útiles para funciones de agrupación del estilo DISTINCT, GROUP BY y ORDER BY.

1.5. Tablas relacionales de objetos.

Una vez definidos los tipos, éstos pueden utilizarse para definir nuevos tipos, tablas relacionales que almacenen objetos de esos tipos, o para definir el tipo de los atributos de una tabla relacional. 1.5.1. Creación de tablas de objetos. Una tabla relacional de objetos es una clase especial de tabla que almacena un objeto en cada fila y que facilita el acceso a los atributos de esos objetos como si fueran columnas de la tabla. Por ejemplo, se puede definir una tabla relacional para almacenar objetos Persona: CREATE TABLE TRPersona OF Persona

(nombre PRIMARY KEY);

Y otra para almacenar la dependencia de trabajo entre dos personas:

Page 270: Java y Oracle 11g

Oracle /270

CREATE TABLE TRJefeEmpleado (

jefe Persona,

empleado Persona);

La diferencia entre la primera tabla (TRPersona) y la segunda (TRJefeEmpleado) es que la primera almacena objetos con su propia identidad (OID) y la segunda no es una tabla de objetos, sino una tabla con dos columnas con un tipo de datos de objeto.

Nota. No podemos borrar y recrear un tipo que es usado por una tabla.

Oracle añade un campo identificador a cada objeto de una tabla relacional para poder referenciarlo. Este identificador puede coincidir con el campo clave de los registros si especificamos: CREATE TABLE TRPersona OF Persona (

nombre PRIMARY KEY

) OBJECT IDENTIFIER IS PRIMARY KEY;

O bien puede ser generado automáticamente si especificamos: CREATE TABLE TRPersona OF Persona (

nombre PRIMARY KEY

) OBJECT IDENTIFIER IS SYSTEM GENERATED;

Además de esto, Oracle permite considerar una tabla de objetos desde dos puntos de vista: • Como una tabla con una sola columna cuyo tipo es el de un tipo de objetos. • Como una tabla que tiene tantas columnas como atributos los objetos que almacena.

Por ejemplo, se puede ejecutar una de las dos instrucciones siguientes. INSERT INTO TRPersona VALUES (

'Juan Pérez',

'696-779789');

/

SELECT VALUE(T) FROM TRPersona T

WHERE T.nombre = 'Juan Pérez';

En la primera instrucción, la tabla TRPersona se considera como una tabla con varias columnas cuyos valores son los especificados. En el segundo caso, se la considera como con una tabla de objetos que en cada fila almacena un objeto. En esta instrucción, la cláusula VALUE permite visualizar el valor de un objeto. Las reglas de integridad, de clave primaria, y el resto de propiedades que se definan sobre una tabla, sólo afectan a los objetos de esa tabla; es decir, no se refieren a todos los objetos del tipo asignado a la tabla. Para crear una tabla relacional que almacene objetos que contengan tablas anidadas debemos especificar una tabla relacional que almacene los elementos de la tabla anidada. Por ejemplo: -- Se crea una tabla relacional 'TRCompra' cuyos registros mapearán los objetos 'Compra'

-- También se crea una tabla relacional 'TRDetalles' cuyos registros mapearán los objetos 'LineaDetalle'

--contenidos en la tabla anidada 'detalles'

CREATE TABLE TRCompra OF Compra (id PRIMARY KEY) OBJECT IDENTIFIER IS PRIMARY KEY

NESTED TABLE detalles STORE AS TRDetalles;

La tabla anidada TRDetalles no podrá ser accedida directamente. Su contenido debe ser accedido a través de los objetos de la tabla TRCompra. 1.5.2. Inserción y acceso a los datos en tablas de objetos. Toda clase o tipo de array dispone de un constructor, el cual es creado automáticamente para poder instanciar un objeto de la clase o tipo. Este constructor tiene como parámetros los atributos que definen la clase en el mismo orden en que fueron definidos. Por ejemplo, para insertar un nuevo objeto de tipo Compra en la base de datos: INSERT INTO TRCompra VALUES (

45,

Persona ('Juan', '555'),

Tabla_detalles (

LineaDetalle('Peras', 4, 3.5),

LineaDetalle('Churros', 12, 0.4)

)

);

En una base de datos con tipos y objetos, lo más recomendable es utilizar siempre alias para los nombres de las tablas. El alias de una tabla debe ser único en el contexto de la consulta. Los alias sirven para acceder al contenido de la tabla, pero en el caso de las tablas que almacenan objetos, el alias también sirve como

Page 271: Java y Oracle 11g

Oracle /271

referencia del objeto, y por tanto se utiliza para acceder a los atributos, métodos y referencias mediante la nomenclatura habitual del punto entre objeto y atributo. Por ejemplo, para hacer una petición de elementos: SELECT T.*

FROM TRCompra T

WHERE T.comprador.nombre='Juan' AND T.comprador.telefono='555';

O bien: SELECT VALUE(T)

FROM TRCompra T

WHERE T.comprador = Persona('Juan', '555');

Ambos devuelven el mismo valor, salvo que la primera en forma de tabla, siendo cada columna un atributo del objeto resultado de la consulta. Y en el segundo devuelve el objeto con la forma de constructor explicada en el ejemplo anterior. 1.5.3. Llamadas a métodos. Para invocar un método hay que utilizar su nombre y unos paréntesis que encierren sus argumentos de entrada. Si el método no tiene argumentos, se especifican los paréntesis aunque estén vacíos. Por ejemplo, la siguiente consulta es correcta: SELECT T.getValor() FROM TRCompra T;

1.5.4. Índices para tablas de objetos. La creación de índices en objetos es igual a la de tablas relacionales, identificando el elemento de indexación mediante la referencia al atributo índice. Por ejemplo, para indexar la tabla TRCompra por el nombre del comprador: CREATE INDEX NombreComprador ON TRCompra (comprador.nombre);

1.6. Tipos referencia (REF).

Los identificadores únicos asignados por Oracle a los objetos que se almacenan en una tabla relacional, permiten que éstos puedan ser referenciados desde los atributos de otros objetos o desde las columnas de tablas. El tipo de datos proporcionado por Oracle para soportar esta facilidad se denomina REF. Un atributo de tipo REF almacena una referencia a un objeto del tipo definido, e implementa una relación de asociación entre los dos tipos de objetos. Estas referencias se pueden utilizar para acceder a los objetos referenciados y para modificarlos; sin embargo, no es posible operar sobre ellas directamente. Para asignar o actualizar una referencia se debe utilizar siempre REF o NULL. Cuando se define una columna de un tipo a REF, es posible restringir su dominio a los objetos que se almacenen en cierta tabla. Si la referencia no se asocia a una tabla sino que sólo se restringe a un tipo de objeto, se podrá actualizar a una referencia a un objeto del tipo adecuado con independencia de la tabla donde se almacene. En este caso su almacenamiento requerirá más espacio y su acceso será menos eficiente. El siguiente ejemplo redefine el tipo Compra y restringe el dominio de su campo comprador a los objetos de cierta tabla. -- Tabla relacional cuyos registros se mapean con objetos "Persona"

CREATE TABLE TRPersona OF Persona;

-- Modificación de la clase "Compra" para que referencie a un objeto "Persona" de la tabla "TRPersona"

CREATE TYPE Compra AS OBJECT (

id NUMBER,

comprador REF Persona,

detalles Tabla_detalles

);

-- Tabla relacional cuyos registros se mapean con objetos "Compra"

CREATE TABLE TRCompra OF Compra (

PRIMARY KEY (id),

SCOPE FOR (comprador) IS TRPersona -- o bien: comprador SCOPE IS TRPersona

);

Cuando se borran objetos de la base de datos puede ocurrir que otros objetos que referencien a los borrados queden en estado inconsistente. Estas referencias se denominan dangling references, y Oracle proporciona el predicado llamado IS DANGLING que permite comprobar cuándo sucede esto. Por ejemplo, la siguiente instrucción pone a nulo el comprador que ha perdido su referencia: UPDATE TRCompra SET comprador = NULL WHERE comprador IS DANGLING;

Los tipos referencia permiten navegar a través de la estructura de objetos de la misma manera que si fuesen un atributo del objeto, mediante el operador punto.

Page 272: Java y Oracle 11g

Oracle /272

SELECT T.comprador.nombre FROM TRCompra T;

Oracle posee dos funciones para los tipos referencia: REF() que devuelve el identificador de objeto dado la instancia de un objeto, y DEREF() que dado el identificador de un objeto devuelve la instancia del objeto (y por tanto es el opuesto de la función REF). Si queremos insertar un objeto en la tabla TRCompra deberemos obtener la referencia de un objeto de la tabla TRPersona de la siguiente manera: INSERT INTO TRCompra (id, comprador, detalles)

SELECT 1, REF(tr), NULL FROM TRPersona tr WHERE nombre='José Pérez';

Para mostrar el contenido de la tabla, ahora debemos aplicar la función DEREF: SELECT id, DEREF(comprador), detalles FROM TRCompra;

1.7. Tablas anidadas y arrays variables.

Como ya se ha visto, los arrays variables (VARRAY) y las tablas anidadas (TABLE) permiten modelar las relaciones de uno a varios que son muy comunes en los modelos orientados a objetos. Estos tipos se denominan colecciones porque representan conjuntos de datos de un mismo tipo. Las tablas anidadas permiten almacenar un conjunto indeterminado de elementos, y por eso no es posible almacenar su contenido dentro del registro que la incluye como campo. Cuando creemos un atributo de tipo tabla anidada debemos indicar siempre qué tabla externa será la que almacene los datos. En el ejemplo del apartado anterior CREATE TABLE TRCompra OF Compra

NESTED TABLE detalles STORE AS TRDetalles;

TRDetalles es la tabla externa que almacenará los elementos LineaDetalle de la tabla anidada detalles. Esta tabla relacional quedará oculta en el diccionario de datos y su acceso sólo podrá realizarse a través de la tabla principal TRCompra. Sin embargo, los arrays variables son de un tamaño máximo fijo, por lo que no es necesario guardarlos en una tabla externa y se almacenan en la propia tabla principal (u objeto). Hay dos formas de acceder a los elementos de las colecciones:

• La primera es recibiendo la colección como un solo elemento en la forma de su constructor. • La segunda es acceder a su contenido como un conjunto de registros mediante la función TABLE().

Un ejemplo del primer tipo sería el siguiente, en el que se devuelve un valor de tipo colección que engloba a todos los valores de la colección: SELECT T.id, T.detalles FROM TRCompra T;

Un resultado posible será: Id detalles

--- -----------–––––––------------------------------------------------------------

1 Tabla_detalles (LineaDetalle('P1',4,3.5), LineaDetalle('P2',12,0.4))

2 Tabla_detalles (LineaDetalle('P1',9,7))

Un ejemplo del segundo tipo es el siguiente en el que se devuelven las líneas de detalle para el pedido de id 1: SELECT * FROM TABLE( SELECT T.detalles FROM TRCompra T WHERE T.id=1);

El resultado será: Producto Cantidad PrecioUnitario

---------- ---------- -–––––––––-------

P1 4 3.5

P2 12 0.4

Igualmente, para la inserción o modificación de colecciones podemos hacer uso del constructor de la colección para introducirle el conjunto de valores por completo, modificándose todo el array variable o tabla anidada de una vez: INSERT INTO TRCompra VALUES (

1,

Persona('Juan', '555'),

Tabla_detalles(

LineaDetalle('chorizo', 4, 3.5),

LineaDetalle('jamón', 3, 7.6)

)

);

O bien (sólo para tablas anidadas) podemos insertar tuplas en la columna correspondiente de la tupla seleccionada por una subconsulta usando la palabra clave THE con la siguiente sintaxis:

Page 273: Java y Oracle 11g

Oracle /273

INSERT INTO THE (SELECT T.detalles FROM TRCompra T WHERE id = 45)

VALUES (LineaDetalle('Nuevo producto', 3, 5.2));

Esta técnica es especialmente útil si dentro de una tabla anidada se guardan referencias a otros objetos. También en el caso de una tabla anidada (no es posible en un array variable), podemos acceder a la tabla directamente haciendo uso de la función TABLE(), lo que nos permitiría actualizar de forma selectiva los elementos: UPDATE TABLE(SELECT T.detalles FROM TRCompra T WHERE T.id=1) P

SET VALUE(P) = LineaDetalle('chorizo', 6, 5.5)

WHERE P.producto = 'choriz';

Para poner condiciones a las tuplas de una tabla anidada, se pueden utilizar cursores dentro de un SELECT o desde un programa PL/SQL. Por ejemplo, la siguiente consulta recupera las compras con su código de compra, el nombre del comprador y las líneas de detalle con cantidades mayores de 5: SELECT T.id, T.comprador.nombre, CURSOR(SELECT * FROM TABLE(T.detalles) D WHERE D.cantidad>5)

FROM TRCompra T;

La cláusula THE también sirve para seleccionar las tuplas de una tabla anidada. Por ejemplo, para seleccionar la primera línea de detalle de la compra de código 45: SELECT LP.*

FROM THE (SELECT T.detalles FROM TRCompra T WHERE T.id=45) LP

WHERE ROWNUM=1;

1.7.1. Funciones adicionales para tablas anidadas y arrays variables. Desde Oracle Database 10g, podemos usar varias funciones nuevas para tablas anidadas y arrays variables. Para los siguientes ejemplos usaremos los siguientes tipos y tablas: CREATE TYPE ANIMALES_AV AS VARRAY(10) OF VARCHAR(20);

CREATE TYPE ANIMALES_TA AS TABLE OF VARCHAR(20);

CREATE TABLE GRANJA (

Propietario VARCHAR2(200),

Ubicacion VARCHAR2(200),

Animales ANIMALES_AV

);

La función CARDINALITY retorna el número de elementos dentro de una tabla anidada o array variable: SELECT CARDINALITY(Animales) FROM GRANJA;

La función MULTISET EXCEPT toma dos tablas anidadas como entrada y retorna el conjunto de registros que están en la primera tabla anidada pero no en la segunda (similar al operador de resta). DECLARE

A1 ANIMALES_TA := ANIMALES_TA ('PERRO', 'GATO');

A2 ANIMALES_TA := ANIMALES_TA('GALLINA', 'PERRO');

A3 ANIMALES_TA;

BEGIN

A3 := A1 MULTISET EXCEPT A2;

-- Como resultado: A3 := ANIMALES_TA('GATO')

END;

La función MULTISET INTERSECT toma dos tablas anidadas como entrada y retorna los elementos en común. DECLARE

A1 ANIMALES_TA := ANIMALES_TA ('PERRO', 'GATO');

A2 ANIMALES_TA := ANIMALES_TA('GALLINA', 'PERRO');

A3 ANIMALES_TA;

BEGIN

A3 := A1 MULTISET INTERSECT A2;

-- Como resultado: A3 := ANIMALES_TA('PERRO')

END;

La función MULTISET UNION toma dos tablas anidadas como entrada y retorna los elementos de ambas tablas. DECLARE

A1 ANIMALES_TA := ANIMALES_TA ('PERRO', 'GATO');

A2 ANIMALES_TA := ANIMALES_TA('GALLINA', 'PERRO');

A3 ANIMALES_TA;

BEGIN

Page 274: Java y Oracle 11g

Oracle /274

A3 := A1 MULTISET INTERSECT DISTINCT A2;

-- Como resultado: A3 := ANIMALES_TA('PERRO', 'GATO', 'GALLINA')

END;

Para esta función podemos especificar MULTISET UNION ALL (por defecto) o MULTISET UNION DISTINCT. La función SET convierte una tabla anidada dentro de un conjunto mientras elimina duplicados, retornando una tabla anidada de valores distintos: DECLARE

A1 ANIMALES_TA := ANIMALES_TA ('PERRO', 'GATO', 'GALLINA', 'PERRO');

A2 ANIMALES_TA;

BEGIN

A3 := SET(A1);

-- Como resultado: A3 := ANIMALES_TA('PERRO', 'GATO', 'GALLINA')

END;

La función COLLECT toma una columna como entrada y crea una tabla anidada del tipo de entrada. Para obtener los resultados de esta función debemos usarla dentro de una función CAST con un comando SELECT con la cláusula GROUP BY. CREATE OR REPLACE TYPE varchar2_ntt AS TABLE OF VARCHAR2(200);

SELECT Propietario, CAST(COLLECT(Ubicacion) AS varchar2_ntt)

FROM GRANJA

GROUP BY Propietario;

Un posible resultado de esta consulta puede ser:

PROPIETARIO CAST(COLLECT(UBICACION)ASVARCHAR2_NTT)

-------------------------- -----------------------------------------------------------

Juan Pérez VARCHAR2_NTT('Madrid', 'Castellón')

María Belo VARCHAR2_NTT('Alicante')

La función POWERMULTISET toma como entrada una tabla anidada y retorna una tabla anidada cuyos elementos son tablas anidadas que contienen todos los subconjuntos no vacíos de la tabla de entrada. SELECT CAST(POWERMULTISET(Animales) AS ANIMALES_TA)

FROM GRANJA;

La función POWERMULTISET_BY_CARDINALITY toma como entrada una tabla anidada y una cardinalidad y retorna una tabla anidada cuyos elementos son tablas anidadas que contienen los subconjuntos no vacíos de la tabal de entras en la cardinalidad especificada. SELECT CAST(POWERMULTISET_BY_CARDINALITY(Animales, 2) AS ANIMALES_TA)

FROM GRANJA;

1.8. Vistas de objeto.

Para poder convertir el modelo relacional en el modelo orientado a objetos, sin necesidad de modificar los datos ni su estructura (metadatos), es posible generar vistas de los datos relacionales de tal manera que éstos puedan ser vistos como objetos. CREATE TABLE Tabla_empleado (

empNum NUMBER (5),

eNombre VARCHAR2 (20),

salario NUMBER (9, 2),

trabajo VARCHAR2 (20)

);

/

CREATE TYPE Empleado_t AS OBJECT(

empNum NUMBER (5),

eNombre VARCHAR2 (20),

salario NUMBER (9, 2),

trabajo VARCHAR2 (20),

MEMBER FUNCTION SalarioNeto RETURN NUMBER

);

/

CREATE VIEW vistaEmpleado OF Empleado_t WITH OBJECT IDENTIFIER (empNum) AS

SELECT e.empNum, e.eNombre, e.salario, e.trabajo

FROM Tabla_empleado e

Page 275: Java y Oracle 11g

Oracle /275

WHERE trabajo = 'Programador';

Al crear la vista de objetos debemos especificar el campo que actuará como identificador de cada objeto y que será normalmente el campo clave de la tabla base. La ventaja de las vistas de objeto está en la capacidad de invocar los métodos del objeto en las consultas: SELECT v.empNo, v.eNombre, v.SalarioNeto()

FROM vistaEmpleado v;

1.9. Trabajando con tipos SQL desde aplicaciones JDBC.

1.9.1. Recuperación de objetos Oracle en objetos «oracle.sql.STRUCT». Podemos recuperar un objeto Oracle directamente dentro de una instancia de oracle.sql.STRUCT. En el siguiente ejemplo, se usa el método getObject() para obtener un objeto de una tabla de objetos. Connection conexion = DriverManager.getConnection( cadenaDeConexion, login, password );

// Se crea un tipo de objeto y una tabla de objetos

String cmd = "CREATE TYPE Tipo_Lote AS OBJECT (codigo NUMBER, fecha DATE)";

Statement stmt = conexion.createStatement();

stmt.execute(cmd);

cmd = "CREATE TABLE Tabla_Lote OF Tipo_Lote";

stmt.execute(cmd);

// Se insertan dos registros en la tabla de objetos

cmd = "INSERT INTO Tabla_Lote VALUES (Tipo_Lote(10,'01-Abr-11'))";

stmt.execute(cmd);

cmd = "INSERT INTO Tabla_Lote VALUES (Tipo_Lote(20,'02-May-11'))";

stmt.execute(cmd);

// Se recuperan los registros de la tabla de objetos

ResultSet rs= stmt.executeQuery("SELECT * FROM Tabla_Lote");

rs.next();

// Se accede al primer registro

oracle.sql.STRUCT oracleSTRUCT = (oracle.sql.STRUCT) rs.getObject(1);

Otro modo es retornar los objetos como un objeto STRUCT es moldear el resultado a un objeto OracleResultSet y usar el método de Oracle getSTRUCT(): oracle.sql.STRUCT oracleSTRUCT = ((OracleResultSet) rs).getSTRUCT(1);

Si queremos acceder ahora a los atributos del STRUCT como tipos oracle.sql, se usa el método getOracleAttributes(): oracle.sql.Datum[] attrs = oracleSTRUCT.getOracleAttributes();

1.9.2. Enlazando objetos «STRUCT» dentro de un comando. Para enlazar un objeto oracle.sql.STRUCT a un comando del tipo PreparedStatement o CallableStatement, podemos usar el método estándar setObject(), o moldear el objeto de comando a un OraclePreparedStatement u OracleCallableStatement, y usar el método setOracleObject(). Siguiendo con el ejemplo anterior: PreparedStatement ps =

conexion.prepareStatement("UPDATE Tabla_Lote T SET VALUE(T) = ? WHERE VALUE(T).codigo=10");

StructDescriptor sd = new StructDescriptor("Tipo_Lote", conexion);

STRUCT miSTRUCT = new STRUCT (sd, conexion, new Object [] {11, new java.sql.Date(2011,12,22)});

ps.setObject(1, miSTRUCT, Types.STRUCT);

ps.execute()

O bien: PreparedStatement ps =

conexion.prepareStatement("UPDATE Tabla_Lote T SET VALUE(T) = ? WHERE VALUE(T).codigo=10");

StructDescriptor sd = new StructDescriptor("Tipo_Lote", conexion);

STRUCT miSTRUCT = new STRUCT (sd, conexion, new Object [] {11, new java.sql.Date(2011,12,22)});

((OraclePreparedStatement) ps).setOracleObject(1, miSTRUCT);

ps.execute()

1.10. Crear y usar clases de objetos Java personalizadas para objetos Oracle.

Si queremos crear clases de objetos personalizadas para los objetos Oracle, entonces debemos definir entradas en el mapa de tipos que especifique las clases de objetos que los drivers deben instanciar para los correspondientes tipos de Oracle. También debemos proporcionar un modo de crear y poblar instancias de la clase personalizada desde los objetos Oracle y sus atributos. Para crear y poblar clases personalizadas y proporcionar capacidades de lectura/escritura podemos elegir entre dos interfaces a implementar:

Page 276: Java y Oracle 11g

Oracle /276

• La interfaz de JDBC estándar java.sql.SQLData. • Las interfaces oracle.sql.ORAData y oracle.sql.ORADataFactory proporcionadas por Oracle.

La clase personalizada debe implementar una de estas interfaces. La interfaz ORAData también puede ser usada para implementar la clase de referencia personalizada correspondiente a una clase de objeto personalizada. Si usamos la interfaz SQLData, sin embargo, sólo podemos usar tipos de referencias débiles en Java (java.sql.Ref u oracle.sql.REF). La interfaz SQLData se usa sólo para mapear objetos SQL. Como un ejemplo, asumamos que existe en la base de datos un tipo de objeto Oracle denominado Empleado.Persona, el cual consiste de dos atributos: Nombre (como un VARCHAR2) y Edad (como un NUMBER). CREATE TYPE Persona AS OBJECT (

Nombre VARCHAR2(100), Edad NUMBER(3)

);

Usaremos el mapeado de tipos para especificar que el objeto Persona debería mapear con una clases personalizada que llamaremos JPersona. Podemos implementar tanto SQLData como ORAData en la clase JPersona. 1.10.1. Ventajas de «ORAData» contra «SQLData». A la hora de decidir qué interfaz implementar, consideremos las siguientes ventajas de cada interfaz:

Ventajas de «oracle.sql.ORAData» Ventajas de «java.sql.SQLData»

• No requiere una entrada en el mapa de tipos para el objeto Oracle. • Tiene acceso a las extensiones de Oracle.

• Podemos crear un ORAData desde un STRUCT. Es más eficiente porque evita conversiones innecesarias.

• Proporciona mejor rendimiento: ORAData trabaja

directamente con tipos Datum, que es el formato interno usando por el driver que contiene los objetos Oracle.

• Es un estándar JDBC, haciendo nuestro código más portable.

La interfaz SQLData es sólo para mapear objetos SQL. Mientras que ORAData es más flexible, permitiendo mapear objetos SQL con cualquier otro tipo SQL. Podemos crear un objeto ORAData para cualquier tipo de dato encontrado dentro de la base de datos de Oracle. Esto podría ser útil para serializar datos RAW en Java. 1.10.2. El mapeado de tipos para implementaciones «SQLData». Si usamos la interfaz SQLData en una clase personalizada, entonces podemos crear entradas para el mapeado de tipos que especifiquen las clases de objetos personalizados que usaremos para mapear los tipos de objetos de Oracle. Podemos usar el mapeado de tipos por defecto para el objeto de conexión, o un mapeado de tipos que podemos especificar cuando recuperemos los datos. El método ResultSet.getObject() tiene una firma que permite especificar un mapeado de tipos: public Object getObject(int columnIndex, Map map);

Cuando usamos la implementación SQLData, si no incluimos una entrada en el mapeado de tipos, entonces el objeto será mapeado a la clase oracle.sql.STRUCT por defecto. (Las implementaciones ORAData, en contrasta, tienen su propia funcionalidad de mapeado, así que no requiere entradas de mapeado de tipos. En este caso se usa el método getORAData() en vez del método getObject().) El mapeado de tipos relaciona un tipo de Java a un nombre de tipo SQL para un objeto Oracle. Este mapeado se almacena en un HashTable como pares clave-valor. Cuando de leen datos de un objeto Oracle, el driver JDBC considera el mapeado de tipos para determinar qué clase Java debe usar para materializar los datos desde el tipo de Oracle. Cuando escribimos datos a un objeto Oracle, el driver JDBC obtiene el nombre del tipo SQL desde la clase de Java invocando el método getSQLTypeName() de la interfaz SQLData. La conversión entre SQL y Java es realizada por el driver. Los atributos de una clase Java que correspondan con objetos Oracle pueden usar tipos nativos Java o tipos nativos Oracle (instancias de las clases oracle.sql.*). Creando un mapeado de tipos para implementaciones «SQLData». Cada instancia de conexión (objetos java.sql.Connection) tiene asociado un mapa, el cual puede ser obtenido mediante el método getTypeMap(). Cuando se establece por primera vez la conexión este mapa está vacío. Podemos poblarlo usando cualquier funcionalidad de mapeado SQL-Java. Normalmente deberemos seguir los siguientes pasos para añadir entradas al mapa existente:

1) Usar el método OracleConnection.getTypeMap() para obtener el mapa de tipos: java.util.Map miMapa = conexion.getTypeMap();

Page 277: Java y Oracle 11g

Oracle /277

2) Usar el método put() para añadir entradas de mapeado. miMapa.put("EMPLEADO.PERSONA", JPersona.class);

Nota. El nombre del tipo SQL debe ser especificado en mayúsculas, porque es así como es almacenado en el diccionario de datos de la base de datos.

Podemos también crear un nuevo mapa y asignarlo al objeto de conexión mediante el método Connection.setTypeMap(). Materializando tipos de objetos no especificados en el fichero de tipos. Si no proporcionamos en el mapa de tipos una entrada apropiada cuando usamos una llamada a getObject(), entonces el driver JDBC materializará un objeto Oracle como una instancia de la clase oracle.sql.STRUCT. Si el objeto Oracle contiene objetos embebidos, y éstos no están presentes en el mapa de tipos, el driver los materializará también como instancias de oracle.sql.STRUCT. Si los objetos embebidos están presentes en el mapa de tipos, una llamada al método getAttributes() retornará los objetos embebidos como instancias de las clases Java especificadas en el mapa. 1.10.3. Cómo se utiliza la interfaz «SQLData». La interfaz SQLData define métodos que trasladan tipos entre SQL y Java para los objetos de Oracle. Una clase que implemente SQLData debe proporcionar los métodos readSQL() y writeSQL(). El driver JDBC llama a nuestro readSQL() para leer un flujo de valores de datos desde la base de datos y puebla una instancia de nuestra clase personalizada. Normalmente el driver usará este método como parte de la llamada al método OracleResultSet.getObject(). Análogamente, el driver llama al método writeSQL() para escribir una secuencia de datos desde una instancia de nuestra clase personalizada a la base de datos. Normalmente el driver usará este métodos como parte de la llamada al método setObject() de objetos OraclePreparedStatement y OracleCallableStatement. La firma de ambos métodos es la siguiente: public void readSQL(SQLInput stream, String typeName) throws SQLException

public void writeSQL(SQLOutput stream) throws SQLException

Uso de las interfaces «SQLInput» y «SQLOutput». El método readSQL() recibe como primer argumento un objeto del tipo java.sql.SQLInput, el cual representa un canal de entrada. SQLInput incluye métodos readXXX() para cada tipo de dato Java a los cuales un atributos del objeto Oracle puede ser convertido, tales como readObject(), readInt(), readLong(), readFloat(), readBlob(), y demás. Cada método readXXX() convierte el dato SQL al dato Java correspondiente. Por su parte, el método writeSQL() recibe como argumento un objeto de tipo SQLOutput, el cual representa un canal de salida. SQLOutput incluye métodos writeXXX() para cada uno de los tipos Java. Implementación de los métodos «readSQL()» y «writeSQL()». El método readSQL() recibe un argumento de tipo SQLInput y un string que indica el nombre del tipo SQL de los datos. Cuando nuestra aplicación Java invoca getObject(), el driver JDBC crea un canal de tipo SQLInput y lo puebla con datos desde la base de datos. El driver puede también determinar el nombre del tipo SQL de los datos cuando son leídos desde la base de datos. Cuando el driver llama a readSQL() le pasa estos parámetros. Para cada tipo de dato Java que mapea a un atributo del objeto Oracle, readSQL() debe llamar al apropiado método readXXX() del canal. Por su parte, el método writeSQL() recibe un canal de tipo SQLOutput. Por cada tipo de dato Java que mapea a un atributo de un objeto Oracle, writeSQL() debe llamar a los apropiados métodos writeXXX() del canal para enviar los datos de sus atributos. Por ejemplo, si estamos leyendo objetos Persona (que tienen un atributo VARCHAR2 para el nombre, y un atributo NUMBER para la edad) debemos llamar a readString() y a readInt() para poblar los campos correspondientes del objeto Java. La implementación puede ser como sigue: public class JPersona implements SQLData {

private String nombre;

private int edad;

public JPersona() throws SQLException {

}

public JPersona(String nombre, int edad) {

this.nombre = nombre;

this.edad = edad;

Page 278: Java y Oracle 11g

Oracle /278

}

@Override

public String getSQLTypeName() throws SQLException {

return "EMPLEADO.PERSONA";

}

@Override

public void readSQL(SQLInput stream, String typeName) throws SQLException {

if (typeName.equals("EMPLEADO.PERSONA")) {

nombre = stream.readString();

edad = stream.readInt();

}

}

@Override

public void writeSQL(SQLOutput stream) throws SQLException {

stream.writeString(nombre);

stream.writeInt(edad);

}

@Override

public String toString() {

return "JPersona{" + "nombre=" + nombre + ", edad=" + edad + '}';

}

}

Leyendo y escribiendo datos con la implementación «SQLData». Veamos ahora cómo leer registros de una tabla de objetos Persona creada en la base de datos: CREATE TABLE TPERSONA OF PERSONA;

INSERT INTO TPERSONA VALUES (PERSONA('JUAN', 30));

INSERT INTO TPERSONA VALUES (PERSONA('ANA', 33));

Previamente debemos crear un objeto de conexión y crear una entrada en el mapa de tipos para nuestro tipo de objeto: Connection conexion = DriverManager.getConnection( cadenaDeConexion );

conexion.getTypeMap().put("EMPLEADO.PERSONA", JPersona.class);

Ahora consultamos la tabla para obtener los registros como objetos Persona de Oracle: Statement comando = conexion.createStatement();

ResultSet rs = comando.executeQuery("SELECT VALUE(T) FROM TPERSONA T");

Por último podemos leer el primer registro y mostrar su contenido como un objeto JPersona de Java: rs.next();

JPersona persona = (JPersona) rs.getObject(1);

System.out.println(persona);

conexion.close();

Si ahora queremos insertar un nuevo objeto Persona en la base de datos podemos utilizar un PreparedStament y pasar el dato como un parámetro: conexion.getTypeMap().put("EMPLEADO.PERSONA", JPersona.class);

PreparedStatement ps = conexion.prepareStatement("INSERT INTO TPERSONA VALUES (?)");

ps.setObject(1, new JPersona("Maria", 23));

ps.execute();

conexion.close();

1.10.4. Cómo se utiliza la interfaz «ORAData». La interfaces oracle.sql.ORAData y oracle.sql.ORADataFactory hacen lo siguiente:

• El método toDatum() de ORAData transforma los datos en una representación de tipos oracle.sql.*. • El método create() de ORADataFactory equivale a un constructor de nuestra clase personalizada. Crea y retorna una instancia de ORAData. El driver JDBC usa el método create() para retornar una instancia de la clase personalizada para nuestra aplicación Java. Toma como entrada un objeto oracle.sql.Datum y un entero que indica el código de tipo SQL (pueden utilizarse las constantes de la clase oracle.jdbc.OracleTypes).

Las interfaces ORAData y ORADataFactory tienen las siguientes definiciones: public interface ORAData {

Datum toDatum (OracleConnection conn) throws SQLException;

}

Page 279: Java y Oracle 11g

Oracle /279

public interface ORADataFactory {

ORAData create (Datum d, int sql_Type_Code) throws SQLException;

}

Recuperando e insertando datos. El driver JDBC proporciona los siguientes métodos para recuperar e insertar objetos de datos como instancias de ORAData. Para recuperar objetos de datos:

• Se usa el método específico de Oracle OracleResultSet.getORAData(), que tiene la siguiente firma: ORAData getORAData (int col_index, ORADataFactory factory);

Este método toma como entrada el índice de la columna de los datos en el ResultSet, y una instancia de ORADataFactory. • O se usa el método estándar ResultSet.getObject(index, map) para recuperar datos como instancias de ORAData. En este caso debemos incluir una entrada en el mapa de tipos para identificar la clase creadora que será usada por el tipo de objeto y que se corresponde con un nombre de tipo SQL.

Para insertar objetos de datos: • Se usa el método específico de Oracle OraclePreparedStatement.setORAData(), que tiene la siguiente firma:

void setORAData (int bind_index, ORAData custom_obj);

Este método toma como entrada el índice del parámetro de la variable enlazada y el objeto que contiene la variable. • O se usa el método estándar PreparedStatement.setObject(). También se puede usar este método, en sus diferentes formas, para insertar instancias ORAData sin requerir un mapa de tipos.

Para continuar con el ejemplo del tipo Persona de Oracle, podemos crear la siguiente clase en nuestra aplicación Java: // Se define la clase personalizada implementando ambas interfaces

public class JPersona2 implements ORAData, ORADataFactory {

private CHAR nombre;

private NUMBER edad;

public JPersona2() throws SQLException {

}

private JPersona2(CHAR nombre, NUMBER edad) {

this.nombre = nombre;

this.edad = edad;

}

public JPersona2(String nombre, int edad) {

this.nombre = new CHAR(nombre.getBytes(), CharacterSet.make(CharacterSet.UTF8_CHARSET));

this.edad = new NUMBER(edad);

}

@Override

public String toString() {

return "JPersona{" + "nombre=" + nombre + ", edad=" + edad.stringValue() + '}';

}

@Override

public Datum toDatum(Connection cnctn) throws SQLException {

StructDescriptor sd = StructDescriptor.createDescriptor("PEDRO.PERSONA", cnctn);

Object[] atributos = {nombre, edad};

return new STRUCT(sd, cnctn, atributos);

}

@Override

public ORAData create(Datum datum, int i) throws SQLException {

if (datum == null) {

return null;

}

Object[] atributos = ((STRUCT) datum).getOracleAttributes();

return new JPersona2((CHAR) atributos[0], (NUMBER) atributos[1]);

}

}

Ahora podemos escribir un código para recuperar un registro Persona de la tabla TPersona:

Page 280: Java y Oracle 11g

Oracle /280

Connection conexion = DriverManager.getConnection( cadenaDeConexion );

Statement comando = conexion.createStatement();

OracleResultSet rs = (OracleResultSet) comando.executeQuery("SELECT VALUE(T) FROM TPERSONA T");

rs.next();

JPersona2 datum = (JPersona2) rs.getORAData(1, new JPersona2());

conexion.close();.

En este ejemplo se recupera el primer registro como un objeto ORAData, y para ello se pasa un objeto de tipo JPersona2, puesto que implementa ORADataFactory. El driver JDBC llamará al método create() de este objeto, de forma que retorna a nuestra aplicación Java una instancia de la clase JPersona2 poblada con los datos del ResultSet.

Nota. ORAData y ORADataFactory pueden ser definidas como interfaces independientes, de forma que diferentes clases pueden implementarlas.

También podemos usar el método estándar getObject() para recuperar los datos. Este método requiere un mapa de tipos que identifique la clase que implementa ORADataFactory y su correspondiente nombre de tipo SQL. HashMap mapa = new HashMap();

mapa.put ("EMPLEADO.PERSONA", JPersona2.class);

Connection conexion = DriverManager.getConnection( cadenaDeConexion );

Statement comando = conexion.createStatement();

OracleResultSet rs = (OracleResultSet) comando.executeQuery("SELECT VALUE(T) FROM TPERSONA T");

rs.next();

JPersona2 datum = (JPersona2) rs.getObject(1, mapa);

conexion.close();.

Para insertar un nuevo registro en la tabla TPersona usando un objeto JPersona2 podemos utilizar el siguiente código: JPersona2 p = new JPersona2("Jóse", 40);

OraclePreparedStatement ps =

(OraclePreparedStatement) conexion.prepareStatement("INSERT INTO TPERSONA VALUES (?)");

ps.setORAData(1, new JPersona2("Jóse", 40));

ps.execute();

conexion.close();

En vez de setORAData() también se puede usar el método estándar setObject(): ps.setObject(1, new JPersona2("Jóse", 40));

1.10.5. Creando objetos SQL a partir de clases de Java. En los apartados previos hemos creado una clase Java para mapear una clase SQL de Oracle creada previamente. También es posible crear una clase Java dentro de la base de datos y usarla para crear a partir de ella un tipo de objeto de Oracle. Definimos la clase JPersona dentro de la base de datos: CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED JPersona AS

import java.sql.*;

import java.io.*;

public class JPersona implements SQLData {

private String tipo_sql = "EMPLEADO.PERSONA";

private String nombre;

private int edad;

public String getNombre() {return nombre;}

public void setNombre(String nombre) {this.nombre= nombre;};

public int getEdad() {return edad;}

public void setEdad(int edad) {this.edad = edad;}

public JPersona () {}

public String getSQLTypeName() throws SQLException { return tipo_sql; }

public void readSQL(SQLInput stream, String typeName) throws SQLException {

tipo_sql = typeName;

nombre = stream.readString();

edad = stream.readInt();

}

Page 281: Java y Oracle 11g

Oracle /281

public void writeSQL(SQLOutput stream) throws SQLException {

stream.writeString(nombre);

stream.writeInt(edad);

}

// otros métodos

public String toString() {

return "(" + nombre + "," + edad + ")";

}

}

Una vez implementada la clase Java y compilada, se puede cargar en la base de datos, y a partir de este momento la clase y sus métodos están disponibles en Oracle. A continuación se define el tipo abstracto SQL de la manera explicada hasta ahora y se indica que el tipo lo implementa una clase Java y cómo se mapean los métodos de Java a Oracle. La firma del método en Oracle va seguida del comando EXTERNAL NAME con la firma del método en Java de donde se infiere el mapeado de tipos. El siguiente ejemplo modifica el tipo de objeto Persona para que mapee la clase de Java denominada JPersona: CREATE OR REPLACE TYPE Persona AS OBJECT EXTERNAL NAME 'JPersona' LANGUAGE JAVA

USING SQLData (

nombre VARCHAR2(30) EXTERNAL NAME 'nombre',

edad NUMBER(3) EXTERNAL NAME 'edad',

MEMBER FUNCTION TO_STRING RETURN VARCHAR2

EXTERNAL NAME 'toString() return java.lang.String'

);

/

2. Documentos XML en Oracle

XML es el formato de codificación/estructuración para el intercambio de datos más común hoy en día. Sin embargo el modelado de este tipo de datos en bases de datos relacionales era complicado por el mismo motivo que el modelado de objetos: los documentos XML no tienen una estructura plana, con lo que su representación en tablas se hace complicada. La opción hasta hace poco era el almacenar los documentos en objetos grandes de tipo CLOB o NCLOB y analizar/transformar estos elementos tomando este flujo de caracteres y haciéndolos pasar por un parseador XML habitual. No se aprovechaba ninguna de las características de la base de datos para mejorar la eficiencia de búsquedas y consultas, salvo por alguna característica de indexado avanzada de texto. Con la llegada del modelo objeto-relacional el problema de la planaridad se resuelve y las mismas facilidades que se daban para el modelado de objetos se ofrecen ahora para el modelado de los objetos XML en la estructura de la base de datos. Esta integración permite hacer uso de la eficiencia de las consultas y flexibilidad de recorrido de estructuras del modelo relacional en estos documentos.

2.1. «XMLType».

Este mapeado de la estructura XML a la estructura de la base de datos en Oracle se realiza con el tipo XMLType, que es un tipo abstracto. El tipo XMLType se almacena en un tipo CLOB y puede asociarse a un Esquema XML para la definición de su estructura, lo que obliga a que cualquier documento sea validado con este esquema. En este caso, el esquema del documento se modela en la estructura objeto-relacional de la base de datos. La ventaja de no validar contra un esquema es que todo tipo de documentos XML pueden almacenarse en ese elemento XMLType. En caso contrario se obliga a que el elemento sea válido frente al esquema asociado, aunque su mapeado en la estructura objeto-relacional permite tratar el documento de manera más eficiente y flexible.

2.2. Mapeado de «XMLType» dado un esquema XML.

Los elementos del esquema XML se mapean como objetos en los que cada elemento anidado de tipo simple es representado por un atributo de un tipo nativo lo más acorde posible con el tipo del esquema: si es un número con NUMBER, si es texto con VARCHAR2,… Aún así, es posible forzar la representación del elemento a un tipo de Oracle mediante el atributo SQLType utilizado en el elemento del esquema. Cuando un elemento contiene un elemento complejo, éste es modelado con un objeto y el elemento padre establece una referencia a él con tipos referencia. Es posible forzar que el mapeado de los tipos complejos se realice en CLOB, NCLOB o VARCHAR2 (sin ser representados en el modelo

Page 282: Java y Oracle 11g

Oracle /282

objeto-relacional) mediante el atributo SQLType (=CLOB) utilizado en el elemento del esquema. Cuando la ocurrencia de un elemento, bien simple o complejo, es mayor que uno el elemento es representado en el objeto padre con un array variable (si el número de ocurrencias máximas es finito) o con un tabla anidada (si es infinito). El mapeado se entiende mucho mejor con el siguiente ejemplo: DECLARE

doc varchar2(3000) :=

'<schema xmlns = "http://www.w3.org/2001/XMLSchema"

targetNamespace="http://www.oracle.com/emp.xsd"

xmlns:emp="http://www.oracle.com/emp.xsd"

xmlns:xdb="http://xmlns.oracle.com/xdb">

<element name = "Empleado">

<complexType>

<sequence>

<element name = "Nombre" type = "string"/>

<element name = "Edad" type = "decimal"/>

<element name = "Direccion" maxOccurs="unbounded">

<complexType>

<sequence>

<element name = "Calle" type = "string"/>

<element name = "Ciudad" type = "string"/>

</sequence>

</complexType>

</element>

</sequence>

</complexType>

</element>

</schema>';

Se mapearía de la siguiente manera: CREATE TYPE Direccion_t AS OBJECT (

calle VARCHAR2(4000),

ciudad VARCHAR2(4000) );

CREATE TYPE Direccion_t_table AS TABLE OF Direccion_t;

CREATE TYPE Empleado AS OBJECT (

Nombre VARCHAR2(4000),

Edad NUMBER,

Direccion_table Direccion_t_table );

2.3. Crear tablas/columnas «XMLType».

Para crear columnas o tablas XMLType sólo tenemos que hacerlo como lo hicimos al definir columnas o tablas de objetos. Si no validamos el tipo XMLType con un esquema, los ejemplos serían los siguientes (el primero es una tabla con una columna tipo XMLType y el segundo es una tabla de XMLType): CREATE TABLE TREmpleado (

id NUMBER(3),

xmlEmpleado XMLTYPE

);

/

CREATE TABLE TXEmpleado OF XMLTYPE;

En este segundo caso, realmente se crea una tabla con un campo de tipo XMLTYPE denominado SYS_NC_ROWINFO$. Para definir una columna o tabla XMLType asociada a un esquema debemos registrar primero el esquema en la base de datos. Esto se realiza mediante la biblioteca DBMS_XMLSCHEMA que posee dos funciones: REGISTERSCHEMA para registrar el esquema y DELETESCHEMA para eliminar el registro. DBMS_XMLSCHEMA.REGISTERSCHEMA ('http://www.oracle.com/empleado.xsd', doc);

DBMS_XMLSCHEMA.DELETESCHEMA ('http://www.oracle.com/empleado.xsd',

DBMS_XMLSCHEMA.DELETE_CASCADE_FORCE);

Una vez registrado el esquema podemos crear columnas y tablas XMLType asociadas haciendo uso del comando XMLSCHEMA en la definición:

Page 283: Java y Oracle 11g

Oracle /283

-- Para la siguiente definición de tabla

CREATE TABLE TREmpleado (

id NUMBER(3),

xmlEmpleado XMLTYPE

)

XMLTYPE COLUMN xmlEmpleado XMLSCHEMA "http://www.oracle.com/empleado.xsd"ELEMENT "Empleado";

-- Se asocia el campo "xmlEmpleado" sobre el elemento "Empleado" del esquema

O bien se crea una tabla de tipo XMLType: CREATE TABLE TXEmpleado OF XMLTYPEXMLSCHEMA "http://www.oracle.com/empleado.xsd"

ELEMENT "Empleado";

-- O bien se utiliza la sintaxis:

CREATE TABLE TXEmpleado OF XMLTYPEELEMENT "http://www.oracle.com/empleado.xsd#Empleado";

2.4. Operaciones con columnas XMLType.

2.4.1. Insertar y actualizar registros con datos XML. Si tratamos a los tipos XMLType como objetos podemos utilizar el constructor de dichos objetos para instanciar nuevos elementos XMLType, tomando como parámetro la cadena que representa al documento XML: INSERT INTO TREmpleado (id, xmlEmpleado) VALUES(

1,

XMLType('<Empleado><Nombre>Juan</Nombre><Edad>34</Edad><Direccion /></Empleado>')

);

Si queremos actualizar el campo debemos proporcionar un nuevo documento XML entero: UPDATE TREmpleado

SET xmlEmpleado = XMLType('<Empleado><Nombre>Juan</Nombre><Edad>34</Edad><Direccion /></Empleado>')

WHERE id = 1;

También podemos hacer uso de los comandos SQLX para generar el documento XML en vez del constructor de XMLType, tal como veremos en un apartado posterior. 2.4.2. Consultar registros con datos XML. Es posible recuperar un documento XML de un registro en forma de CLOB, VARCHAR2 o NUMBER mediante los métodos de XMLType: getClobVal(), getStringVal(), y getNumberVal(). Con estas funciones simplemente obtenemos el documento XML convertido en un tipo nativo de Oracle. El siguiente ejemplo obtiene el contenido del campo xmlEmpleado como un valor de tipo CLOB: SELECT e.xmlEmpleado.getClobval() AS "EmpleadoXML"

FROM TREmpleado e

WHERE e.id = 1;

También es posible recuperar partes del documento y efectuar predicados de selección en partes del documento. Estas partes se basan en la estructura DOM de XML y se señalan haciendo uso de expresiones XPath. Las funciones incluidas con este propósito son extract, extractValue y existsNode: las dos primeras devuelven el nodo del documento XML (de la estructura DOM) solicitado y la tercera devuelve verdadero (1) cuando existe el nodo solicitado o falso (0) si no existe. En el siguiente ejemplo se hace una consulta a la tabla relacional TREmpleado para obtener el nodo Empleado de aquellos empleados cuyo nombre sea "Juan": SELECT e.xmlEmpleado.getStringVal() AS EmpleadoXML

FROM TREmpleado e

WHERE e.xmlEmpleado.existsNode('/Empleado [Nombre = "Juan"]') = 1;

Siendo el resultado (se muestra un registro): EmpleadoXML

--------------------------------

<Empleado><Nombre>Juan</Nombre><Edad>34</Edad><Direccion /></Empleado>

En el siguiente ejemplo se hace una consulta para obtener el id y el nodo Direccion de aquellos empleados en la tabla relacional TREmpleado que tengan asignado el campo xmlEmpleado, y lo convertimos en una cadena (si no sería un XMLType): SELECT e.id ,EXTRACT(e.xmlEmpleado, '/Empleado/Direccion').getStringVal()AS "Dirección"

FROM TREmpleado e

WHERE e.xmlEmpleado IS NOT NULL;

Un resultado posible podría ser (se muestran dos registros):

Page 284: Java y Oracle 11g

Oracle /284

id Dirección

---- -------------------------------------------------------------------------------------

1 <Direccion />

2 <Direccion><Calle>La Ronda></Calle><Ciudad>La Coruña</Ciudad></Direccion>

La función extract() siempre devuelve el nodo en formato XMLType; si queremos recuperar el valor de un nodo final podemos utilizar getNumberVal() o getStringVal() sobre el elemento XMLType devuelto. O bien podemos utilizar extractValue(), que tiene una sintaxis idéntica a extract() pero que devuelve el valor del nodo de texto y no el elemento XMLType. Estas funciones sólo son válidas para nodos que tengan un solo y único nodo de texto. Por ejemplo, si queremos recuperar el nombre del empleado de id 1 podemos usar una de las dos consultas siguientes: SELECT EXTRACT(e.xmlEmpleado, '/Empleado/Nombre/text()').getStringVal() AS "Nombre"

FROM TREmpleado e

WHERE e.id = 1;

/

SELECT EXTRACTVALUE(e.xmlEmpleado, '/Empleado/Nombre').getStringVal() AS "Nombre"

FROM TREmpleado e

WHERE e.id = 1;

2.4.3. Borrar registros con datos XML. El borrado de registros que contienen columnas de tipo XMLType es similar a borrar registros con cualquier otro tipo de datos. Por ejemplo, para eliminar los registros con la ciudad Madrid, ejecutaremos el siguiente comando: DELETE FROM TREmpleado

WHERE e.xmlEmpleado.extractValue('/Empleado/Direccion/Ciudad')='Madrid';

En esta consulta se supone que cada empleado posee una única dirección. En caso contrario la función extractValue() produciría un error.

2.4.4. Actualizar datos XML. La función updateXML() permite actualizar el contenido de un objeto de tipo XMLType retornando un nuevo XMLType. Tiene tres parámetros:

- El objeto XMLType que queremos modificar. - Una expresión XPath que estable el o los elementos que queremos modificar. - Una expresión que será utilizada para hacer la sustitución.

Gracias a esta función podemos evitar modificar todo el documento XML cuando sólo varíe una parte. Por ejemplo, el siguiente comando modifica la edad de todos los empleados de la tabla TREmpleado al valor 33: UPDATE TREmpleado

SET xmlEmpleado = UPDATEXML(xmlEmpleado,'/Empleado/Edad/text()', '33');

Es importante notar que, efectivamente, no se modifica un único nodo Edad, sino que el valor de cada nodo Edad de empleado es modificado al valor 33. Si actualizamos un elemento XML a valor null, Oracle elimina los atributos y elementos hijos del mismo, y entonces el elemento queda vacío. Pero el elemento no desaparece como tal, y retiene sus propiedades de tipo y espacio de nombres. Un valor null para un elemento actualizado es equivalente a asignar un elemento vacío. Si actualizamos un nodo de texto de un elemento a null, Oracle quita el valor quedando el elemento vacío. Asignar un atributo a null es similar a poner el valor del atributo a un string vacío. Si aplicamos la función: updateXML(xmlEmpleado,'/Empleado/Direccion',null)

Obtenemos:

Valor original en el campo xmlEmpleado Nuevo valor tras la actualización

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

</Empleado>

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direccion />

</Empleado>

Page 285: Java y Oracle 11g

Oracle /285

Por tanto no se puede utilizar updateXML() de forma directa para añadir o eliminar elementos o atributos del documento. Para ello se debe actualizar el contenido del documento a un nuevo valor. Supongamos, por ejemplo, que queremos añadir una nueva dirección al empleado de código 1. Podemos obtener la lista de nodos de dirección mediante la función extract() y concatenarle la nueva dirección. A continuación se puede utilizar la función UpdateXml() para cambiar el contenido antiguo por el nuevo. El siguiente comando añade un nuevo nodo de dirección al empleado de id 1: UPDATE TREmpleado

SET xmlEmpleado = UPDATEXML(xmlEmpleado,

'/Empleado/Direccion',

EXTRACT(xmlEmpleado, '/Empleado/Direccion') || '<Direccion />' )

WHERE id = 1;

Y el resultado es:

Valor original en el campo xmlEmpleado Nuevo valor tras la actualización

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

</Empleado>

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

<Direccion />

</Empleado>

Pero aunque esto funciona, ¡CUIDADO! Si volviésemos a ejecutar nuevamente este comando obtendríamos un resultado no esperado donde se duplicarían las direcciones existentes más la nueva. Esto es así porque la primera vez sólo existía un nodo Dirección y por tanto era sustituido por sí mismo más la nueva dirección concatenada. La segunda vez ya existen dos nodos Direccion, y la sustitución se realiza por cada nodo que retorna la expresión Xpath '/Empleado/Direccion'. Este problema se resolvería si encapsulamos los nodos Direccion en un único nodo Direcciones, tal como se muestra a continuación: UPDATE TREmpleado

SET xmlEmpleado = UPDATEXML(xmlEmpleado,

'/Empleado/Direcciones', '<Direcciones>' ||

EXTRACT(xmlEmpleado, '/Empleado/Direcciones/Direccion') || '<Direccion />' ||

' </Direcciones>' )

WHERE id = 1;

Y el resultado es:

Valor original en el campo xmlEmpleado Nuevo valor tras la actualización

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direcciones>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

<Direcciones>

</Empleado>

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direcciones>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

<Direccion />

</Direcciones>

</Empleado>

Si queremos eliminar una dirección, podemos obtener la lista de direcciones mediante extract() aplicando una selección sólo sobre las que queremos conservar. Por ejemplo, si queremos eliminar la última dirección podemos realizar la siguiente actualización: UPDATE TREmpleado

SET xmlEmpleado = UPDATEXML(xmlEmpleado,

'/Empleado/Direccion','<Direcciones>' ||

EXTRACT(xmlEmpleado, '/Empleado/Direcciones/Direccion [not (position() = last())]') ) ||

'</Direcciones>'

WHERE id = 1;

Page 286: Java y Oracle 11g

Oracle /286

Y el resultado es:

Valor original en el campo xmlEmpleado Nuevo valor tras la actualización

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direcciones>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

<Direccion>

<Calle>444</Calle>

<Ciudad>Boston</Ciudad>

</Direccion>

</Direcciones>

</Empleado>

<Empleado>

<Nombre>Juan</Nombre>

<Edad>33</Edad>

<Direcciones>

<Direccion>

<Calle>333</Calle>

<Ciudad>Nueva York</Ciudad>

</Direccion>

</Direcciones>

</Empleado>

2.4.5. Aplicar hojas de estilo XSTL. Otra forma de modificar un documento XML es utilizando una hoja de estilo XSLT y la función XMLTransform(). El comando XMLTransform() toma como parámetros dos instancias de XMLtype, siendo la primera el documento origen y la segunda un documento XSLT de transformaciones XML, y devuelve el documento resultante de la transformación. Un sinónimo de este comando es el método Transform() de la clase XMLType. Como ejemplo, supongamos la siguiente tabla: CREATE TABLE testxml (xml XMLType);

Insertamos un registro: INSERT INTO testxml VALUES (XMLType('<a><b>1</b><b>2</b></a>'));

Tras la inserción, el contenido de la tabla es el siguiente: XML

-----------------

<a><b>1</b><b>2</b></a>

Ahora aplicamos una transformación, de forma que se eliminan las etiquetas <b> y sus valores se concatenan como valor de la etiqueta <a>. SELECT XMLTransform(x.xml, XMLType(

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="a">

<a><xsl:apply-templates/></a>

</xsl:template>

</xsl:stylesheet>'

)) AS xml FROM testxml x;

El resultado es: XML

-----------------

<a>12</a>

Otra forma de aplicar la transformación es con la función Transform(): SELECT x.xml.transform(XMLType( . . . )) AS xml FROM testxml x;

Las funciones Transform() retornan un valor XMLType; esto quiere decir que el resultado de la transformación debe ser un documento o parte de un documento XML bien formado.

2.5. Validar los documentos XML sobre un esquema.

Es posible validar documentos XML frente a esquemas XML mediante el comando XMLisValid() y el método de XMLType, isSchemaValidated(). Ambos devuelven verdadero (1) si el documento se valida correctamente. El siguiente ejemplo valida cada documento de la columna xmlEmpleado de la tabla TREmpleado con el esquema dado devolviendo 1 (verdadero) cuando la validación es correcta y 0 (falso) cuando no: SELECT e.xmlEmpleado.isSchemaValid('http://www.oracle.com/empleado.xsd','Empleado')

FROM TREmpleadoe;

Page 287: Java y Oracle 11g

Oracle /287

2.6. Indexar elementos «XMLType».

Para acelerar las consultas en los tipos XMLType podemos utilizar índices basados en función, para acelerar las funciones extract() o existsNode() en rutas XPath definidas, como por ejemplo: CREATE INDEX ciudad_index ON TREmpleado(xmlEmpleado.extract('//Ciudad/text()').getStringVal());

CREATE BITMAP INDEX direccion_index ON TREmpleado(xmlEmpleado.existsNode('//Direccion'));

Para los documentos XMLType mapeados, el índice a utilizar para acelerar las consultas con parámetros o rutas XPath es CTXSYS.CTXXPATH: CREATE INDEX xml_idx ON TREmpleado(xmlEmpleado) INDEXTYPE IS CTXSYS.CTXXPATH;

Sin embargo, si quisiéramos más flexibilidad y funcionalidad de un índice para los documentos XML sin esquema deberíamos utilizar "Oracle Text", que se utiliza en columnas tipo CLOB o VARCHAR y que en la última versión de Oracle se ha extendido para utilizar XMLType. Para crear un índice de "Oracle Text" en la columna xmlEmpleado de la tabla TREmpleado: CREATE INDEX emp_text_index ON TREmpleado(xmlEmpleado) INDEXTYPE IS CTXSYS.CONTEXT;

El tipo de índice utilizado es CTXSYS.CONTEXT, que es apropiado para predicados y consultas de "Oracle Text" como CONTAINS, SCORE, INPATH, HASPATH. Si queremos acelerarlas consultas con las funciones extract() y existsNode() debemos utilizar el índice CTXSYS.CTXXPATH. Cuando trabajemos con las opciones de secciones de "Oracle Text" debemos definir cómo se crean estas secciones. Las secciones son partes del documento a las que referenciaren las búsquedas, como son por ejemplo los nodos de cada documento XML. Las secciones permiten restringir las búsquedas a ciertas partes del documento. El predicado de búsqueda de "Oracle Text" es CONTAINS(), que toma 2 parámetros: el primero es la columna donde efectuar la búsqueda y el segundo es el predicado a cumplir por el documento. Por cada fila devuelta CONTAINS() devuelve el porcentaje entre 0 y 100 de relevancia del documento. SELECT id FROM mi_tabla

WHERE CONTAINS (mi_columna, 'ingresos') > 0

Si el documento esta dividido por secciones (en el modo de secciones de "Oracle Text" AUTO_SECTION_GROUP, las secciones se crean automáticamente con las etiquetas [y se las referencia: etiqueta] y atributos XML [y se las referencia: etiqueta@atributo]) nos es posible restringir la búsqueda a una sección en particular. En el siguiente ejemplo se busca ingresos en el atributo titulo de la etiqueta informe: SELECT id FROM mi_tabla

WHERE CONTAINS (mi_columna,'ingresos WITHIN informe@titulo') > 0

El operador INPATH es muy parecido a WITHIN pero ha sido incluido para dar soporte a documentos XML. Su sintaxis es igual pero la ruta de sección ya no es del tipo etiqueta o etiqueta@atributo, si no que se utiliza XPath para referirse a cada nodo del documento. El operador HASPATH busca qué documentos tienen la ruta especificada en XPath como parámetro. SELECT id FROM catalogo

WHERE CONTAINS(text,'caja INPATH(pedido/item)') > 0;

SELECT id FROM catalogo

WHERE CONTAINS(text,'HASPATH(pedido/item)') > 0;

2.7. SQLX, generar XML de los datos relacionales.

Al igual que en el modelado objeto-relacional, en el que no es necesario convertir los datos del modelo plano relacional al modelo objeto-relacional para trabajar con ellos en este último modelo, es posible mediante comandos y vistas representar datos del modelo relacional u objeto-relacional como documentos XML sin necesidad de modificarlos. Oracle soporta cinco comandos del estándar SQLX (SQL para XML, SQL/XML) para la representación de datos relacionales con XML: XMLElement(), XMLForest(), XMLConcat(), XMLAttributes() y XMLAgg(). También soporta XMLColAttVal() como comando SQLX propio, pero aún no es aceptado en el estándar. Estos comandos permiten representar datos como un documento XML definiendo nosotros la estructura de ese documento. Oracle, además, soporta las funciones SYS_XMLGEN.SYS_XMLAGG(), XMLSEQUENCE() y XMLFormat con el mismo propósito que las anteriores pero sin ser parte del estándar SQLX o de su propuesta. Nos es posible también crear vistas del tipo XMLType para representar tablas y vistas relacionales como documentos XML de forma transparente para la consulta, como si de una consulta a un XMLType se tratase. 2.7.1. Funciones SQLX. XMLElement() es una función que devuelve un tipo XMLType dados como parámetros el nombre del elemento

Page 288: Java y Oracle 11g

Oracle /288

XML, una serie de atributos y el contenido del nodo. El XMLType devuelto es un nodo con el nombre del primer parámetro, los atributos del segundo y el contenido de los últimos parámetros. El contenido puede ser un valor o un nuevo elemento XMLType para poder formar la estructura anidada de los documentos XML. Los atributos se definen mediante la función XMLAttributes() que toman como método el listado de atributos a asignar al elemento XML. Si no se especifica la cláusula AS en cada atributo se toma como nombre de atributo el inferido de la estructura relacional, y si se utiliza AS se toma el indicado. Supongamos la siguiente tabla relacional: CREATE TABLE TEmpleado (

id INTEGER PRIMARY KEY,

fecha DATE,

fnombre VARCHAR2(100),

lnombre VARCHAR2(100),

dept VARCHAR2(20),

jubila DATE );

La instrucción siguiente muestra el contenido de esta tabla en formato XML: SELECT XMLELEMENT("Empleado",

XMLATTRIBUTES (e.id,e.fecha AS "fechaNacimiento"),

XMLELEMENT("nombre", e.fnombre ||''|| e.lnombre),

XMLELEMENT("jubilacion", e.jubila)) AS "result"

FROM TEmpleado e

WHERE id > 200 ;

Donde el resultado puede ser algo como esto (se muestra un único registro): result

---------------------------------------------------------------

<Empleado id="200" fechaNacimiento="13-07-1951">

<nombre>Juan Martín</nombre>

<jubilacion>24-05-2000</jubilacion>

</Empleado>

La función XMLForest() crea un árbol XML de los parámetros que toma. Un árbol XML son nodos situados a la misma altura, es decir, nodos que partirían del mismo nodo raíz, salvo que no definimos este nodo raíz. Cuando el parámetro se acompaña de la cláusula AS se utiliza éste como nombre del elemento XML, cuando no, se infiere de la estructura de los datos. El siguiente es un ejemplo de XMLElement() para crear el nodo raíz y de XMLForest() para crear los elementos de este nodo: SELECT XMLELEMENT("Empleado",

XMLATTRIBUTES ( e.fnombre ||''|| e.lnombre AS "nombre" ),

XMLForest ( e.jubila, e.dept AS "departamento")) AS "result"

FROM TEmpleado e;

Donde el resultado puede ser algo como esto (se muestran dos registros): result

--------------------------------------------------

<Empleado nombre="Juan Gómez"><jubila>24-05-2000</jubila><departamento>Finanzas</departamento></Empleado>

<Empleado nombre="María Martin"><jubila>01-02-1996</jubila><departamento>Ventas</departamento></Empleado>

La función XMLConcat(), dados como parámetros una secuencia de elementos XMLType o datos tipo XMLType, los concatena uno tras otro en el orden en que aparecen como parámetros. Mientras que en XMLForest() los parámetros son datos relacionales, en XMLConcat() son tipos XMLType: SELECT XMLCONCAT(XMLELEMENT("primero", e.fnombre),XMLElement("ultimo", e.lnombre))AS "result"

FROM TEmpleado e ;

Donde el resultado puede ser algo como esto (se muestran dos registros): result

------------------------------------------

<primero>María</primero><ultimo>Martin</ultimo>

<primero>Juan</primero><ultimo>Gómez</ultimo>

La función XMLAgg() es una función de agregado que produce un bosque de elementos XML dada una colección de elementos. Se usa normalmente en consultas con cláusulas de agrupación como GROUP BY, como se puede ver en el siguiente ejemplo: SELECT XMLELEMENT("Departamento",

XMLATTRIBUTES ( e.dept AS "nombre" ),

Page 289: Java y Oracle 11g

Oracle /289

XMLAGG (XMLELEMENT("emp", e.lnombre))) AS "result"

FROM TEmpleado e

GROUP BY dept;

Donde el resultado puede ser algo como esto (se muestran dos registros): result

----------------------------------------------

<Departamento nombre="Finanzas"><emp>López</emp><emp>García</emp></Departamento>

<Departamento nombre="Ventas"><emp>Gutierrez</emp><emp>Martin</emp></Departamento>

En este ejemplo, la función XMLAgg() genera elementos <emp> por cada registro dentro un grupo y los concatena como un único XMLType. La función XMLColAttVal() crea un árbol de XML donde cada elemento es de tipo column y posee un atributo tipo name con el nombre del elemento, especificado por AS en los parámetros o inferido de los datos. El siguiente es un ejemplo de uso de XMLColAttVal(): SELECT XMLELEMENT("Emp",XMLATTRIBUTES(e.fnombre ||''||e.lnombre AS "nombre" ),

XMLCOLATTVAL ( e.jubila, e.dept AS "departamento")) AS "result"

FROM TEmpleado e;

Donde el resultado puede ser algo como esto (se muestran tres registros): result

-------------------------------------------------------------

<Emp nombre="Juan Gómez">

<column nombre="Jubilacion">25-05-2000</column>

<column nombre="departamento">Finanzas</column>

</Emp>

<Emp nombre="María López">

<column nombre="Jubilacion">01-02-1996</column>

<column nombre="departamento">Ventas</column>

</Emp>

<Emp nombre="Samanta Rojas">

<column nombre="Jubilacion">15-11-1992</column>

<column nombre="departamento">Personal</column>

</Emp>

2.7.2. «SYS_Xmlgen», «Sys_XmlAgg», «XMLSequence» y «XMLFormat». La función SYS_XMLAGG() permite englobar todos los resultados (con formato XMLType) de una consulta en un único valor de tipo XMLType. La etiqueta raíz del resultado englobado será por defecto ROWSET, pero puede ser cambiada con la función XMLFormat(). Como ejemplo, supongamos la siguiente tabla donde se insertan documentos XML con la información de ordenadores: CREATE TABLE TXOrdenador OF XMLTYPE;

/

INSERT INTO TXOrdenador VALUES(XMLType('<pc><tipo>sobremesa</tipo><marca>Fujitsu</marca></pc>'));

/

INSERT INTO TXOrdenador VALUES(XMLType('<pc><tipo>portátil</tipo><marca>ACER</marca></pc>'));

/

INSERT INTO TXOrdenador VALUES(XMLType('<pc><tipo>sobremesa</tipo><marca>IBM/marca></pc>'));

La siguiente consulta obtiene de cada registro de la tabla TXOrdenador el nodo <marca /> y devuelve un único resultado con los nodos concatenados dentro de una etiqueta <marcas />: SELECT SYS_XMLAGG(EXTRACT(VALUE(T),'/pc/marca'), XMLFORMAT('marcas'))AS "Marcas"

FROM TXOrdenador T;

El resultado sería el siguiente (se muestra un único registro): Marcas

----------------------------------------------------------------------------------------------

<marcas><marca>Fujitsu</marca><marca>ACER</marca><marca>IBM/marca></marcas>

Por su parte, la función XMLSEQUENCE() devuelve una colección (un array variable) de objetos XMLType a partir de un XMLType. Es decir, toma los nodos hijos directos del XMLType y devuelve un nodo XMLType por cada uno de ellos en un objeto SYS.XMLSequenceType. Supongamos la siguiente tabla:

Page 290: Java y Oracle 11g

Oracle /290

CREATE TABLE TXPlantilla OF XMLTYPE;

La cual contendrá en cada registro la lista de empleados de un departamento. El contenido de un registro se especifica en el siguiente comando de inserción: INSERT INTO TXPlantilla VALUES (

'<empleados departamento="Finanzas">

<emp>

<empno>112</empno>

<nombre>Joe</nombre>

<salario>50000</salario>

</emp>

<emp>

<empno>217</empno>

<nombre>Jane</nombre>

<salario>60000</salario>

</emp>

<emp>

<empno>412</empno>

<nombre>Jack</nombre>

<salario>40000</salario>

</emp>

</empleados>' );

El siguiente código PL muestra como obtener los nodos emp del departamento de Finanzas como un array de valores XMLType: DECLARE

vEmp XMLSequenceType;

BEGIN

-- Realizo una consulta que obtiene la colección de nodos <emp /> y la asigna en vEmp

SELECT XMLSEQUENCE(EXTRACT(VALUE(P), '/empleados/emp')) INTO vEmp

FROM TXPlantilla P

WHERE EXTRACTVALUE(VALUE(P),'/empleados/@departamento')='Finanzas';

-- Muestro el contenido del varray vEmp

FOR I IN vEmp.First()..vEmp.Last() LOOP

DBMS_OUTPUT.PUT_LINE( vEmp(I).getStringVal() );

END LOOP;

END;

El tipo SYS.XMLSequenceType es un tipo de VArray predefinido en Oracle cuyos elementos son de tipo XMLType. Si ahora quisiéramos realizar una consulta que muestre los datos de cada empleado del departamento de Finanzas como si se tratase de una tabla relacional, podemos usar la función TABLE() para convertir la colección que devuelve XmlSequence en un origen de datos válido para la cláusula FROM: SELECT EXTRACTVALUE(VALUE(T), '/emp/empno') AS "Número de empleado" ,

EXTRACTVALUE(VALUE(T), '/emp/nombre') AS "Nombre de empleado" ,

EXTRACTVALUE(VALUE(T), '/emp/salario') AS "Salario de empleado" ,

FROM TABLE(

(SELECT XMLSEQUENCE(EXTRACT(VALUE(P), '/empleados/emp'))FROM TXPlantilla P

WHERE EXTRACTVALUE(VALUE(P),'/empleados/@departamento')='Finanzas' )) T;

El resultado sería: Número de empleado Nombre de empleado Salario de empleado

----------------------- ----------------------- ----------------------

112 Joe 50000

217 Jane 60000

412 Jack 40000

Por su parte, la función SYS_XMLGEN() toma un tipo nativo, un tipo abstracto o un tipo XMLType y genera con él un documento XML. Si es un tipo nativo, forma una etiqueta con el valor dentro, si es un tipo abstracto mapea los atributos del tipo abstracto a un documento XML y si es un XMLType engloba a este elemento en otro elemento de nombre por defecto ROW. Es posible indicar el nombre de la etiqueta principal del documento XML generado mediante la función XMLFormat().

Page 291: Java y Oracle 11g

Oracle /291

Supongamos el tipo de objeto CLIENTE_T, y una tabla asociada TRCliente donde se insertan varios registros: CREATE TYPE CLIENTE_T AS OBJECT (

cod INT, nombre VARCHAR2(150)

);

/

CREATE TABLE TRCliente OF CLIENTE_T (cod PRIMARY KEY);

/

INSERT INTO TRCliente VALUES(1, 'Roberto Pérez');

/

INSERT INTO TRCliente VALUES(2, 'Marisa Rubén');

/

INSERT INTO TRCliente VALUES(3, 'Adrián Martínez');

La siguiente consulta recupera todos los nombres de cliente como un documento XML: SELECT SYS_XMLGEN( nombre ) AS "Cliente" FROM TRCliente;

El resultado sería el siguiente (se muestran tres registros): Cliente

----------------------------------------------

<NOMBRE>Roberto Pérez</NOMBRE>

<NOMBRE>Marisa Rubén</NOMBRE>

<NOMBRE>Adrián Martinez</NOMBRE>

Si ahora aplicamos la función sobre los registros como objetos: SELECT SYS_XMLGEN( VALUE(T),XMLFORMAT('CLIENTE')) AS "Cliente" FROM TRCliente T;

Se obtiene (se muestran tres registros): Cliente

----------------------------------------------

<CLIENTE><COD>1</COD><NOMBRE>Roberto Pérez</NOMBRE></CLIENTE>

<CLIENTE><COD>2</COD><NOMBRE>Marisa Rubén</NOMBRE></CLIENTE>

<CLIENTE><COD>3</COD><NOMBRE>Adrián Martinez</NOMBRE></CLIENTE>

La función XMLFormat()puede ser un parámetro de SYS_XMLGEN() y SYS_XMLAGG(). Esta función define las características del documento generado por estas dos funciones mediante sus atributos. Si queremos cambiar el formato del documento XML generado tan solo tendremos que darle el valor adecuado al correspondiente parámetro de XMLFormat(). Los parámetros más importantes son:

• enclTag. Es el nombre de la etiqueta que engloba el documento. • vschemaType. Indica si el documento está validado por un esquema o no, sus valores válidos son NO_SCHEMA y USE_GIVEN_SCHEMA. • schemaName. Nombre del esquema. • targetNameSpace. Namespace del documento. • dburl. URL donde encontrar la definición de los esquemas, si no se declara se consideran relativos al documento.

2.8. Vistas «XMLType».

Las vistas XMLType nos permiten tomar elementos relacionales u objeto-relacionales de la base de datos y sin modificar ni los datos ni su estructura poder mostrarlos como si documentos XML fuesen. En la forma habitual, se crea una vista indicando que es de tipo XMLType (OF XMLTYPE): -- Tabla relacional:

CREATE TABLE TInquilino (

idNUMBER(4),

fnombreVARCHAR2(20),

lnombreVARCHAR2(20),

alquiler DATE,

precio NUMBER(6));

/

-- Vista de XMLType:

CREATE OR REPLACE VIEW VInquilino OF XMLTYPE

WITH OBJECT ID (EXTRACT(sys_nc_rowinfo$,'/inquilino/@id').getNumberVal())AS

SELECT XMLELEMENT(

"inquilino",

Page 292: Java y Oracle 11g

Oracle /292

XMLAttributes(id),

XMLForest(T.fnombre ||''|| T.lnombre AS "nombre", T.alquiler AS "fechaAlquilier")) AS "result"

FROM TInquilino T

WHERE precio> 20000;

La cláusula OBJECT ID indica que id será el identificador único de cada elemento y que el tipo XMLType se almacenará en la columna sys_nc_rowinfo$. Y creamos la vista mediante las funciones SQLX y de Oracle vistas en el apartado anterior. Si ahora insertamos datos en la tabla relacional y los consultamos mediante la vista: INSERT INTO TInquilino VALUES (2100, 'John', 'Smith', '24-05-2000', 30000);

/

INSERT INTO TInquilino VALUES (2200, 'Mary', 'Martin', '01-02-1996', 30000);

/

SELECT * FROM VInqulino;

Obtendremos: SYS_NC_ROWINFO$

------------------------------------------------------------

<inquilino id="2100">

<nombre>John Smith</nombre>

<fechaAlquiler>24-05-2000</fechaAlquiler>

</inquilino>

<inquilino id="2200">

<nombre>Mary Martin</nombre>

<fechaAlquiler>01-02-1996</fechaAlquiler>

</inquilino>

También es posible crear vistas XMLType mapeando los datos relacionales mediante un esquema y no con el comando SELECT de la definición de la vista. El esquema define el mapeado de cada elemento a la columna de datos mediante el atributo xdb:SQLName en el elemento del esquema, de tal manera que el elemento contendrá el valor de la columna indicada en ese atributo. Usaremos los siguientes tipos de objetos: CREATE OR REPLACE TYPE Departamento_t AS OBJECT (

deptno NUMBER(2),

nombre VARCHAR2(14),

lugar VARCHAR2(13));

/

CREATE OR REPLACE TYPE Empleado_t AS OBJECT (

empno NUMBER(4),

nombre VARCHAR2(10),

trabajo VARCHAR2(9),

salario NUMBER(7,2),

departamento Departamento_t);

Y las siguientes tablas relacionales: CREATE TABLE TRDepartamento (

deptno NUMBER(2) PRIMARY KEY,

nombre VARCHAR2(14),

lugar VARCHAR2(13));

/

CREATE TABLE TREmpleado (

empno NUMBER(4) PRIMARY KEY,

nombre VARCHAR2(10),

trabajo VARCHAR2(9),

salario NUMBER(7,2),

deptno NUMBER(2) REFERENCES TRDepartamento(depno));

El esquema es el siguiente y en él podemos comprobar cómo cada columna de los tipos Empleado_t y Departamento_t es mapeada a un elemento del documento XML. La estructura del documento XML también es definida por el esquema: <schema xmlns="http://www.w3.org/2001/XMLSchema"

targetNamespace="http://www.oracle.com/emp.xsd"

version="1.0"

Page 293: Java y Oracle 11g

Oracle /293

xmlns:xdb="http://xmlns.oracle.com/xdb"

elementFormDefault="qualified">

<element name = "Empleado" xdb:SQLType="Empleado_t" xdb:SQLSchema="SCOTT">

<complexType>

<sequence>

<element name = "EmpleadoId" type = "positiveInteger"

xdb:SQLName="empno" xdb:SQLType="NUMBER"/>

<element name = "Nombre" type = "string" xdb:SQLName="nombre"

xdb:SQLType="VARCHAR2"/>

<element name = "trabajo" type = "string" xdb:SQLName="trabajo"

xdb:SQLType="VARCHAR2"/>

<element name = "Salario" type = "positiveInteger" xdb:SQLName="salario"

xdb:SQLType="NUMBER"/>

<element name = "Departamento" xdb:SQLName="departamento" xdb:SQLType="Departamento_t"

xdb:SQLSchema="SCOTT">

<complexType>

<sequence>

<element name = "DeptNo" type = "positiveInteger"

xdb:SQLName="deptno" xdb:SQLType="NUMBER"/>

<element name = "DeptNombre" type = "string" xdb:SQLName="nombre"

xdb:SQLType="VARCHAR2"/>

<element name = "Lugar" type = "string" xdb:SQLName="lugar"

xdb:SQLType="VARCHAR2"/>

</sequence>

</complexType>

</element>

</sequence>

</complexType>

</element>

</schema>

A continuación podemos crear la vista de la siguiente manera: CREATE OR REPLACE VIEW Empleado_xml OF XMLTYPE

XMLSCHEMA "http://www.oracle.com/emp.xsd"

ELEMENT "Empleado"

WITH OBJECT ID (EXTRACTVALUE(sys_nc_rowinfo$, '/Empleado/EmpleadoId'))

AS

SELECT Empleado_t(e.empno, e.enombre, e.trabajo, e.salario, Departemento_t(d.deptno, d.nombre, d.lugar))

FROM TREmpleado e JOIN TRDepartamento d USING (deptno);

La consulta tendría como respuesta el siguiente documento XML: <Empleado xmlns="http://www.oracle.com/emp.xsd"

xmlns:xsi="http://www.oracle.com/emp.xsd http://www.oracle.com/emp.xsd">

<EmpleadoId>2100</EmpleadoId>

<Nombre>John</Nombre>

<Salario>123003</Salario>

<Departamento>

<Deptno>2000</Deptno>

<DeptNombre>Sports</DeptNombre>

<Lugar>San Francisco</Lugar>

</Departamento>

</Empleado>

Page 294: Java y Oracle 11g

Oracle /294

VI. PROCEDIMIENTOS DE GESTIÓN DE LA BASE DE DATOS.

1. Diccionario de datos de Oracle

El diccionario de datos de Oracle almacena toda la información que es usada para controlar todos los objetos de una base de datos. Aunque el diccionario de datos es normalmente el dominio de los administradores de base de datos (DBA's), también es una fuente de información valiosa para programadores y usuarios finales. En este capítulo veremos el diccionario de datos desde la perspectiva de un usuario final. Con algunas excepciones, los nombres de los objetos en el diccionario de datos de Oracle comienzan con uno de los siguientes prefijos: USER_, ALL_ o DBA_. Los registros en la vistas USER_ muestran normalmente información acerca de los objetos propiedad de la cuenta que realiza la consulta. Los registros en las vistas ALL_ incluyen los registros USER_ más información acerca de los objetos sobre los que se posee el permiso PUBLIC. Las vistas DBA_ incluyen todos los objetos de base de datos, independientemente de su propietario. Existen vistas USER_TipoObjeto, ALL_TipoObjeto y DBA_TipoObjeto para la mayoría de objetos de base de datos.

1.1. Las vistas «DICTIONARY» (DICT) y «DICT_COLUMNS».

Las descripciones de todos los objetos que constituyen el diccionario de datos son accesibles a través de una vista llamada DICTIONARY. Esta vista, también accesible con el sinónimo público DICT, consulta la base de datos para determinar qué vistas del diccionario de datos podemos ver. También muestra los sinónimos públicos para estas vistas. El siguiente ejemplo consulta DICT para obtener los nombres de todas las vistas del diccionario de datos cuyos nombres incluyen el término 'VIEWS'. Haciendo la selección desde una cuenta de no administrador se obtiene el siguiente resultado, donde se muestra el nombre y comentario de cada objeto. SELECT Table_Name, Comments

FROM DICT

WHERE Table_Name LIKE '%VIEWS%'

ORDER BY Table_Name;

TABLE_NAME COMMENTS

----------------------------------- -------------------------------------------------------------------------------

ALL_BASE_TABLE_MVIEWS All materialized views with log(s) in the database that the user can

see

ALL_MVIEWS All materialized views in the database

ALL_REGISTERED_MVIEWS Remote materialized views of local tables that the user can see

ALL_VIEWS Description of views accessible to the user

ALL_XML_VIEWS Description of all XMLType views that the user has privileges on

USER_BASE_TABLE_MVIEWS All materialized views with log(s) owned by the user in the database

USER_MVIEWS All materialized views in the database

USER_REGISTERED_MVIEWS Remote materialized views of local tables currently using logs owned

by the user

USER_VIEWS Description of the user's own views

USER_XML_VIEWS Description of the user's own XMLType views

Podemos consultar las columnas de las vistas del diccionario de datos mediante la vista DICT_COLUMNS. Como la vista DICTIONARY, DICT_COLUMNS muestra también comentarios. DICT_COLUMNS tiene tres columnas: Table_Name, Column_Name, y Comments. Consultando DICT_COLUMNS podremos determinar cuales vistas del diccionario de datos serán las más útiles para nuestras necesidades. Por ejemplo, si queremos ver el espacio asignado y usado por nuestros objetos de base de datos pero no estamos seguros de qué vista almacena esta información, podemos consultas DICT_COLUMNS, tal como se muestra en el siguiente ejemplo, el cual mira por todas las tablas del diccionario que tienen una columna llamada Blocks: SELECT Table_Name

FROM DICT_COLUMNS

WHERE Column_Name = 'BLOCKS' AND Table_Name LIKE 'USER%'

ORDER BY Table_Name;

TABLE_NAME

Page 295: Java y Oracle 11g

Oracle /295

--------------------------------------

USER_ALL_TABLES

USER_EXTENTS

USER_FREE_SPACE

USER_OBJECT_TABLES

USER_SEGMENTS

USER_TABLES

USER_TAB_PARTITIONS

USER_TAB_STATISTICS

USER_TAB_SUBPARTITIONS

USER_TS_QUOTAS

Para listar todos los nombres de columnas disponibles que podríamos haber usado en este último ejemplo, podemos consultar DICT_COLUMNS: SELECT DISTINCT Column_Name

FROM DICT_COLUMNS

ORDER BY Column_Name;

Siempre que no estemos seguros de dónde encontrar los datos que necesitamos, podemos verificar las vistas DICTIONARY y DICT_COLUMNS. Si parece que un número grande de vistas podrían ser útiles consultar DICTIONARY para ver los comentarios de cada vista.

1.2. Cosas que podemos seleccionar de: tablas (y columnas), vistas, sinónimos y secuencias.

Un catálogo de usuario lista todos aquellos objetos de los cuales el usuario puede seleccionar registros; esto es, cualquier objeto que puede ser listado en la cláusula FROM de una consulta. Aunque las secuencias no pueden ser referenciadas directamente en la cláusula FROM, Oracle las incluye en el catálogo. En esta sección veremos cómo recuperar información sobre tablas, columnas, vistas, sinónimos, secuencias y el catálogo de usuario. 1.2.1. Catálogo: USER_CATALOG (CAT) Consultando la vista USER_CATALOG (o con su sinónimo CAT) podemos ver todas las tablas, vistas, sinónimos y secuencias propiedad del usuario. La columna Table_Name muestra el nombre del objeto (aunque no sea una tabla), y la columna Table_Type muestra el tipo del objeto: SELECT Table_Name, Table_Type

FROM USER_CATALOG

WHERE Table_Name LIKE 'T%';

Hay dos catálogos adicionales disponibles. La vista ALL_CATALOG muestra lo mismo que USER_CATALOG más cualquier objeto sobre el que tengamos el permiso PUBLIC. La vista DBA_CATALOG muestra todos los objetos del catálogo. Además de las columnas Table_Name y Table_Type mostradas en la consulta de USER_CATALOG, las vistas ALL_CATALOG y DBA_CATALOG incluyen la columna Owner, que indica el propietario del objeto. 1.2.2. Objetos: USER_OBJECTS (OBJ) La vista USER_CATALOG sólo muestra información sobre tablas, vistas, secuencias y sinónimos. Para recuperar información sobre todos los tipos de objetos podemos consultar la vista USER_OBJECTS. Podemos usar esta vista para encontrar cualquier cosa sobre muchos tipos de objetos, incluidos clústeres, enlaces de base de datos, directorios, funciones, índices, librerías, paquetes, cuerpos de paquete, clases Java, tipos de datos abstractos, recursos, secuencias, sinónimos, tablas, triggers, vistas materializadas, LOB's y vistas. Las columnas de la vista USER_OBJECTS se describen en la siguiente tabla.

Nombre col. Descripción

Object_Name El nombre del objeto.

SubObject_Name El nombre del sub-objeto, como un nombre de partición.

Object_ID Un identificador único de Oracle para el objeto.

Data_Object_ID El número de objeto del segmento que contiene los datos del objeto.

Object_Type El tipo de objeto (TABLE, INDEX, TABLE PARTITION, ...).

Created La fecha y hora de creación del objeto (como una columna DATE).

Last_DDL_Time La fecha y hora en que se usó el último comando DDL sobre el objeto, incluidos ALTER,

GRANT y REVOKE.

Timestamp La fecha y hora de creación del objeto (como Created, pero almacenado como una columna de caracteres).

Status El estado del objeto (VALID o INVALID).

Page 296: Java y Oracle 11g

Oracle /296

Temporary Un indicador de si el objeto es una tabla temporal.

Generated Un indicador de si el nombre del objeto fue generado por el sistema.

Secondary Un indicador de si el objeto es un índice secundario creado por un índice de dominio.

USER_OBJECTS (o su sinónimo OBJ) contiene varias piezas vitales de información que no se encuentran en otras vistas del diccionario de datos. Registra las fechas de creación de los objetos (la columna Created) y la última vez que un objeto fue alterado (la columna Last_DDL_Time). Estas columnas son útiles cuando intentamos reconciliar diferentes conjuntos de objetos en la misma aplicación.

Nota. Si recreamos objetos de alguna manera (por ejemplo, usando la utilidad Import), su valor para la columna Created cambiará a la última vez en que fueron creados.

Análogamente disponemos de las vistas ALL_OBJECTS y DBA_OBJECTS. Estas vistas añaden la columna Owner. 1.2.3. Tablas: USER_TABLES (TABS) Aunque todos los objetos de usuario son mostrados en la vista USER_OBJECTS, pocos atributos de estos objetos se muestran aquí. Para obtener más información sobre un objeto necesitamos mirar la vista que es específica para el tipo de objeto. Para las tablas, se usa la vista USER_TABLES (o por su sinónimo TABS).

Nota. Versiones previas de Oracle incluyen una vista llamada TAB. Esta vista, la cual es similar a TABS, se sigue soportando porque es usada por algunos productos de Oracle. Sin embargo, TAB no contiene las mismas columnas que TABS. Hay que usar TABS en nuestras consultas al diccionario de datos.

Las columnas de USER_TABLES pueden dividirse en cuatro categorías (identificación, relativas al espacio, relativas a estadísticas, y otras), tal como se muestran en la siguiente tabla.

Columnas de identificación

Table_Name Nombre de la tabla.

Backed_Up Indica si la tabla fue respaldada desde la última modificación.

Partitioned Indica si la tabla fue particionada (YES o NO).

IOT_Name Nombre de la tabla de sólo índice, si la hay, a la cual el desbordamiento o las entradas de mapeo pertenecen.

Logging Atributo de logging.

IOT_Type Si es una tabla de solo índice, entonces es IOT o IOT_OVERFLOW o

IOT_MAPPING, sino es NULL.

Temporary Indica si la sesión actual puede ver solo los datos puesto en este mismo objeto.

Nested Indica si es una tabla anidada.

Secondary Indica si la tabla fue creada como parte de la creación de índices de dominio.

Columnas relativas al espacio

Tablespace_Name Nombre del tablespace que contiene la tabla.

Cluster_Name Nombre del clúster, si lo hay, en el cual la tabla está contenido.

Pct_Free Mínimo porcentaje de espacio libre en un bloque.

Pct_Used Mínimo porcentaje de espacio usado en un bloque.

Ini_Trans Número inicial de transacciones.

Max_Trans Máximo número de transacciones.

Initial_Extent Tamaño de la extensión inicial en bytes.

Next_Extent Tamaño de la extensión secundaria en bytes.

Min_Extents Mínimo número de extensiones permitidas en el segmento.

Max_Extents Máximo número de extensiones permitidas en el segmento.

Pct_Increase Porcentaje de incremento en el tamaño de las extensiones.

Freelists Número de procesos libres asignados en este segmento.

Freelist_Groups Número de grupos libres asignados en este segmento.

Columnas relativas a estadísticas

Num_Rows El número de filas en la tabla.

Blocks El número de bloques usados en la tabla.

Empty_Blocks El número de bloques vacíos (nunca usados) de la tabla.

Avg_Space El promedio de espacio libre disponible en la tabla.

Chain_Cnt El número de filas encadenadas en la tabla.

Page 297: Java y Oracle 11g

Oracle /297

Avg_Row_Len El promedio de longitud de filas, incluidas las filas de desbordamiento.

Sample_Size El tamaño de la muestra usada en los análisis de la tabla.

Last_Analyzed La fecha y la hora más reciente en que la tabla fue analizada.

Avg_Space_Freelist_Blocks El promedio de espacio libre de todos los bloques de una lista libre.

Num_Freelist_Blocks El número de bloques de una lista libre.

Global_Stats Indica si las estadísticas se calculan sin combinar particiones subyacentes.

User_Stats Indica si la estadística fue entrada directamente por el usuario.

Otras columnas

Degree El número de hilos por instancia para consultas paralelas sobre la tabla.

Instances El número de instancias para consultas paralelas sobre la tabla.

Cache Indica si la tabla ha sido almacenada en una caché.

Table_Lock Indica si el bloqueo de tabla está habilitado o deshabilitado.

Buffer_Pool El búfer por defecto que será usado por los bloques de la tabla.

Row_Movement Indica si el movimiento de fila particionada está permitido o no.

Duration Si es una tabla temporal, entonces es SYS$SESSION o SYS$TRANSACTION,

sino es NULL.

Skip_Corrupt Indica si está habilitado o no saltarse bloques corruptos.

Monitoring Indica si deben registrarse la cantidad de modificaciones.

Cluster_Owner Propietario del clúster, si lo hay, al cual pertenece la tabla.

Dependencies Indica si deberíamos nosotros guardar la pista de dependencias del nivel de fila.

Dropped Indica si la tabla fue enviada al reciclador (YES) o no (NO).

Compression Indica si la tabla está comprimida (YES) o no (NO).

El nombre de la tabla se muestra en la columna Table_Name. La columna Backed_Up muestra si la tabla ha sido o no respaldada desde su última modificación. La columna Partitioned tendrá el valor 'YES' si la tabla ha sido particionada. Las columnas relativas a estadísticas como Num_Rows, Blocks, Empty_Blocks, Avg_Row_Len y Last_Analyzed, son pobladas cuando la tabla es analizada.

Nota. Desde Oracle Database 10g, podemos usar la vista USER_TAB_STATISTICS para acceder a las estadísticas de nuestras tablas.

La siguiente consulta lista todas las tablas cuyos nombres empiezan con la letra 'L': SELECT Table_Name

FROM USER_TABLES

WHERE Table_Name LIKE 'L%';

La vista ALL_TABLES muestra todas las tablas propiedad del usuario así como cualquier otra sobre la que tenga permisos de acceso. La vista DBA_TABLES lista todas las tablas de la base de datos. Estas dos vistas incluyen la columna Owner con el nombre del propietario de la tabla.

Nota. Para tablas externas, usar las vistas USER_EXTERNAL_TABLES y USER_EXTERNAL_LOCATIONS.

1.2.4. Columnas: USER_TAB_COLUMNS (COLS) Aunque los usuarios no consulten por columnas, la vista del diccionario de datos que muestra columnas está estrechamente ligada a la vista de diccionario de datos de tablas. Esta vista, llamada USER_TAB_COLUMNS, muestra información específica de las columnas. USER_TAB_COLUMNS también puede ser consultada mediante el sinónimo público COLS. Las columnas que podemos consultar de USER_TAB_COLUMNS pueden ser separadas en tres categorías:

• Identificación, como Table_Name, Column_Name, y Column_ID. • Relativas a la definición, como Data_Type, Data_Length, Data_Precision, Data_Scale, Nullable, y Default_Length • Relativas a estadísticas, como Num_Distinct, Low_Value, High_Value, Density, Num_Nulls, y otras.

Las columnas Table_Name y Column_Name contienen los nombres de nuestras tablas y columnas. Las columnas relativas a estadísticas son pobladas cuando la tabla es analizada. Las columnas para estadísticas son también proporcionadas en la vista USER_TAB_COL_STATISTICS. Para ver las definiciones de columnas de una tabla podemos consultar USER_TAB_COLUMNS, especificando Table_Name en la cláusula WHERE: SELECT Column_Name, Data_Type

FROM USER_TAB_COLUMNS

Page 298: Java y Oracle 11g

Oracle /298

WHERE Table_Name = 'MI_TABLA';

COLUMN_NAME DATA_TYPE

--------------------- -----------------

CAMPO1 VARCHAR2

CAMPO2 CHAR

CAMPO3 NUMBER

Esta misma información también se puede obtener mediante SQL*Plus con el comando DESCRIBE; sin embargo, DESCRIBE no nos da la opción de ver los valores por defecto de las columnas y las estadísticas. La vista ALL_TAB_COLUMNS muestra las columnas de todas las tablas y vistas propiedad del usuario así como cualquier otra sobre los que tenga permisos de acceso. DBA_TAB_COLUMNS muestra las columnas de todas las tablas y vistas de la base de datos. Ambas vistas añaden la columna Owner indicando el propietario de las tablas. Columnas de estadísticas. Muchas de las columnas de estadísticas están disponibles tanto en USER_TAB_COLUMNS como en USER_TAB_COL_STATISTICS. Las columnas disponibles en USER_TAB_COL_STATISTICS son las mismas de USER_TAB_COLUMNS más Table_Name y Column_Name. USER_TAB_COL_STATISTICS contiene columnas de estadísticas que también están en USER_TAB_COLUMNS para compatibilidad. Deberíamos acceder a ellos usando USER_TAB_COL_STATISTICS. Histogramas de valores de columna. Podemos usar histogramas para mejorar el análisis usado por el optimizador. La vista USER_TAB_HISTOGRAMS contiene información acerca de cada histograma de columna: Table_Name, Column_Name, Endpoint_Number, Endpoint_Value, y Endpoint_Actual_Value. Los valores en USER_TAB_HISTOGRAMS son usados por el optimizador para determinar la distribución de los valores de columna dentro de la tabla. También están disponibles las vistas ALL_TAB_HISTOGRAMS y DBA_TAB_HISTOGRAMS. Columnas actualizables. Podemos actualizar registros en vistas que contengan un JOIN en su consulta, a condición de que el JOIN cumpla ciertos criterios. La vista USER_UPDATABLE_COLUMNS muestra todas las columnas que podemos actualizar. Para una columna determinada podemos consultar Owner, Table_Name, y Column_Name; la columna Updatable tendrá el valor 'YES' si la columna puede ser actualizada, y el valor 'NO' si la columna no puede ser actualizada. Podemos también consultar las columnas Insertable y Deletable para saber si podemos insertar o borrar registros mediante la vista. 1.2.5. Vistas: USER_VIEWS La consulta subyacente de una vista es accesible mediante la vista del diccionario de datos USER_VIEWS, la cual contiene 10 columnas. Las 3 columnas principales son:

View_Name El nombre de la vista

Text_Length La longitud de la consulta base, en caracteres.

Text La consulta que usa la vista.

La columna Text es de tipo LONG. Esto puede causar problemas cuando consultemos USER_VIEWS mediante SQL*Plus, porque SQL*Plus trunca los LONG. Sin embargo, el punto en el que ocurre el truncamiento puede ser cambiado mediante el comando SET LONG. USER_VIEWS proporciona un mecanismo para determinar el valor apropiado para el punto de truncamiento, tal como se ve en el siguiente ejemplo. La columna Text_Length muestra la longitud de la consulta subyacente. Por lo tanto, el punto de truncamiento LONG en SQL*Plus debe asignarse a un valor mayor o igual que el valor de esta columna. Por ejemplo, lo siguiente muestra una vista llamada Mi_Vista y cuya longitud de texto es de 355: SELECT View_Name, Text_Length

FROM USER_VIEWS

WHERE View_Name = 'MI_VISTA';

VIEW_NAME TEXT_LENGTH

---------------------- ---------------------

MI_VISTA 355

Ya que la longitud del texto de la vista es de 355 caracteres, podemos usar el comando SET LONG para incrementar el punto de truncamiento (que por defecto es 80): SET LONG 355

Page 299: Java y Oracle 11g

Oracle /299

Entonces podemos consultar USER_VIEWS para ver el texto de la vista: SELECT Text

FROM USER_VIEWS

WHERE View_Name = 'MI_VISTA';

Nota. Podemos consultar la definición de columnas de una vista con USER_TAB_COLUMNS, la misma vista del diccionario de datos para consultar por tablas.

Si usamos alias para las columnas de nuestra vista, y los alias de columna son parte de la consulta de la vista, entonces las consultas en el diccionario de datos sobre la vista se simplificarán. Ya que se muestra el texto entero de la consulta de la vista con USER_VIEWS, los alias de columna se mostrarán. Podemos crear vistas usando este formato: CREATE VIEW Cliente_View (Codigo, Nombre) AS

SELECT Id, Apellidos || ', ' || Nombre

FROM Cliente;

Indicando los nombres de columnas en la cabecera del comando CREATE VIEW se previene que una consulta con USER_VIEWS muestre los alias de las columnas de nuestra vista. El único medo de ver los nombres de las columnas de nuestra vista es consultando USER_TAB_COLUMNS. Para soportar vistas de objetos (véase el capítulo "Soporte de objetos y XML"), USER_VIEWS contiene las siguientes columnas:

Type_Text Tipo de cláusula de la vista tipada.

Type_Text_Length Longitud del tipo de cláusula de la vista tipada.

OID_Text Cláusula WITH OID de la vista tipada.

OID_Text_Length Longitud de la cláusula WITH OID de la vista tipada.

View_Type_Owner Propietario del tipo de la vista para la vista tipada

View_Type Tipo para la vista

Análogamente también se dispone de las vistas ALL_VIEWS y DBA_VIEWS. 1.2.6. Sinónimos: USER_SYNONYMS (SYN) La vista USER_SYNONYMS (o SYN) muestra todos los sinónimos de los que somos propietarios. Las columnas que incluye son:

Synonym_Name El nombre del sinónimo.

Table_Owner El propietario de la tabla a la que se refiere el sinónimo.

Table_Name El nombre de la tabla a la que se refiere el sinónimo.

DB_Link El nombre del enlace de base de datos usado en el sinónimo.

La vista USER_SYNONYMS es usada depurando programas o resolviendo problemas con accesos de usuario a objetos dentro de aplicaciones. La columna DB_Link será NULL si el sinónimo no usa un enlace de base de datos. Por lo tanto, si queremos ver una lista de enlaces de base de datos actualmente usados por sinónimos de nuestra cuenta, podemos ejecutar la siguiente consulta: SELECT DISTINCT DB_Link

FROM USER_SYNONYMS

WHERE DB_Link IS NOT NULL;

Análogamente también se dispone de las vistas ALL_SYNONYMS y DBA_SYNONYMS. 1.2.7. Secuencias: USER_SEQUENCES (SEQ) Para mostrar los atributos de secuencias podemos consultar la vista USER_SEQUENCES (o su sinónimo SEQ). Las columnas de USER_SEQUENCES se describen a continuación:

Nombre col. Descripción

Sequence_Name Nombre de la secuencia.

Min_Value Mínimo valor de la secuencia.

Max_Value Máximo valor de la secuencia

Increment_By Incremento entre los valores de la secuencia.

Cycle_Flag Un indicador de si la secuencia es cíclica.

Order_Flag Un indicador de si los números de la secuencia son generados en orden.

Cache_Size Número de entradas de secuencia en caché.

Last_Number El último número de secuencia escrito a disco o en caché.

Page 300: Java y Oracle 11g

Oracle /300

La columna Last_Number no es actualizada durante operaciones normales de la base de datos; se usa durante operaciones de reinicio/recuperación de la base de datos. Análogamente también se dispone de las vistas ALL_SEQUENCES y DBA_SEQUENCES.

1.3. Papelera: USER_RECYCLEBIN y DBA_RECYCLEBIN

Desde Oracle Database 10g podemos usar el comando FLASHBACK TABLE para recuperar tablas y objetos dependientes que han sido borrados. Para ver los objetos presentes en nuestra papelera podemos consultar la vista USER_RECYCLEBIN (o RECYCLEBIN). Nos mostrará el nombre del objeto original, si puede ser recuperado (columna Can_Undrop) y su fue borrado porque estaba relacionado con un objeto borrado explícitamente (columna Base_Object). Los objetos permanecen en el portapapeles hasta que son purgados. Los objetos pueden ser removidos de la papelera automáticamente sin no hay espacio libre en el tablespace donde fueron borrados. Los DBA's pueden ver todos los objetos de todas las papeleras mediante la vista DBA_RECYCLEBIN. No hay una versión ALL_ para esta vista.

1.4. Restricciones y comentarios.

Las restricciones y comentarios ayudan a comprender cómo las tablas y columnas se relacionan entre sí. Los comentarios son estrictamente información; no imponen ninguna condición sobre los datos almacenados en los objetos que describen. Las restricciones, por otra parte, definen las condiciones bajo las cuales los datos son válidos. Restricciones habituales incluyen NOT NULL, UNIQUE, PRIMARY KEY y FOREIGN KEY. 1.4.1. Restricciones: USER_CONSTRAINTS La información de restricciones es accesible mediante la vista USER_CONSTRAINTS. Esta información es muy útil cuando intentamos modificar las restricciones sobre datos o resolver problemas con los datos de la aplicación. Las columnas de esta vista son las siguientes:

Nombre col. Descripción

Owner El propietario de la restricción.

Constraint_Name El nombre de la restricción.

Constraint_Type El tipo de la restricción:

'C' para restricción CHECK; incluye NOT NULL

'P' para restricción PRIMARY KEY

'R' para restricción FOREIGN KEY

'U' para restricción UNIQUE

'V' para restricción WITH CHECK OPTION (en vistas)

'O' para restricción WITH READ ONLY (en vistas)

Table_Name El nombre de la tabla asociada con la restricción.

Search_Condition La condición de búsqueda usada (para restricciones CHECK)

R_Owner El propietario de la tabla referenciada por la restricción FOREIGN KEY.

R_Constraint_Name El nombre de la restricción referenciada por una restricción FOREIGN KEY.

Delete_Rule La acción a tomar sobre tablas FOREIGN KEY cuando un registro PRIMARY KEY es

borrado (CASCADE o NO ACTION).

Status El estado de la restricción (ENABLED o DISABLED).

Deferrable Un indicador de si la restricción puede ser diferida.

Deferred Un indicador de si la restricción fue inicialmente diferida.

Validated Un indicador (VALIDATED o NOT VALIDATED) de si todos los datos cumplen la restricción.

Generated Un indicador de si el nombre de restricción ha sido generado por la base de datos.

Bad Un indicador de si un dato que fue usado en la creación de la restricción sin especificar

un valor de centuria para restricciones CHECK es un año ambiguo de dos dígitos; se aplica sólo a restricciones en bases de datos actualizadas desde versiones previas.

Rely Un indicador de si la restricción está forzada o no.

Last_Change La fecha y hora en la que la restricción fue activada o desactivada.

Index_Owner Propietario de índice relacionado.

Index_Name Nombre del índice relacionado.

Invalid Indicador de valido o no válido.

View_Related Indicador Yes/No para registrar si la restricción es relativa a una vista.

Aunque es una vista USER_ contiene una columna Owner. En esta vista, Owner hace referencia al propietario

Page 301: Java y Oracle 11g

Oracle /301

de la restricción, y no al propietario de la tabla. Las restricciones FOREIGN KEY tienen siempre un valor para las columnas R_Owner y R_Constraint_Name. Estas dos columnas indican las restricciones referenciadas. Una FOREIGN KEY referencia otra restricción, no otra columna. Las restricciones NOT NULL sobre columnas son almacenadas en restricciones CHECK, así que tiene el tipo 'C'. Consultado USER_CONSTRAINTS obtendremos los nombres de todas las restricciones sobre una tabla. Esto es importante cuando intentamos interpretar mensajes de error que sólo proporcionan el nombre de la restricción que fue violada. Una vez que conocemos el nombre y tipo de la restricción, podemos verificar las columnas asociadas mediante la vista USER_CONS_COLUMNS, descrita en la siguiente sección. Si no hemos asignado nombre a una restricción cuando la hemos creado, Oracle genera un nombre único. Si la restricción tiene un nombre generado por el sistema, esto se indicará en la columna Generated. Si deferimos una restricción (como se indica en la columna Deferred, la restricción no será forzada durante una transacción. Por ejemplo, si hemos realizado actualizaciones masivas sobre tablas relacionadas y no podemos garantizar el orden de las transacciones, podemos decidir diferir la verificación de la restricción sobre las tablas hasta que la actualización se ha completado. Análogamente también se dispone de las vistas ALL_CONSTRAINTS y DBA_CONSTRAINTS. 1.4.2. Restricción de columnas: USER_CONS_COLUMNS Podemos ver las columnas asociadas con restricciones mediante la vista USER_CONS_COLUMNS. Si hemos consultado USER_CONSTRAINTS para obtener los tipos y nombres de las restricciones involucradas, podemos usar USER_CONS_COLUMNS para determinar qué columnas están involucradas con la restricción. Las columnas de esta vista son las siguientes:

Nombre col. Descripción

Owner El propietario de la restricción.

Constraint_Name El nombre de la restricción.

Table_Name El nombre de la tabla asociada con la restricción.

Column_Name El nombre de la columna asociada con la restricción.

Position El orden de la columna dentro de la definición de la restricción.

Hay sólo dos columnas de USER_CONS_COLUMNS que no están en USER_CONSTRAINTS: Column_Name y Position. Una simple consulta de esta tabla se muestra a continuación: SELECT Column_Name, Position

FROM USER_CONS_COLUMNS

WHERE Constraint_Name = 'SYS_C0008791';

COLUMN_NAME POSITION

---------------------- ---------------------

NOMBRE 1

APELLIDOS 2

Como se ve en el resultado, la combinación del nombre y apellidos forman la restricción (en este caso, una clave primaria). La columna Position es significativa. Cuando creamos una restricción UNIQUE o PRIMARY KEY, Oracle crea automáticamente un índice único sobre el conjunto de columnas que especificamos. El índice es creado según el orden de las columnas. El orden de las columnas afecta al rendimiento del índice. Un índice sobre varias columnas puede ser más eficiente si la primera columna del índice (Position=1) es usada en la cláusula WHERE de consultas. Análogamente también se dispone de las vistas ALL_CONS_COLUMNS y DBA_CONS_COLUMNS. 1.4.3. Excepciones en restricciones: EXCEPTIONS Cuando activamos restricciones sobre tablas que ya contienen datos, podemos encontrarnos con violaciones con los datos existentes. Por ejemplo, podemos intentar crear una restricción PRIMARY KEY sobre una columna que contiene el mismo valor en varios registros, pero tal tentativa fallaría debido a violaciones de unicidad. Podemos capturar información acerca de las filas que cusan fallos en la creación de la restricción. Primero, crearemos una tabla llamada EXCEPTIONS en nuestro esquema; el script SQL que debemos usar para crear eta tabla se denomina utlexcpt.sql, y normalmente esta localizado en el directorio /rdbms/admin dentro de la carpeta de instalación de Oracle.

Page 302: Java y Oracle 11g

Oracle /302

Archivo «utlexcpt.sql»

rem

rem $Header: utlexcpt.sql,v 1.1 1992/10/20 11:57:02 GLUMPKIN Stab $

rem

Rem Copyright (c) 1991 by Oracle Corporation

Rem NAME

Rem except.sql - <one-line expansion of the name>

Rem DESCRIPTION

Rem <short description of component this file declares/defines>

Rem RETURNS

Rem

Rem NOTES

Rem <other useful comments, qualifications, etc.>

Rem MODIFIED (MM/DD/YY)

Rem glumpkin 10/20/92 - Renamed from EXCEPT.SQL

Rem epeeler 07/22/91 - add comma

Rem epeeler 04/30/91 - Creation

create table exceptions(row_id rowid, owner varchar2(30),

table_name varchar2(30), constraint varchar2(30));

La tabla EXCEPTIONS contiene cuatro columnas: Row_ID (el ROWID de cada fila que viola la restricción), Owner (el propietario de la restricción violada), Table_Name (la tabla en la cual fue creada la restricción violada), y Constraint (la restricción violada por la fila). Después de crear la tabla EXCEPTIONS, intentaremos habilitar la restricción PRIMARY KEY sobre una tabla REVISTA: ALTER TABLE REVISTAENABLE PRIMARY KEY EXCEPTIONS INTO EXCEPTIONS;

ORA-02437: cannot enable (NEWSPAPER.SYS_C00516) - primary key violated

La creación de la restricción falla, y una referencia a todas las filas que violan la restricción es puesta en la tabla EXCEPTIONS. Por ejemplo, si la restricción PRIMARY KEY de este último ejemplo genera una excepción, entonces podemos consultar la tabla EXCEPTIONS tal como se muestra a continuación: SELECT Owner, Table_Name, Constraint FROM EXCEPTIONS;

OWNER TABLE_NAME CONSTRAINT

------------------- --------------------- --------------------

EMPLEADO REVISTA SYS_C00516

EMPLEADO REVISTA SYS_C00516

Dos filas violan la restricción llamadaSYS_C00516 (el cual, en este ejemplo, es el nombre de la restricción PRIMARY KEY para la tabla REVISTA). Podemos determinar qué filas de la tabla REVISTA se corresponden a estas excepciones combinando la columna Row_ID de la tabla EXCEPTIONS con la pseudo-columna ROWID de la tabla sobre la cual se aplicó la restricción: SELECT *

FROM REVISTA

WHERE RowID IN (SELECT Row_ID FROM EXCEPTIONS);

Nota. Operaciones de flashback y recuperación pueden cambiar los ROWID de las filas previamente insertadas dentro de las tablas.

1.4.4. Comentarios de tablas: USER_TAB_COMMENTS Podemos añadir un comentario a una tabla, vista o columna después de haber sido creadas. Podemos mostrar estos comentarios mediante las vistas del diccionario de datos DICTIONARY y DICT_COLUMNS. Para mostrar comentarios sobre nuestras propias tablas se usa la vista USER_TAB_COMMENTS. La vista USER_TAB_COMMENTS contiene tres columnas:

Table_Name El nombre de la tabla o vista.

Table_Type El tipo de objeto (TABLE, OBJECT TABLE, o VIEW).

Comments Comentarios asociados al objeto.

Para añadir comentarios a una tabla se usa el comando COMMENT, tal como se muestra a continuación: COMMENT ON TABLE Festivos IS 'Fechas con los días festivos de la empresa';

Consultando la vista USER_TAB_COMMENTS especificando Table_Name podemos ver los comentarios, tal

Page 303: Java y Oracle 11g

Oracle /303

como se muestra a continuación: SELECT Comments

FROM USER_TAB_COMMENTS

WHERE Table_Name = 'Festivos';

COMMENTS

---------------------------------------------------------

Fechas con los días festivos de la empresa

Para borrar un comentario se debe asignar a un string vacío: COMMENT ON TABLE Festivos IS '';

Podemos ver los comentarios de todas las tablas mediante la vista ALL_TAB_COMMENTS. Esta vista tiene una columna adicional, Owner, que especifica el propietario de la tabla. La vista DBA_TAB_COMMENTS muestra todas tablas de la base de datos. 1.4.5. Comentarios de columnas: USER_COL_COMMENTS La vista USER_COL_COMMENTS muestra los comentarios asociados con las columnas de nuestras tablas. Estos comentarios son añadidos a la base de datos mediante el comando COMMENT. La vista USER_COL_COMMENTS contiene tres columnas:

Table_Name El nombre de la tabla o vista.

Column_Name El nombre de la columna.

Comments Comentarios asociados ala columna.

Para añadir un comentario a una columna se usa el comando COMMENT, tal como se muestra a continuación: COMMENT ON COLUMN Festivos.Dia IS 'Día correspondiente al festivo';

Consultado USER_COL_COMMENTS y especificando Table_Name y Column_Name podemos ver los comentarios para una columnas. SELECT Comments

FROM USER_COL_COMMENTS

WHERE Table_Name = 'Festivos' AND Column_Name = 'Dia';

Para borrar un comentario hay que asignar un string vacío: COMMENT ON COLUMN Festivos.Dia IS '';

También disponemos de las vistas análogas ALL_COL_COMMENTS y DBA_COL_COMMENTS.

1.5. Índices y clústeres.

Los índices y clústeres no cambian los datos almacenados en las tablas; sin embargo, cambian el modo en que los datos son accedidos y almacenados. 1.5.1. Índices: USER_INDEXES (IND) En Oracle, los índices están muy relacionados con las restricciones. Las restricciones PRIMARY KEY y UNIQUE

siempre tienen asociados índices de unicidad. Hay dos vistas del diccionario de datos para consultar información sobre los índices: USER_INDEXES(o IND) y USER_IND_COLUMNS. Las columnas de USER_INDEXES pueden ser agrupadas en cuatro categorías, tal como se muestra en la siguiente tabla.

Columnas de identificación

Indexe_Name Nombre del índice.

Table_Owner Propietario de la tabla asociada con el índice.

Table_Name Nombre de la tabla asociada con el índice.

Table_Type

Uniqueness

Status

Partitioned

Index_Type

Temporary

Generated

Logging

Compression

Prefix_Length

Secondary

Page 304: Java y Oracle 11g

Oracle /304

Ityp_Owner

Ityp_Name

Columnas relativas al espacio

Tablespace_Name

Ini_Trans

Max_Trans

Initial_Extent

Next_Extent

Min_Extents

Max_Extents

Pct_Increase

Pct_Free

Freelists

Freelist_Groups

Pct_Threshold

Columnas relativas a estadísticas

Blevel

Leaf_Blocks

Distinct_Keys

Avg_Leaf_Blocks_Per_Key

Avg_Data_Blocks_Per_Key

Clustering_Factor

Num_Rows

Sample_Size

Last_Analyzed

User_Stats

Global_Stats

Otras columnas

Degree

Instances

Include_Column

Buffer_Pool

Duration

Pct_Direct_Access

Parameters

Domidx_Status

Domidx_Opstatus

Funcidx_Status

Join_Index

IOT_Redundant_Pkey_Elim

Dropped

El nombre del índice se muestra en la columna Index_Name. El propietario y nombre de la tabla están en las columnas Table_Owner y Table_Name. La columna Uniqueness se asignará a UNIQUE para índices de unicidad y a NONUNIQUE para otros índices. La columna Table_Type registra si el índice es sobre una tabla ('TABLE') o clúster ('CLUSTER'). La columna Dropped, disponible desde Oracle Database 10g, identifica índices que están en la papelera de reciclaje.

Nota. Desde Oracle Database 10g, podemos usar la vista USER_IND_STATISTICS para acceder a las estadísticas de nuestros índices.

Para ver todos los índices de una tabla hay que consultar USER_INDEXES usando las columnas Table_Owner y Table_Name en la cláusula WHERE, tal como se muestra a continuación: SELECT Index_Name, Uniqueness

FROM USER_INDEXES

WHERE Table_Owner = 'Empleado' AND Table_Name = 'Festivos';

INDEX_NAME UNIQUENES

------------------- --------------------

Page 305: Java y Oracle 11g

Oracle /305

PK_FESTIVOS UNIQUE

La columna Clustering_Factor no está directamente relacionada con los clústeres, sino que representa el grado en el cual las filas de la tabla están ordenadas. Cuando más ordenadas estén las filas, más eficientes serán las consultas de rango (las consultas de rango son aquellas en las cuales se da un rango de valores para una columna). Para descubrir qué columnas son parte de los índices, y su orden dentro del índice, necesitamos consultar la vista USER_IND_COLUMNS. También se dispone de las vistas ALL_INDEXES y DBA_INDEXES.

Nota. Para índices basados en funciones, ver la vista USER_IND_EXPRESSIONS.

1.5.2. Columnas de índices: USER_IND_COLUMNS Podemos determinar qué columnas están en un índice consultando la vista USER_IND_COLUMNS. Las columnas disponibles de esta vista son las siguientes.

Index_Name El nombre del índice.

Table_Name El nombre de la tabla del índice.

Column_Name El nombre de la columna dentro del índice.

Column_Position La posición de la columna en el índice.

Column_Length La longitud en el índice de la columna.

Char_Length Longitud máxima del código de la columna (Unicode)

Descend Un indicador Y/N que indica si la columna está ordenada en orden descendente.

Cinco columnas de esta vista no están en USER_INDEXES: Column_Name, Column_Position, Column_Length, Char_Length, y Descend. La columna Column_Length, como las columnas relativas a estadísticas en USER_INDEXES, se puebla cuando la tabla base del índice es analizada. A continuación se muestra una simple consulta sobre esta tabla, usando el Index_Name de USER_INDEXES (en este ejemplo, la columna Column_Position es referenciada con el alias Pos): SELECT Column_Name, Column_Position Pos

FROM USER_IND_COLUMNS

WHERE Index_Name = 'PK_FESTIVOS';

COLUMN_NAME POS

--------------------- ----------

MES 1

DIA 2

También se dispone de las vistas ALL_IND_COLUMNS y DBA_IND_COLUMNS. 1.5.3. Columnas de índices Bitmap Join: USER_JOIN_IND_COLUMNS Si hemos creado un índice Bitmap Join, podemos consultar la vista USER_JOIN_IND_COLUMNS para obtener los detalles de join. La vista USER_JOIN_IND_COLUMNS registra los nombres de las tablas involucradas en el Join, la columna INNER y OUTER JOIN, y la dimensión y tablas involucradas. 1.5.4. Clústeres: USER_CLUSTERS (CLU) Los parámetros de almacenamiento y estadísticos asociados con clústeres son accesibles mediante la vista USER_CLUSTERS (también conocida por su sinónimo CLU). Las columnas de esta vista se muestran en la siguiente tabla separadas por tipos.

Columnas de identificación

Cluster_Name

Cluster_Type

Function

Columnas relativas al espacio

Tablespace_Name

Pct_Free

Pct_Used

Key_Size

Ini_Trans

Max_Trans

Initial_Extent

Next_Extent

Page 306: Java y Oracle 11g

Oracle /306

Min_Extents

Max_Extents

Pct_Increase

Freelists

Freelist_Groups

Columnas relativas a estadísticas

Avg_Blocks_Per_Key

Hashkeys

Otras columnas

Degree

Instances

Cache

Buffer_Pool

Single_Table

Dependencies

La columna Cluster_Name contiene el nombre del clúster. Cluster_Type especifica si el clúster usa un índice estándar B*-tree o una función de hashing para el clúster. 1.5.5. Columnas de clúster: USER_CLU_COLUMNS Para ver el mapeado de columnas de tabla a columnas de clúster, hay que consultar la vista USER_CLU_COLUMNS, cuyas columnas son las siguientes:

Cluster_Name El nombre del clúster.

Clu_Column_Name El nombre de la columna clave en el clúster.

Table_Name El nombre de la tabla dentro del clúster.

Tab_Column_Name El nombre de la columna clave en la tabla.

Desde un único clúster podemos almacenar datos para varias tablas, USER_CLU_COLUMNS es útil para qué columnas de qué tablas mapean las columnas del clúster. No hay versión ALL_ para esta vista, pero sí existe la vista DBA_CLU_COLUMNS.

1.6. Tipos de datos abstractos, estructuras ORDBMS y LOB's.

En esta sección veremos las vistas del diccionario de datos asociadas con las estructuras objeto-relacional de Oracle, como los tipos de datos abstractos, métodos y objetos grandes (LOB's). Existen versiones USER_, ALL_ y DBA_ de estas vistas. 1.6.1. Tipos de datos abstractos: USER_TYPES Los tipos de datos abstractos creados dentro de nuestro esquema pueden ser listados usando la vista USER_TYPES, la cual incluye columnas para el nombre del tipo (Type_Name), número de atributos (Attributes) y número de métodos (Methods) definidos por el tipo de dato. Por ejemplo, el tipo de dato ANIMAL_TY tiene tres atributos y un método: SELECT Type_Name, Attributes, Methods

FROM USER_TYPES

WHERE Type_Name = 'ANIMAL_TY';

TYPE_NAME ATTRIBUTES METHODS

-------------------- ------------------ --------------

ANIMAL_TY 3 1

Atributos de tipos de datos: USER_TYPE_ATTRS Para ver los atributos de un tipo de datos podemos consultar la vista USER_TYPE_ATTRS. Las columnas de esta vista son las siguientes:

Type_Name Nombre del tipo.

Attr_Name Nombre del atributo.

Attr_Type_Mod El tipo de modificador del atributo.

Attr_Type_Owner Propietario del tipo del atributo, si el atributo está basado en otro tipo.

Attr_Type_Name Nombre del tipo del atributo.

Length Longitud del atributo.

Precision Precisión del atributo.

Scale Escala del atributo.

Character_Set_Name Juego de caracteres del atributo.

Page 307: Java y Oracle 11g

Oracle /307

Attr_No Posición ordinal del atributo dentro de la definición del tipo.

Inherited Indicador Y/N de si el atributo es heredado de un supertipo.

Podemos consultar USER_TYPE_ATTRS para ver la relación entre los tipos de datos abstractos anidados. Por ejemplo, el tipo de dato PERSONA_TY usa el tipo DIRECCION_TY, tal como se muestra en el siguiente ejemplo: SELECT Attr_Name, Length, Attr_Type_Name

FROM USER_TYPE_ATTRS

WHERE Type_Name = 'PERSONA_TY';

ATTR_NAME LENGTH ATTR_TYPE_NAME

-------------------- ------------ ------------------------

NOMBRE 25 VARCHAR2

DIRECCION DIRECCION_TY

Métodos de tipos de datos: USER_TYPE_METHODS y USER_METHOD_PARAMS Si un tipo tiene métodos definidos, entonces podemos consultar la vista USER_TYPE_METHODS para determinar los nombres de los métodos. La vista USER_TYPE_METHODS contiene columnas para mostrar el nombre del tipo (Type_Name), el nombre del método (Method_Name), el número del método (Method_No, usado para métodos sobrecargados), y el tipo de método (Method_Type). La vista USER_TYPE_METHODS

también incluye columnas para mostrar el número de parámetros (Parameters) y resultados (Results) retornado por el método. Por ejemplo, el tipo ANIMAL_TY tiene un método, una función miembro llamada EDAD. El método EDAD tiene un parámetro de entrada (FechaNacimiento) y uno de salida (la edad, en días). Consultando USER_TYPE_METHODS se muestra que hay dos parámetros definidos en EDAD y no uno: SELECT Parameters, Results

FROM USER_TYPE_METHODS

WHERE Type_Name = 'ANIMAL_TY' AND Method_Name = 'EDAD';

PARAMETERS RESULTS

-------------------- ------------

2 1

¿Por qué EDAD tiene dos parámetros si sólo hay uno definido? Para entenderlos, podemos consultar la vista USER_METHOD_PARAMS, la cual describe los parámetros de nuestros métodos: SELECT Param_Name, Param_No, Param_Type_Name

FROM USER_METHOD_PARAMS

ORDER BY Param_No;

PARAM_NAME PARAM_NO PARAM_TYPE_NAME

-------------------------- ---------------- --------------------------

SELF 1 ANIMAL_TY

FECHANACIMIENTO 2 DATE

Aquí podemos ver que para cada método Oracle crea un parámetro SELF implícito. Los resultados de nuestros métodos son mostrados en USER_METHOD_RESULTS: SELECT Method_Name, Result_Type_Name

FROM USER_METHOD_RESULTS

WHERE Type_Name = 'ANIMAL_TY';

METHOD_NAME RESULT_TYPE_NAME

-------------------- ---------------------------

EDAD NUMBER

Otros tipos de datos: USER_REFS, USER_COLL_TYPES y USER_NESTED_TABLES Si usamos referencias a tipos con REF podemos consultar la vista USER_REFS para mostrar los REF's que hemos definido. La vista USER_REFS muestra el nombre de la tabla que contiene la columna REF (Table_Name) y el nombre de columna del objeto columna (Column_Name). Los atributos del REF —como si están o no almacenados con el ROWID— también son accesibles mediante USER_REFS. Los tipos de colección (tablas anidadas y arrays variables) son descritos mediante la vista USER_COLL_TYPES del diccionario de datos. Las columnas de USER_COLL_TYPES incluyen Type_Name, Upper_Bound (para arrays variables), y la longitud (Length) y precisión (Precision) de los elementos. Podemos usar USER_COLL_TYPES en conjunción con las vistas de los tipos de datos abstractos mostrados previamente para determinar el tipo de

Page 308: Java y Oracle 11g

Oracle /308

estructura de una colección. Podemos también consultar USER_NESTED_TABLES y USER_VARRAYS para ver los detalles de nuestras colecciones. 1.6.2. LOB's: USER_LOBS La vista USER_LOBS proporciona información de los LOB's definidos en nuestras tablas. A continuación se muestra un ejemplo: SELECT Table_Name, Column_Name

FROM USER_LOBS;

TABLE_NAME COLUMN_NAME

------------------- ---------------------

OFERTA TEXTO_OFERTA

OFERTA PRESUPUESTO

USER_LOBS también muestra los nombres de los segmentos usados para contener los datos LOB cuando se agranda; sin embargo, no muestra los tipos de datos de columnas LOB. Para ver los tipos de datos LOB podemos usar el comando DESCRIBE sobre la tabla que incluye los LOB's o consultar la vista USER_TAB_COLUMNS.

1.7. Enlaces de base de datos y vistas materializadas.

Los enlaces de base de datos y vistas materializadas se usan para acceder a datos remotos. Dependiendo de los tipos de vistas materializadas que usemos, podemos habilitar el uso de registros de vistas materializadas. 1.7.1. Enlaces de base de datos: USER_DB_LINKS Para ver los enlaces de base de datos creados bajo nuestra cuenta podemos consultar la vista USER_DB_LINKS. Las columnas de esta vista, incluyendo el nombre del enlace (DB_Link), nombre de usuario para conectar (Username), la contraseña de la cuenta (Password), y la cadena de conexión (Host), muestran la información sobre la conexión remota que el enlace usará para establecerse. Los valores de Username y Password serán usados para registrarse en la base de datos remota definida por el valor de Host. La columna Host almacena el nombre del servicio Oracle Net. Esta columna almacena la cadena de caracteres exacta especificada durante el comando CREATE DATABASE LINK, y no altera su casuística. Por lo tanto, debemos ser cuidadosos al crear enlaces de base de datos y escribir el nombre del servicio.

Nota. Si estamos usando inicios de conexión por defecto a la base de datos remota, la columna Password tendrá el valor NULL.

También se dispone de las vistas ALL_DB_LINKS y DBA_DB_LINKS. Estas vistas tienen una columna Owner en vez de la columna Password. 1.7.2. Vistas materializadas. Podemos consultar USER_MVIEWS para mostrar información sobre las vistas materializadas propiedad de nuestra cuenta. Esta vista muestra la información de estructura de la vista materializada así como su planificación de refresco. Las columnas de esta vista son:

Nombre col. Descripción

Owner La cuenta que es propiedad de la vista materializada.

Mview_Name En nombre de la vista materializada.

Container_Name La tabla base (en la base de datos local) con los datos de la vista materializada.

Query La consulta que define la vista materializada.

Query_Len La longitud de la consulta base de la vista materializada.

Updatable Un indicador de si la instantánea puede ser actualizada.

Update_Log El nombre de la tabla que registra los cambios hechos en instantáneas actualizables.

Master_Rollback_Seg El segmento de rollback usado cuando se pueblan instantáneas y se hacen operaciones de refresco.

Master_Link El enlace de base de datos usado para acceder a la base de datos maestra.

Rewrite_Enabled Un indicado Yes/No de si ha habilitado rescritura de consultas para la vista.

Rewrite_Capability Reglas y restricciones para consultas rescritas.

Refresh_Mode DEMAND, COMMIT o NEVER, dependiendo del modo de refresco de la vista.

Refresh_Method Valores usando para dirigir un refresco rápido de la vista (Complete, Fast, Never, o

Force).

Build_Mode Instanciación IMMEDIATE, DEFERRED o PREBUILT de los datos de la vista materializada durante su creación.

Page 309: Java y Oracle 11g

Oracle /309

Fast_Refreshable Métodos disponibles para refrescos rápidos de la vista materializada.

Last_Refresh_Type El tipo de refresco más reciente usado.

Last_Refresh_Date La fecha y hora de registro de la última vez que los datos fueron refrescados.

Staleness El estado de los datos de la vista materializada relativos a su tabla maestra.

After_Fast_Refresh Estado siguiente a un refresco rápido.

Compile_State Validez de la vista materializada.

Use_No_Index Indicador Yes/No de si la vista materializada fue creada con la cláusula USING NO

INDEX.

Unknown_Trusted_FD Indica si la vista materializada usa restricción de confianza para el refresco.

Stale_Since El tiempo en que la vista materializada se hizo antigua.

El nombre de la vista materializada se encuentra en la columna Mview_Name de USER_MVIEWS. La tabla base local de la vista está en la columna Container_Name. Las nuevas columnas de USER_MVIEWS disponibles desde Oracle Database 10g, incluyen Stale_Since (cuándo la vista materializada se hace vieja). Para determinar qué enlaces de base de datos están siendo usados por vistas materializadas, podemos consultar la columna Master_Link, tal se muestra en el siguiente ejemplo: SELECT Master_Link

FROM USER_MVIEWS;

Los nombres de los enlaces de base de datos retornados por esta consulta pueden ser usados como entradas para consultas a la vista USER_DB_LINKS. Esta consulta mostrará toda la información disponible de todos los enlaces de base de datos usados en nuestras vistas materializadas: SELECT *

FROM USER_DB_LINKS

WHERE DB_Link IN (SELECT Master_Link FROM USER_MVIEWS);

Las vistas ALL_MVIEWS y DBA_MVIEWS tienen las mismas definiciones de columnas que USER_MVIEWS. Dos vistas relacionadas —USER_REFRESH y USER_REFRESH_CHILDREN— muestran información sobre grupos de refrescos. USER_MVIEW_REFRESH_TIMES muestra la última vez que las vistas materializadas fueron refrescadas. Desde Oracle Database 10g, podemos acceder a los comentarios de las vistas materializadas mediante la vista USER_MVIEW_COMMENTS, y consultar detalles de rescritura mediante USER_REWRITE_EQUIVALENCES. Capacidades adicionales de vistas materializadas. Podemos consultar la vista USER_MVIEW_ANALYSIS para ver las vistas materializadas que soportan rescritura de consulta. Si una vista materializada contiene referencias a tablas remotas, éstas no serán mostradas en esta vista. Podemos consultar el propietario de la vista materializada (Owner), su nombre (Mview_Name) y el propietario de la tabla base (Mview_Table_Owner). Muchas de las columnas de esta vista son indicadores, como Summary ('Y' si la vista contiene una agregación), Known_Stale ('Y' si los datos de la vista son inconsistente con la tabla base), y Contains_Views ('Y' si la vista materializada referencia una vista). Si la vista materializada contiene agregaciones, podemos consultar USER_MVIEW_AGGREGATES para ver los detalles de la agregación. Las columnas de esta vista son las siguientes:

Owner Propietario de la vista materializada.

Mview_Name Nombre de la vista materializada.

Position_in_Select Posición dentro de la consulta.

Container_Column Nombre de la columna.

Agg_Function Función de agregado.

DistinctFlag 'Y' si la agregación usa la función DISTINCT.

Measure El texto SQL de la medida, excluyendo la función agregada

Podemos consultar los detalles de las relaciones dentro de las vistas materializadas usando las vistas USER_MVIEW_DETAIL_RELATIONS y USER_MVIEW_KEYS. Si la vista materializada está basada en JOIN's, podemos consultar USER_MVIEW_JOINS para los detalles del JOIN. En general, USER_MVIEW_ANALYSIS se usará más habitualmente para consultar datos sobre las vistas materializadas. 1.7.3. Registros de vistas materializadas: USER_MVIEW_LOGS Los registros de vistas materializadas pueden ser usados por muchas vistas materializadas para determinar qué registros en la tabla principal necesitan ser refrescados en la vista materializada de esta tabla. Podemos consultar USER_MVIEW_LOGS para obtener información sobre los registros de un usuario, incluyendo el nombre de la tabla principal (Master), la tabla que contiene los registros (Log_Table), y si la vista materializada

Page 310: Java y Oracle 11g

Oracle /310

está basada sobre la clave primaria o sobre el ROWID (las columnas Primary_Key y ROWID). La vista USER_MVIEW_LOGS es normalmente consultada para propósitos de mantenimiento, como para determinar el nombre del trigger usado para crear los registros de la vista materializada. La vista DBA_MVIEW_LOGS tiene las mismas columnas que USER_MVIEW_LOGS. Podemos consultar la vista USER_BASE_TABLE_MVIEWS para obtener las tablas primarias de la vista materializada que usan registros de vista materializada.

1.8. Triggers, procedimientos, funciones y paquetes.

Podemos usar procedimientos, paquetes y triggers para forzar reglas del negocio o para realizar procesos complejos. 1.8.1. Triggers: USER_TRIGGERS La vista USER_TRIGGERS contiene información sobre los trigger propiedad de nuestra cuenta. Esta vista muestra el tipo de trigger y su cuerpo. Las columnas de esta vista son:

Nombre col. Descripción

Trigger_Name Nombre del trigger.

Trigger_Type El tipo de trigger (BEFORE STATEMENT, BEFORE EACH ROW, y demás).

Triggering_Event El comando que ejecuta el trigger (INSERT, UPDATE, o DELETE).

Table_Owner El propietario de la tabla para el cual se define el trigger.

Base_Object_Type El tipo de objeto sobre el que está basado el trigger (TABLE, VIEW, SCHEMA, o

DATABASE).

Table_Name El nombre de la tabla o vista para el cual está definido el trigger.

Column_Name Para triggers de tablas anidadas, el nombre de la columna de la tabla anidada.

Referencing_Names Nombres usados para referenciar los valores OLD y NEW en el trigger.

When_Clause La cláusula WHEN usada por el trigger.

Status Si es trigger está activado (ENABLED) o desactivado (DISABLED).

Description La descripción del trigger.

Action_Type Tipo de acción del cuerpo del trigger (CALL o PL/SQL).

Trigger_Body El texto del trigger.

Las vistas ALL_TRIGGERS y DBA_TRIGGERS añaden la columna Owner para indicar el propietario del trigger. Otra vista relacionada con los triggers es USER_TRIGGER_COLS, las cual muestra cómo las columnas son usadas por el trigger. Lista el nombre de cada columna afectada por un trigger, así cómo el trigger la usa. También existen las versiones ALL_TRIGGER_COLS y DBA_TRIGGER_COLS. 1.8.2. Procedimientos, funciones y paquetes: USER_SOURCE El código fuente de procedimientos, funciones, paquetes y cuerpos de paquetes puede ser consultado usando la vista USER_SOURCE. La columnas Type de USER_SOURCE identifica el objeto procedimental, y puede tomar los valores: 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'TRIGGER','TYPE', 'TYPE BODY' o 'JAVA

SOURCE'. Cada línea de código es almacenada en un registro independiente de USER_SOURCE. Podemos seleccionar información de USER_SOURCE mediante una consulta similar a la mostrada a continuación. En este ejemplo, se selecciona la columna Text y se ordena por el número de la columna Line. El nombre y tipo del objeto se usan para especificar qué código será mostrado: SELECT Text

FROM USER_SOURCE

WHERE Name = '&procedure_name' AND Type = 'PROCEDURE'

ORDER BY Line;

Nota. La secuencia de las líneas es mantenida por la columna Line; por lo tanto, esta columna debe ser usada para ordenar la consulta.

Las vistas ALL_SOURCE y DBA_SOURCE añaden la columna adicional Owner para indicar el propietario del objeto.

Nota. Para ver los valores de los parámetros persistentes para unidades PL/SQL hay que consultar la vista USER_STORED_SETTINGS.

Códigos de error: USER_ERRORS El comando SHOW ERRORS de SQL*Plus verifica la vista del diccionario de datos USER_ERRORS para mostrar los errores asociados con la última compilación de un objeto procedimental. SHOW ERRORS mostrará la línea

Page 311: Java y Oracle 11g

Oracle /311

y número de columna de cada error, así como el texto del mensaje de error. Para ver los errores asociados con los objetos procedimentales creados previamente, podemos consultar USER_ERRORS directamente. Podemos necesitar hacer esto cuando miramos los errores asociados con cuerpos de paquetes, donde la compilación del paquete resultó en un error no mostrado por el comando SHOW ERROR. Podemos también necesitar consultar USER_ERRORS cuando encontremos errores de compilación con varios objetos procedimentales. Las siguientes son las columnas disponibles en esta vista:

Nombre col. Descripción

Name El nombre del objeto procedimental.

Type El tipo de objeto ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY',

'TRIGGER', 'TYPE', 'TYPE BODY', 'VIEW', 'JAVA CLASS', o 'JAVA SOURCE').

Sequence El número de línea de secuencia, para usar en la cláusula ORDER BY.

Line El número de línea dentro del código fuente en el que ocurrió el error.

Position La posición dentro de la línea en la que ocurrió el error.

Text El texto del mensaje de error.

Attribute Indicador de si la fila seleccionada es un error ('ERROR') o un aviso ('WARNING').

Message_Number Número de error, sin prefijo.

Consultas sucesivas a esta vista deberían siempre incluir la columna Sequence en la cláusula ORDER BY. Las vistas ALL_ERRORS y DBA_ERRORS añaden la columna adicional Owner. Tamaño del código: USER_OBJECT_SIZE Podemos consultar la cantidad de espacio usado en el tablespace SYSTEM por un objeto procedimental mediante la vista USER_OBJECT_SIZE. Tal como se muestra en el siguiente listado, podemos acumular el tamaño de las cuatro áreas separadas para determinar el espacio total usado en las tablas del diccionario de datos de SYSTEM para almacenar los objetos. Las cuatro columnas de tamaño (_Size), seguidas de las columnas Name y Type, constituyen todas las columnas de esta vista. SELECT Source_Size+Code_Size+Parsed_Size+Error_Size AS Total

FROM USER_OBJECT_SIZE

WHERE Name = '&procedure_name' AND Type = 'PROCEDURE';

También existe la vista DBA_OBJECT_SIZE.

1.9. Dimensiones.

Podemos crear y mantener dimensiones y jerarquías. Podemos consultar la columna Dimension_Name de la vista USER_DIMENSIONS para mostrar los nombres de nuestras dimensiones. USER_DIMENSIONS también contiene columnas para el propietario de la dimensión (Owner), estado (columna Invalid, asignada a 'Y' o 'N'), y nivel de revisión (columna Revision). Los atributos de una dimensión son accedidos mediante vistas adicionales del diccionario de datos. Para ver las jerarquías dentro de una dimensión podemos consultar la vista USER_DIM_HIERARCHIES. Esta vista tiene sólo tres columnas: Owner, Dimension_Name, y Hierarchy_Name. Consultando USER_DIM_HIERARCHIES para la dimensión GEOGRAFIA obtendremos el nombre de sus jerarquías: SELECT Hierarchy_Name

FROM USER_DIM_HIERARCHIES

WHERE Dimension_Name=''GEOGRAFIA';

HIERARCHY_NAME

-------------------------

PAISES_ROLLUP

Podemos ver los detalles de la jerarquía para PAISES_ROLLUP consultando la vista USER_DIM_CHILD_OF, tal como se muestra a continuación. El primero comando crea una dimensión llamada GEOGRAFIA que registra las jerarquías de países y continentes. CREATE DIMENSION GEOGRAFIA

LEVEL PAIS_ID IS PAIS.Pais

LEVEL CONTINENT_ID IS CONTINENTE.Continente

HIERARCHY PAISES_ROLLUP (

PAIS_ID CHILD OF CONTINENTE_ID JOIN KEY PAIS.Continente REFERENCES CONTINENTE_ID

);

Page 312: Java y Oracle 11g

Oracle /312

SELECT Child_Level_Name, Parent_Level_Name, Position, Join_Key_Id

FROM USER_DIM_CHILD_OF

WHERE Hierarchy_Name = 'PAISES_ROLLUP';

CHILD_LEVEL_NAME PARENT_LEVEL_NAME POSITION JOIN

-------------------------- ---------------------------- -------------- --------

PAIS_ID CONTINENTE_ID 1 1

Podemos consultar la vista USER_DIM_JOIN_KEY para ver la clave de join de una jerarquía: SELECT Level_name, Child_Join_Column

FROM USER_DIM_JOIN_KEY

WHERE Dimension_Name = 'GEOGRAFIA' AND Hierarchy_Name = 'PAISES_ROLLUP';

LEVEL_NAME CHILD_JOIN_COLUMN

----------------------- ----------------------------

CONTINENTE_ID CONTINENTE

Podemos ver los niveles de una dimensión consultando la vista USER_DIM_LEVELS, y ver las columnas claves mediante USER_DIM_LEVEL_KEY. La información de atributos para dimensiones es accesible mediante la vista USER_DIM_ATTRIBUTES. Hay versiones ALL_ y DBA_ de las vistas relacionadas con las dimensiones.

1.10. Asignación y uso de espacio, incluyendo particiones y subparticiones.

Podemos consultar el diccionario de datos para determinar el espacio que está disponible y asignado para los objetos de base de datos. 1.10.1. Tablespaces: USER_TABLESPACES Podemos consultar la vista USER_TABLESPACES para determinar sobre qué tablespaces tenemos permisos de acceso y los parámetros de almacenamiento por defecto de cada uno. Los parámetros de almacenamiento por defecto del tablespace serán usados para cada objeto almacenado dentro del tablespace a menos que el comando CREATE o ALTER para estos objetos especifique sus propios parámetros de almacenamiento. Las columnas relativas al espacio de USER_TABLESPACES, son muy similares a las columnas relativas al espacio de USER_TABLES. No hay versión ALL_ de esta vista. DBA_TABLESPACES muestra los parámetros de almacenamiento de todos los tablespaces.

Nombre col. Descripción

Tablespace_Name Nombre del tablespace.

Block_Size El tamaño de bloque en uso por este tablespace.

Initial_Extent El parámetro INITIAL por defecto para objetos del tablespace.

Next_Extent El parámetro NEXT por defecto para objetos del tablespace.

Min_Extents El parámetro MINEXTENTS por defecto para objetos del tablespace.

Max_Extents El parámetro MAXEXTENTS por defecto para objetos del tablespace.

Pct_Increase El parámetro PCTINCREASE por defecto para objetos del tablespace.

Min_Extlen El tamaño mínimo de extensión para los objetos del tablespace.

Status El estado del tablespace ('ONLINE', 'OFFLINE', 'INVALID', 'READ ONLY'). Un tablespace inválido es uno que ha sido borrado; sus registros están todavía visibles a través de esta vista.

Contents Un indicador de si el tablespace es usado para almacenar objetos permanentes

('PERMANENT') o solo segmentos temporales ('TEMPORARY').

Logging Indicador del valor por defecto del parámetro LOGGING/NOLOGGING para objetos del tablespace.

Extent_Management Dónde se realiza la administración de las extensiones en el tablespace

('DICTIONARY' o 'LOCAL').

Allocation_ Type Tipo de asignación de extensión efectiva.

Segment_Space_Management Indicador de si el espacio libro es controlado mediante listas libres

('MANUAL') o mapas de bits ('AUTO').

Retention Retención del tablespace de deshacer ('GUARANTEE', 'NO GUARANTEE', o

'NOT APPLY').

Bigfile Indica si el tablespace es un tablespace de ficheros grandes ('YES') o de

Page 313: Java y Oracle 11g

Oracle /313

ficheros pequeños ('NO').

Force_Logging Indicador Yes/No de si el tablespace está en modo logging.

1.10.2. Cuotas de espacio: USER_TS_QUOTAS La vista USER_TS_QUOTAS es muy útil para determinar la cantidad de espacio que tenemos actualmente asignada y la máxima cantidad de espacio disponible para nuestros tablespaces. Una consulta sencilla de USER_TS_QUOTAS se muestra a continuación: SELECT * FROM USER_TS_QUOTAS;

TABLESPACE_NAME BYTES MAX_BYTES BLOCKS MAX_BLOCKS

-------------------------- ---------- ---------------- ---------- ------------------

USERS 67584 0 33 0

USER_TS_QUOTAS contiene un registro por cada nombre de tabla (Tablespace_Name). La columna Bytes

refleja el número de bytes asignados para objetos propiedad del usuario. La columna Max_Bytes es el máximo número de bytes que el usuario puede poseer en este tablespace; si no hay cuota para este tablespaces, entonces Max_Bytes mostrará el valor 0. Las columnas Bytes y Max_Bytes son trasladadas dentro de bloques de Oracle en las columnas Blocks y Max_Blocks, respectivamente. No hay versión ALL_ para esta vista. La vista DBA_TS_QUOTAS muestra las cuotas para todos los usuarios y tablespaces, y es el modo más efectivo de mostrar el uso de espacio dentro de toda la base de datos. 1.10.3. Segmentos y extensiones: USER_SEGMENTS y USER_EXTENTS El espacio se asigna a los objetos (como tablas, clústeres e índices) en segmentos, las contrapartidas físicas de los objetos lógicas creados en la base de datos. Podemos consultar la vista USER_SEGMENTS para ver los parámetros de almacenamiento actuales y el espacio usado en nuestros segmentos. USER_SEGMENTS es muy útil cuando estamos en peligro de exceder uno de los límites de almacenamiento. Las columnas de esta vista son las siguientes:

Nombre col. Descripción

Segment_Name El nombre del segmento.

Partition_Name NULL si el objeto no esta particionado; sino, el nombre de la partición del segmento.

Segment_Type El tipo de segmento ('TABLE', 'CLUSTER', 'INDEX', 'ROLLBACK', y demás).

Tablespace_Name El nombre del tablespace en el cual el segmento es almacenado.

Bytes El número de bytes asignados al segmento.

Blocks El número de bloques Oracle asignados al segmento.

Extents El número de extensiones en el segmento.

Initial_Extent El tamaño de la extensión inicial del segmento.

Next_Extent El valor del parámetro NEXT para el segmento.

Min_Extents El número mínimo de extensiones en el segmento.

Max_Extents El valor del parámetro MAXEXTENTS para el segmento.

Pct_Increase El valor del parámetro PCTINCREASE para el segmento.

Freelists El número de procesos freelist (lista de bloques de datos en el segmento que pueden usarse durante inserciones) asignados al segmento; si un segmento tiene varios freelists, entonces la contención para los bloques libres durante inserciones actuales disminuirá.

Freelist_Groups El número de grupos freelist asignados al segmento.

Buffer_Pool Búfer interno en el cual el segmento será leído ('DEFAULT', 'KEEP', o 'RECYCLE') si hemos definido varios grupos de búferes.

Los segmentos consisten de secciones contiguas llamadas extensiones. Las extensiones que constituyen segmentos son descritas en la vista USER_EXTENTS. En USER_EXTENTS podremos ver el tamaño actual de cada extensión dentro del segmento; esto es útil para registrar el impacto de cambios en las opciones NEXT y PCTINCREASE. Además de las columnas Segment_Name, Segment_Type y Tablespace_Name, la vista USER_EXTENTS tiene tres nuevas columnas: Extent_ID (para identificar la extensión dentro del segmento), Bytes (el tamaño de la extensión, en bytes), and Blocks (el tamaño de la extensión, en bloques Oracle). Tanto USER_SEGMENTS como USER_EXTENTS tienen versiones DBA_, que añaden la columna adicional Owner. Si queremos mostrar todos los propietarios que poseen segmentos en un tablespace, podemos consultar asignando la columna Tablespace_Name en DBA_SEGMENTS. No hay versión ALL_ de estas vistas. 1.10.4. Particiones y subparticiones. Un simple dato de tabla puede ser almacenado a través de varias particiones. Para ver cómo una tabla está particionada debemos consultar la vista USER_PART_TABLES, cuyas columnas se describen en la siguiente

Page 314: Java y Oracle 11g

Oracle /314

tabla.

Columnas de identificación

Table_Name

Partitioning_Type

Subpartitioning_Type

Partition_Count

Def_Subpartition_Count

Partitioning_Key_Count

Subpartitioning_Key_Count

Def_Logging

Def_Buffer_Pool

Columnas relativas a almacenamiento

Def_Tablespace_Name

Def_Pct_Free

Def_Pct_Used

Def_Ini_Trans

Def_Max_Trans

Def_Initial_Extent

Def_Next_Extent

Def_Min_Extents

Def_Max_Extents

Def_Pct_Increase

Def_Freelists

Def_Freelist_Groups

Def_Compression

La mayoría de columnas de USER_PART_TABLES definen los parámetros de almacenamiento por defecto para las particiones de la tabla. Cuando una partición es añadida a la tabla, por defecto usará los parámetros de almacenamiento mostrados en USER_PART_TABLES. La vista USER_PART_TABLES también muestra el número de particiones de la tabla (Partition_Count), el número de columnas en la clave de partición (Partitioning_Key_Count), y el tipo de partición (Partitioning_Type). USER_PART_TABLES almacena una fila por cada tabla que ha sido particionada. Para ver información sobre cada partición individual que pertenece a la tabla podemos consultar la vista USER_TAB_PARTITIONS. En USER_TAB_PARTITIONS veremos una fila por cada partición de una tabla. Las columnas de USER_TAB_PARTITIONS se muestran en la siguiente tabla.

Columnas de identificación

Table_Name

Composite

Partition_Name

Subpartition_Count

High_Value

High_Value_Length

Partition_Position

Logging

Buffer_Pool

Columnas relativas a almacenamiento

Tablespace_Name

Pct_Free

Pct_Used

Ini_Trans

Max_Trans

Initial_Extent

Next_Extent

Min_Extent

Max_Extent

Pct_Increase

Page 315: Java y Oracle 11g

Oracle /315

Freelists

Freelist_Groups

Compression

Columnas relativas a estadísticas

Num_Rows

Blocks

Empty_Blocks

Avg_Space

Chain_Cnt

Avg_Row_Len

Sample_Size

Last_Analyzed

Global_Stats

User_Stats

La vista USER_TAB_PARTITIONS contiene columnas que identifican la tabla a la cual pertenecen las particiones y muestra los parámetros de almacenamiento de la partición y las estadísticas para la partición. Las columnas relacionadas con estadísticas son pobladas cuando la tabla es analizada. Las columnas de identificación muestran los valores mayores para el rango usado para definir la partición (High_Value)y la posición de la partición dentro de la tabla (Partition_Position). Las columnas usadas por la clave de partición son accesibles mediante la vista USER_PART_KEY_COLUMNS. Esta vista contiene sólo cuatro columnas.

Name El nombre de la tabla o índice particionado

Object_Type El tipo de objeto (TABLE o INDEX).

Column_Name El nombre de la columna que es parte de la clave de partición.

Column_Position La posición de la columna dentro de la clave de partición.

Las estadísticas para las columnas de partición son accesibles mediante la vista USER_PART_COL_STATISTICS. Las columnas en USER_PART_COL_STATISTICS reflejan las correspondientes en USER_TAB_COL_STATISTICS. La información de histogramas de datos para particiones es accesible mediante la vista USER_PART_HISTOGRAMS. Las columnas de esta vista muestran los valores de punto final para cada uno de los cubos del histograma. Las columnas son Table_Name, Partition_Name, Column_Name, Bucket_Number, Endpoint_Value y Endpoint_Actual_Value. Ya que los índices pueden ser particionados, hay una vista USER_IND_PARTITIONS. Las columnas en USER_IND_PARTITIONS pueden agruparse en tres categorías, tal como se muestra en la siguiente tabla.

Columnas de identificación

Index_Name

Composite

Partition_Name

Subpartition_Count

High_Value

High_Value_Length

Partition_Position

Status

Logging

Buffer_Pool

Compression

Domidx_Opstatus

Parameters

Columnas relativas a espacio

Tablespace_Name

Ini_Trans

Max_Trans

Initial_Extent

Next_Extent

Min_Extent

Page 316: Java y Oracle 11g

Oracle /316

Max_Extent

Pct_Increase

Pct_Free

Freelists

Freelist_Groups

Columnas relativas a estadísticas

Blevel

Leaf_Blocks

Distinct_Keys

Avg_Leaf_Blocks_Per_Key

Avg_Data_Blocks_Per_Key

Clustering_Factor

Num_Rows

Sample_Size

Last_Analyzed

User_Stats

Pct_Direct_Access

Global_Stats

Las columnas en USER_IND_PARTITIONS son paralelas a las de USER_INDEXES, con unas pocas modificaciones en las columnas de identificación. Las columnas de identificación para índices particionados incluyen el nombre de la partición, el mayor valor para la partición, y la posición de la partición dentro de la tabla. Las columnas relativas a las estadísticas son pobladas cuando la partición es analizada. Las columnas relativas al espacio describen la asignación de espacio para el índice. Si una partición tiene subparticiones, podemos ver los detalles de las subparticiones mediante varias vistas del diccionario de datos. La vista USER_IND_SUBPARTITIONS contiene las mismas columnas relativas al espacio y a las estadísticas que USER_IND_PARTITIONS, incluyendo columnas para identificar la subpartición (Subpartition_Name y Subpartition_Position). Similarmente, la vista USER_TAB_SUBPARTITIONS contiene las mismas columnas relativas al espacio y las estadísticas que USER_TAB_PARTITIONS, además de las columnas Subpartition_Name y Subpartition_Position. Por tanto, podemos determinar las definiciones de espacio para cada partición y subpartición. Como hemos visto, podemos consultar las vistas USER_PART_COL_STATISTICS y USER_PART_HISTOGRAMS

por información de estadísticas independientemente de las particiones. Para subparticiones, podemos consultar USER_SUBPART_COL_STATISTICS y USER_SUBPART_HISTOGRAMS, cuyas estructuras reflejan las vistas de estadísticas de partición. Para ver las columnas claves de subparticiones podemos consultar USER_SUBPART_KEY_COLUMNS, cuya estructura de columnas es idéntica a la de USER_PART_KEY_COLUMNS. 1.10.5. Espacio libre: USER_FREE_SPACE Además de ver el espacio que hemos usado, podemos también consultar el diccionario de datos para ver cuánto espacio esta marcado actualmente como espacio libre. La vista USER_FREE_SPACE muestra las extensiones libres en todos los tablespaces accesibles por el usuario. Esta vista cataloga por el nombre de tablespace (Tablespace_Name) el ID de fichero (File_ID), el ID de bloque (Block_ID), y el número de archivo relativo al punto de partida de la extensión libre. El tamaño de la extensión libre es mostrado tanto en bytes como en bloques. La vista DBA_FREE_SPACE es usado con frecuencia por DBA's para supervisar la cantidad de espacio libre disponible y el grado en el cual está fragmentado.

1.11. Usuarios y permisos.

Los usuarios y sus permisos son registrados dentro del diccionario de datos. 1.11.1. Usuarios: USER_USERS Podemos consultar la vista USER_USERS para mostrar información sobre nuestra cuenta. La vista USER_USERS incluye nuestro nombre de usuario (Username), un ID asignado por la base de datos (User_ID), el tablespace por defecto (Default_Tablespace), el tablespace temporal (Temporary_Tablespace), y la fecha de creación de la cuenta (Created). La columna Account_Status de USER_USERS muestra el estado de nuestra cuenta, si está bloqueada, desbloqueada ('OPEN'), o ha expirado. Si una cuenta está bloqueada, la columna Lock_Date mostrará la fecha en que fue la cuenta bloqueada y la columna Expiry_Date mostrará la fecha de expiración. Podemos también consultar información sobre los grupos de recursos consumidos

Page 317: Java y Oracle 11g

Oracle /317

(Initial_Rsrc_Consumer_Group) asignados a nosotros por el DBA, y nuestro nombre externo (External_Name). La vista ALL_USERS contiene sólo el nombre de usuario (Username), identificador (User_ID) y fecha de creación (Created).ALL_USERS es útil cuando necesitamos saber los nombres de usuarios que están disponibles (por ejemplo, durante comandos GRANT). La vista DBA_USERS contiene las mismas columnas que USER_USERS, más dos columnas adicionales: Password (la contraseña cifrada de la cuenta) y Profile (el perfil de recurso del usuario). 1.11.2. Límites de los recursos: USER_RESOURCE_LIMITS En Oracle, se pueden usar perfiles para poner límites a la cantidad de recursos disponibles del sistema y base de datos para un usuario. Si no son creados perfiles en la base de datos, el perfil por defecto especificará recursos ilimitados para todos los usuarios. Los perfiles fuerzan medidas de seguridad adicionales, como fechas de expiración sobre cuentas y la longitud mínima de contraseñas. Para ver los límites que están puestos en nuestra sesión actual podemos consultar la vista USER_RESOURCE_LIMITS. Sus columnas son las siguientes:

Resource_Name El nombre del recurso (por ejemplo, SESSIONS_PER_USER).

Limit El límite puesto sobre el recurso.

La vista USER_PASSWORD_LIMITS describe los parámetros de perfil de contraseña para el usuario. Tiene las mismas columnas que USER_RESOURCE_LIMITS. No hay versiones ALL_ o DBA_ de esta vista. Para ver el coste asociado con cada recurso disponible podemos consultar la vista RESOURCE_COST. Los DBA's pueden acceder a la vista DBA_PROFILES para ver los límites de recursos para todos los perfiles. La columna Resource_Type de DBA_PROFILES indica si el perfil de recurso es un perfil 'PASSWORD' o 'KERNEL'. 1.11.3. Permisos de tabla: USER_TAB_PRIVS Podemos usar la vista USER_TAB_PRIVS(Permisos de tabla de usuario) para ver los permisos que nos fueron concedidos, el otorgador del permiso y el propietario del objeto. Además de sus columnas Grantee, Grantor y Owner, esta vista contiene columnas Table_Name, Hierarchy, Privilege, y un indicador (asignado a 'YES' o 'NO') para ver si el permiso fue concedido con WITH ADMIN OPTION (Grantable). La vista USER_TAB_PRIVS_MADE muestra los registros de USER_TAB_PRIVS para los cuales el usuario es el propietario (y por lo tanto carece de una columna Owner). La vista USER_TAB_PRIVS_RECD (Permisos recibidos de tabla de usuario) muestra los registros de USER_TAB_PRIVS con los permisos concedidos al usuario (y por tanto carece de una columna Grantee). Por tanto, tanto USER_TAB_PRIVS_MADE como USER_TAB_PRIVS_RECD son subconjuntos de USER_TAB_PRIVS. Hay versiones ALL_ para las vistas USER_TAB_PRIVS, USER_TAB_PRIVS_MADE y USER_TAB_PRIVS_RECD. Las versiones ALL_ catalogan aquellos objetos para los cuales el usuario o PUBLIC es el cesionario o el otorgador. Hay una versión DBA_ de USER_TAB_PRIVS llamada DBA_TAB_PRIVS. Permisos de columna: USER_COL_PRIVS Además de conceder permisos sobre tabla, podemos también conceder permisos al nivel de columnas. Las vistas del diccionario de datos usada para mostrar los permisos de columnas son la mayoría idénticos en diseña a las vistas de permisos de tablas. Hay una columna adicional Column_Name en cada vista COL_, y la columna Hierarchy se ha suprimido. La vista USER_COL_PRIVS es análoga a USER_TAB_PRIVS, la vista USER_COL_PRIVS_MADE es análoga a USER_TAB_PRIVS_MADE, y la vista USER_COL_PRIVS_RECD es análoga a USER_TAB_PRIVS_RECD. Versiones ALL_ también están disponibles para todas estas vistas. Y DBA_COL_PRIVS muestra todos los permisos de columna concedidos a los usuarios de la base de datos. 1.11.4. Permisos del sistema: USER_SYS_PRIVS La vista USER_SYS_PRIVS muestra los permisos del sistema que han sido concedidos al usuario. Sus columnas son Username, Privilege, y Admin_Option (un indicador 'YES' o 'NO' que dice si el permiso fue concedido con WITH ADMIN OPTION). Todos los permisos de sistema concedidos directamente a un usuario son mostrados mediante esta vista. Los permisos de sistema concedidos a un usuario mediante un rol no son mostrados aquí. La siguiente consulta muestra la salida para la cuenta EMPLEADO: SELECT Username, Privilege * FROM USER_SYS_PRIVS;

USERNAME PRIVILEGE

---------------- ----------------------------------

EMPLEADO CREATE DIMENSION

EMPLEADO UNLIMITED TABLESPACE

Page 318: Java y Oracle 11g

Oracle /318

No hay versión ALL_ para esta vista. La vista DBA_SYS_PRIVS permite consultar todos los permisos de sistema concedidos a todos los usuarios de la base de datos.

1.12. Roles.

Además de los permiso concedidos directamente a los usuarios, podemos agrupar conjuntos de permisos dentro de roles. Los roles pueden ser concedidos a los usuarios o a otros roles, y pueden consistir de ambos objetos y permisos de sistema. Para ver qué roles nos han sido concedidos debemos consultar la vista USER_ROLE_PRIVS. Cualquier rol que hay sido concedido como PUBLIC será mostrado aquí. Las columnas de esta vista son:

Username El nombre de usuario (puede ser 'PUBLIC').

Granted_Role El nombre del rol concedido al usuario.

Admin_Option Un indicador de si el rol fue concedido con WITH ADMIN OPTION.

Default_Role Un indicador de si el rol es un rol por defecto de usuario.

OS_Granted Un indicador de si el sistema operativo está siendo usado para gestionar roles.

Para mostrar todos los roles disponibles en la base de datos, necesitamos tener autoridad de DBA; entonces podemos usar la vista DBA_ROLES para consultar todos los roles. La vista DBA_ROLE_PRIVS muestra todas las asignaciones de estos roles a todos los usuarios de la base de datos. Los roles pueden recibir tres tipos diferentes de concesiones, cada una de las cuales se corresponde con una vista del diccionario de datos:

De tabla/columna ROLE_TAB_PRIVS. Similar a USER_TAB_PRIVS y USER_COL_PRIVS, excepto que

tienen una columna Role en vez de la columna Grantee, y no tiene la columna Grantor. Permisos de sistema ROLE_SYS_PRIVS. Similar a USER_SYS_PRIVS, excepto que tiene una columna Role

en vez de la columna Username. De rol ROLE_ROLE_PRIVS. Muestra todos los roles que han sido concedidos a otros roles.

Nota. Si no somos DBA, estas vistas del diccionario de datos muestran solo los roles que nos han sido concedidos.

Además de estas vistas, hay dos vistas, cada una con una única columna, que muestran los permisos y roles habilitados para la sesión actual:

SESSION_PRIVS La columna Privilege muestra todos los permisos de sistema habilitados para la sesión, tanto concedidos directamente o a través de roles.

SESSION_ROLES La columna Role muestra todos los roles de la sesión actual.

Las vistas SESSION_PRIVS y SESSION_ROLES están disponibles para todos los usuarios.

1.13. Auditoría.

Si no se es un usuario DBA dentro de una base de datos de Oracle no se pueden habilitar las características de auditoría de la base de datos. Si la auditoría ha sido habilitada hay varias vistas del diccionario de datos que alguien puede usar para ver los resultados de auditoría. Hay muchos resultados de auditoría que podemos ver mediante vistas del diccionario de datos. La mayoría de estas vistas están basadas sobre una única tabla de resultados de auditoría llamada SYS.AUD$. La más genérica de las vistas de resultados de auditoría se denomina USER_AUDIT_TRAIL. Sus columnas se describen a continuación.

Nombre col. Descripción

OS_Username La cuenta del sistema operativo del usuario.

Username El nombre de usuario Oracle del usuario auditado.

UserHost Un ID numérico de la instancia usada por el usuario auditado.

Terminal El identificador de terminal del sistema operativo del usuario.

TimeStamp La fecha y hora en que fue creado el registro auditado.

Owner El propietario del objeto afectado por una acción (para acciones de auditoría).

Obj_Name El nombre del objeto afectado por una acción (para acciones de auditoría).

Action El código numérico para la acción de auditoría.

Action_Name El nombre de la acción de auditoría.

New_Owner El propietario del objeto nombrado en la columna New_Name.

New_Name El nuevo nombre de un objeto que ha sido renombrado.

Page 319: Java y Oracle 11g

Oracle /319

Obj_Privilege El permiso de objeto que ha sido concedido o revocado.

Sys_Privilege El permiso de sistema que ha sido concedido o revocado.

Admin_Option Un indicador Y/N de si el rol o permiso de sistema fue concedido con WITH ADMIN

OPTION.

Grantee El nombre de usuario especificado en un comando GRANT o REVOKE.

Audit_Option Las opciones de auditoría asignadas mediante el comando AUDIT.

Ses_Actions Una cadena de caracteres que sirve como resumen de sesión, registrando sucesos y fallos de diferentes acciones.

Logoff_Time La fecha y hora de la salida de sesión del usuario.

Logoff_LRead El número de lecturas lógicas realizadas durante la sesión.

Logoff_PRead El número de lecturas físicas realizadas durante la sesión.

Logoff_LWrite El número de escrituras lógicas realizadas durante la sesión.

Logoff_DLock El número de bloqueos detectados durante la sesión.

Comment_Text Un comentario de texto sobre la entrada de seguimiento de auditoría.

SessionID El ID numérico de la sesión.

EntryID Un ID numérico de la entrada de seguimiento de auditoría.

StatementID El ID numérico para cada comando que fue ejecutado.

ReturnCode El código de retorno de cada comando que fue ejecutado; si el comando sucedió sin fallos, el código de retorno será cero.

Priv_Used El permiso de sistema usado para ejecutar la acción.

Client_ID El ID cliente.

Session_CPU La sesión de CPU usada.

Extended_Timestamp Fecha y hora de la creación de la entrada se seguimiento de auditoría en la zona horaria de la sesión.

Proxy_SessionID Número de serie del proxy de sesión, si el usuario empresarial ha iniciado sesión mediante un mecanismo de proxy.

Global_UID Identificador global del usuario, si el usuario ha iniciado sesión como un usuario empresarial.

Instance_Number Número de instancia especificado en el fichero de inicialización de parámetros.

OS_Process Identificador del proceso del sistema operativo del proceso servidor de Oracle.

TransactionID Identificador de transacción de la transacción en la cual el objeto es accedido o modificado.

SCN SCN de la consulta.

SQL_Bind Variable de enlace de la consulta.

SQL_Text Texto de la consulta.

Aunque esta vista muestra registros de auditoría para muchos tipos diferentes de acciones, muchas de las columnas pueden ser inaplicables para alguna de las filas. La versión DBA_ de esta vista, DBA_AUDIT_TRAIL, muestra todas las entradas de la tabla de seguimiento de auditorías; USER_AUDIT_TRAIL muestra sólo aquellas relevantes para el usuario. Desde Oracle Database 10g, hay nuevas columnas en USER_AUDIT_TRAIL, incluyendo SQL_Text, SQL_Bind, OS_Process, y SCN. Cada tipo de auditoría puede ser accedido mediante su propia vista del diccionario de datos. Las siguientes son las vistas disponibles:

USER_AUDIT_OBJECT Para comandos concernientes a objetos.

USER_AUDIT_SESSION Para conexiones y desconexiones.

USER_AUDIT_STATEMENT Para comandos GRANT, REVOKE, AUDIT, NOAUDIT, y ALTER SYSTEM realizados por el usuario.

Hay versiones DBA_ de esas tres vistas. Podemos ver las opciones de auditoría que actualmente afectan a nuestros objetos consultando la vista USER_OBJ_AUDIT_OPTS. Para cada objeto mostrado en USER_OBJ_AUDIT_OPTS, las opciones de auditoria de cada comando que puede ser ejecutado sobre estos objetos (identificados por las columnas Object_Name y Object_Type) se muestran en USER_OBJ_AUDIT_OPTS. Los nombres de columna de USER_OBJ_AUDIT_OPTS se corresponden a las primeras letras del comando (por ejemplo, Alt para ALTER, Upd para UPDATE, etc.). Cada columna será registrada si el comando es auditado para el objeto cuando suceda el comando ('S'), no suceda ('U') o ambos. Las opciones de auditoría por defecto en efecto para cualquier nuevo objeto de la base de

Page 320: Java y Oracle 11g

Oracle /320

datos pueden ser mostradas mediante la vista ALL_DEF_AUDIT_OPTS, la cual tiene las mismas columnas que USER_OBJ_AUDIT_OPTS. La vista USER_OBJ_AUDIT_OPTS incluye las columnas Object_Name y Object_Type. Los comandos que pueden ser auditados son almacenados en una tabla de referencia llamada AUDIT_ACTIONS, la cual tiene dos columnas: Action (el código numérico de la acción) y Name (el nombre de la acción o comando). Action y Name se corresponden con las columnas Action y Action_Name de USER_AUDIT_TRAIL. Los DBA's pueden usar varias vistas de auditoría adicionales que no tienen su contrapartida USER_, incluyendo DBA_AUDIT_EXISTS, DBA_PRIV_AUDIT_OPTS, DBA_STMT_AUDIT_OPTS, y STMT_AUDIT_OPTION_MAP.

1.14. Supervisión: las tablas de rendimiento dinámico V$.

Las vistas que supervisan el rendimiento del entorno de base de datos son llamadas vistas de rendimiento dinámico. Estas vistas son referenciadas normalmente como tablas V$, porque todas ellas comienzan con este término. La definición y uso de columnas dentro de las vistas de supervisión están sujetos a cambios con cada versión de la base de datos. Las tablas V$ son usadas normalmente sólo por el DBA. 1.14.1. La tabla CHAINED_ROWS. Se puede usar el comando ANALYZE para generar un listado de las filas encadenadas o migradas dentro de una tabla. Este listado de filas encadenadas puede ser almacenado en una tabla llamada CHAINED_ROWS. Para crear la tabla CHAINED_ROWS en nuestro esquema podemos ejecutar el script utlchain.sql (que normalmente se encuentra en el subdirectorio /rdbms/admin del directorio de instalación de Oracle).

Fichero «utlchain.sql»

rem

rem $Header: utlchain.sql 07-may-96.19:40:01 sbasu Exp $

rem

Rem Copyright (c) 1990, 1995, 1996, 1998 by Oracle Corporation

Rem NAME

REM UTLCHAIN.SQL

Rem FUNCTION

Rem Creates the default table for storing the output of the

Rem analyze list chained rows command

Rem NOTES

Rem MODIFIED

Rem syeung 06/17/98 - add subpartition_name

Rem mmonajje 05/21/96 - Replace timestamp col name with analyze_timestam

Rem sbasu 05/07/96 - Remove echo setting

Rem ssamu 08/14/95 - merge PTI with Objects

Rem ssamu 07/24/95 - add field for partition name

Rem glumpkin 10/19/92 - Renamed from CHAINROW.SQL

Rem ggatlin 03/09/92 - add set echo on

Rem rlim 04/29/91 - change char to varchar2

Rem Klein 01/10/91 - add owner name for chained rows

Rem Klein 12/04/90 - Creation

Rem

create table CHAINED_ROWS (

owner_name varchar2(30),

table_name varchar2(30),

cluster_name varchar2(30),

partition_name varchar2(30),

subpartition_name varchar2(30),

head_rowid rowid,

analyze_timestamp date

);

Para poblar la tabla CHAINED_ROWS hay que usar la cláusula LIST CHAINED ROWS INTO del comando ANALYZE, tal como se muestra a continuación: ANALYZE TABLE Festivos LIST CHAINED ROWS INTO CHAINED_ROWS;

Page 321: Java y Oracle 11g

Oracle /321

La tabla CHAINED_ROWS muestra las columnas Owner_Name, Table_Name, Cluster_Name (si la tabla es un clúster), Partition_Name (si la tabla está particionada), Subpartition_Name (si la tabla contiene subparticiones), Head_RowID (el ROWID de la fila) y Analyze_TimeStamp (que muestra la última vez que la tabla o clúster fue analizado). Podemos consultar la tabla según los valores de Head_RowID, como en el siguiente ejemplo: SELECT *

FROM Festivos

WHERE RowID IN

(SELECT Head_RowID FROM CHAINED_ROWS WHERE Table_Name = 'Festivo');

Si la fila encadenada es de poca longitud, entonces podemos eliminar el encadenamiento borrando y reinsertando la fila. 1.14.2. La tabla PLAN_TABLE. Cuando afinamos comandos SQL podemos querer determinar los pasos que el optimizador realizó para ejecutar nuestra consulta. Para ver la ruta de la consulta debemos primero crear una tabla en nuestro esquema llamada PLAN_TABLE. El script usando para crear esta tabla se denomina utlxplan.sql, y normalmente se almacena en el subdirectorio /rdbms/admin.

Fichero «utlxplan.sql»

rem

rem $Header: utlxplan.sql 08-may-2004.12:53:19 bdagevil Exp $ xplainpl.sql

rem

Rem Copyright (c) 1988, 2004, Oracle. All rights reserved.

Rem NAME

REM UTLXPLAN.SQL

Rem FUNCTION

Rem NOTES

Rem MODIFIED

Rem bdagevil 05/08/04 - add other_xml column

Rem bdagevil 06/18/03 - rename hint alias to object_alias

Rem ddas 06/03/03 - increase size of hint alias column

Rem bdagevil 02/13/03 - add plan_id and depth column

Rem ddas 01/17/03 - add query_block and hint_alias columns

Rem ddas 11/04/02 - revert timestamp column to DATE (PL/SQL problem)

Rem ddas 10/28/02 - change type of timestamp column to TIMESTAMP

Rem ddas 10/03/02 - add estimated_time column

Rem mzait 04/16/02 - add row vector to the plan table

Rem mzait 10/26/01 - add keys and filter predicates to the plan table

Rem ddas 05/05/00 - increase length of options column

Rem ddas 04/17/00 - add CPU, I/O cost, temp_space columns

Rem mzait 02/19/98 - add distribution method column

Rem ddas 05/17/96 - change search_columns to number

Rem achaudhr 07/23/95 - PTI: Add columns partition_{start, stop, id}

Rem glumpkin 08/25/94 - new optimizer fields

Rem jcohen 11/05/93 - merge changes from branch 1.1.710.1 - 9/24

Rem jcohen 09/24/93 - #163783 add optimizer column

Rem glumpkin 10/25/92 - Renamed from XPLAINPL.SQL

Rem jcohen 05/22/92 - #79645 - set node width to 128 (M_XDBI in gendef)

Rem rlim 04/29/91 - change char to varchar2

Rem Peeler 10/19/88 - Creation

Rem

Rem This is the format for the table that is used by the EXPLAIN PLAN

Rem statement. The explain statement requires the presence of this

Rem table in order to store the descriptions of the row sources.

create table PLAN_TABLE (

statement_id varchar2(30),

plan_id number,

timestamp date,

remarks varchar2(4000),

Page 322: Java y Oracle 11g

Oracle /322

operation varchar2(30),

options varchar2(255),

object_node varchar2(128),

object_owner varchar2(30),

object_name varchar2(30),

object_alias varchar2(65),

object_instance numeric,

object_type varchar2(30),

optimizer varchar2(255),

search_columns number,

id numeric,

parent_id numeric,

depth numeric,

position numeric,

cost numeric,

cardinality numeric,

bytes numeric,

other_tag varchar2(255),

partition_start varchar2(255),

partition_stop varchar2(255),

partition_id numeric,

other long,

distribution varchar2(30),

cpu_cost numeric,

io_cost numeric,

temp_space numeric,

access_predicates varchar2(4000),

filter_predicates varchar2(4000),

projection varchar2(4000),

time numeric,

qblock_name varchar2(30),

other_xml clob

);

Después de crear esta tabla podemos usar el comando EXPLAIN PLAN, el cual generará registros en nuestra tabla PLAN_TABLE, etiquetando el valor Statement_ID podemos especificar la consulta que queremos desgranar. Las columnas ID y Parent_ID de la tabla PLAN_TABLE establecen la jerarquía de pasos (Operations) que el optimizador sigue cuando ejecuta la consulta. 1.14.3. Interdependencias: USER_DEPENDENCIES y IDEPTREE. Unos objetos dentro de las bases de datos de Oracle pueden depender de otros. Por ejemplo, un procedimiento almacenado puede depender de una tabla, o un paquete pude depender de un cuerpo de paquete. Cuando un objeto dentro de la base de datos cambia, cualquier objeto procedimental que depende de él tendrá que ser recompilado. Esta recompilación puede ser automática (con una penalización consecuente del rendimiento) o manualmente. Dos conjuntos de vistas del diccionario de datos están disponibles para ayudarnos a trazar dependencias. El primero es USER_DEPENDENCIES, el cual muestra todas las dependencias directas de los objetos. Sin embargo, esta vista sólo muestra el primer nivel de dependencias. Para evaluar completamente el árbol de dependencias debemos crear objetos de seguimiento de dependencias recursivas en nuestro esquema. Para crear estos objetos hay que ejecutar el script utldtree.sql (normalmente localizado en el subdirectorio /rdbms/admin). Este script crea dos objetos que podemos consultar: DEPTREE y IDEPTREE. Estos objetos contienen información idéntica, pero IDEPTREE está indentada según la pseudo-columna Level, y por tanto es fácil de leer e interpretar.

Parte del contenido del fichero «utldtree.sql»

drop sequence deptree_seq

/

create sequence deptree_seq cache 200 /* cache 200 to make sequence faster */

Page 323: Java y Oracle 11g

Oracle /323

/

drop table deptree_temptab

/

create table deptree_temptab

(

object_id number,

referenced_object_id number,

nest_level number,

seq# number

)

/

create or replace procedure deptree_fill (type char, schema char, name char) is

obj_id number;

begin

delete from deptree_temptab;

commit;

select object_id into obj_id from all_objects

where owner = upper(deptree_fill.schema)

and object_name = upper(deptree_fill.name)

and object_type = upper(deptree_fill.type);

insert into deptree_temptab

values(obj_id, 0, 0, 0);

insert into deptree_temptab

select object_id, referenced_object_id, level, deptree_seq.nextval

from public_dependency

connect by prior object_id = referenced_object_id

start with referenced_object_id = deptree_fill.obj_id;

exception

when no_data_found then

raise_application_error(-20000, 'ORU-10013: ' ||

type || '' || schema || '.' || name || ' was not found.');

end;

/

drop view deptree

/

create view sys.deptree (nested_level, type, schema, name, seq#)

as

select d.nest_level, o.object_type, o.owner, o.object_name, d.seq#

from deptree_temptab d, dba_objects o

where d.object_id = o.object_id (+)

union all

select d.nest_level+1, 'CURSOR', '<shared>', '"'||c.kglnaobj||'"', d.seq#+.5

from deptree_temptab d, x$kgldp k, x$kglob g, obj$ o, user$ u, x$kglob c,

x$kglxs a

where d.object_id = o.obj#

and o.name = g.kglnaobj

and o.owner# = u.user#

and u.name = g.kglnaown

and g.kglhdadr = k.kglrfhdl

and k.kglhdadr = a.kglhdadr /* make sure it is not a transitive */

and k.kgldepno = a.kglxsdep /* reference, but a direct one */

and k.kglhdadr = c.kglhdadr

and c.kglhdnsp = 0 /* a cursor */

/

create view deptree (nested_level, type, schema, name, seq#)

as

select d.nest_level, o.object_type, o.owner, o.object_name, d.seq#

Page 324: Java y Oracle 11g

Oracle /324

from deptree_temptab d, all_objects o

where d.object_id = o.object_id (+)

/

drop view ideptree

/

create view ideptree (dependencies)

as

select lpad('',3*(max(nested_level))) || max(nvl(type, '<no permission>')

|| '' || schema || decode(type, NULL, '', '.') || name)

from deptree

group by seq# /* So user can omit sort-by when selecting from ideptree */

/

1.14.4. Seguridad de etiquetas de Oracle. Los usuarios de la Seguridad de etiquetas de Oracle disponen de vistas adicionales del diccionario de datos, incluyendo ALL_SA_GROUPS, ALL_SA_POLICIES, ALL_SA_USERS, y ALL_SA_USER_PRIVS. 1.14.5. Vistas de carga directa de SQL*Loader. Para controlar la opciones de carga directa de SQL*Loader, Oracle mantiene varias vistas del diccionario de datos. Éstas son generalmente sólo consultadas para propósitos de depuración. Las vistas que soportan la opción de carga directa son LOADER_COL_INFO, LOADER_CONSTRAINT_INFO, LOADER_FILE_TS, LOADER_PARAM_INFO, LOADER_PART_INFO, LOADER_REF_INFO, LOADER_TAB_INFO y LOADER_TRIGGER_INFO. Estas vistas son creadas mediante el script catldr.sql, normalmente localizado en el subdirectorio/rdbms/admin. 1.14.6. Vista para soportar globalización. Tres vistas del diccionario de datos muestran información sobre los parámetros de soporte de globalización que se están aplicando actualmente en la base de datos. Valores no estándar para los parámetros NLS (como NLS_DATE_FORMAT y NLS_SORT) pueden asignarse mediante el fichero de parámetros de la base de datos o mediante el comando ALTER SESSION. Para ver las asignaciones NLS de nuestra sesión, instancia y base de datos podemos consultar las vistas NLS_SESSION_PARAMETERS, NLS_INSTANCE_PARAMETERS, y NLS_DATABASE_PARAMETERS, respectivamente. 1.14.7. Librerías. Nuestras rutinas PL/SQL pueden ser programas C externos. Para ver de qué librerías externas somos propietarios, podemos consultar la vista USER_LIBRARIES, la cual muestra el nombre de la librería (Library_Name), el fichero asociado (File_Spec), si la librería es no dinámica (Dynamic), y el estado de la librería (Status). También están disponibles las vistas ALL_LIBRARIES y DBA_LIBRARIES, que incluyen la columna adicional Owner. 1.14.8. Servicios heterogéneos. Para soportar la gestión de servicios heterogéneos, Oracle proporciona varias vistas del diccionario de datos. Todas las vistas de esta categoría comienzan con HS_ en vez de DBA_. En general, estas vistas son usadas principalmente por los DBA's. 1.14.9. Tipos de índices y operadores. Los operadores y tipos de índices están estrechamente relacionados. Podemos usar el comando CREATE

OPERATOR para crear un nuevo operador y definir sus enlaces. Podemos referenciar operadores en tipos de índices en comandos SQL. Los operadores, a su vez, referencian funciones, paquetes, tipos y otros objetos definidos por el usuario. Podemos consultar la vista USER_OPERATORS para ver de cada operador su propietario (Owner), nombre (Operator_Name), y número de enlaces (Number_of_Binds). La información auxiliar para los operadores es accesible mediante la vista USER_OPANCILLARY, y podemos consultar USER_OPARGUMENTS para ver los argumentos de los operadores. Podemos consultar USER_OPBINDINGS para ver los enlaces de operadores. La vista USER_INDEXTYPE_OPERATORS muestra los operadores soportados por tipos de índices. Los tipos de índices pueden ser mostrados mediante la vista USER_INDEXTYPES. Existen versiones ALL_ y DBA_ de estas vistas. Desde Oracle Database 10g, podemos ver los tipos de índices de tipos de arrays mediante la vista USER_INDEXTYPE_ARRAYTYPES.

Page 325: Java y Oracle 11g

Oracle /325

1.14.10. Outlines. Cuando usamos outlines almacenados, podemos recuperar sus nombres y detalles mediante la vista USER_OUTLINES. Para ver las marcas que constituyen los outlines, podemos consultar USER_OUTLINE_HINTS. Hay versiones ALL_ y DBA_ de USER_OUTLINES y USER_OUTLINE_HINTS. 1.14.11. Consejeros. Desde Oracle Database 10g, los usuarios pueden acceder a vistas del diccionario de datos relacionadas con las recomendaciones para afinar. Estas vistas empiezan con USER_ADVISOR_ y tienen sus correspondientes versiones DBA_. Por ejemplo, USER_ADVISOR_ACTIONS contiene datos sobre las acciones asociadas con todas las recomendaciones en la base de datos. Cada acción se especifica en la columna Command, con seis columnas de atributos relacionados. USER_ADVISOR_LOG muestra información sobre el estado actual de todas las tareas, como los datos específicos de ejecución, el progreso de supervisión y el estado completo. Los parámetros para los consejeros son accesibles mediante la vista USER_ADVISOR_PARAMETERS; podemos ver las exposiciones razonadas para recomendaciones mediante la vista USER_ADVISOR_RATIONALE. Para los resultados de un análisis de todas las recomendaciones en la base de datos podemos consultar la vista USER_ADVISOR_RECOMMENDATIONS. Una recomendación puede tener varias acciones asociadas (ver USER_ADVISOR_ACTIONS) basadas en las exposiciones razonadas usadas. 1.14.12. Planificadores. Desde Oracle Database 10g, podemos acceder a un conjunto de vistas relacionadas con las tareas de planificación en la base de datos. Estas vistas incluyen USER_SCHEDULER_JOBS (todas las tareas planificadas propiedad del usuario), USER_SCHEDULER_PROGRAMS (las tareas programadas), y USER_SCHEDULER_PROGRAM_ARGS (los argumentos para los programas programados). Podemos ver los detalles de ejecución mediante las vistas USER_SCHEDULER_JOB_LOG, USER_SCHEDULER_JOB_RUN_DETAILS, y USER_SCHEDULER_RUNNING_JOBS. Podemos ver los planificadores mediante USER_SCHEDULER_SCHEDULES.

2. Administración de la base de datos.

En este capítulo veremos los pasos involucrados en la administración de Oracle Database 11g. Hay muchos componentes para las tareas de los administradores de base de datos (DBA). Las tareas básicas consisten de la creación de una base de datos, iniciar y parar la base de datos, dar tamaño y controlar las áreas de memoria de la base de datos, asignar y gestionar espacio para los objetos, crear y gestionar recuperaciones y realizar copias de respaldo.

2.1. Creación de una base de datos.

El modo más simple de generar un script de creación de una base de datos es mediante el Instalador Universal de Oracle (OUI). Cuando instalamos Oracle, el OUI nos da la opción de crear una base de datos. Si usamos esta opción, OUI creará una base de datos que es normalmente usada para practicar o que será la base para el desarrollo de nuestras aplicaciones. El comando CREATE DATABASE se utiliza con SQL*Plus, a través de una cuenta con el permiso de sistema SYSDBA: CONNECT system/contraseña AS SYSDBA

STARTUP NOMOUNT

CREATE DATABASE . . .

Para intentar conectarse como un SYSDBA debemos tener la apropiada autorización en los niveles del sistema operativo y la base de datos. 2.1.1. Usando el «Oracle Enterprise Manager». Oracle Enterprise Manager (OEM), es una herramienta con interfaz gráfica de usuario. Las herramientas incluidas en OEM proporcionan una interfaz robusta para administrar bases de datos remotas. Todos los DBA's pueden usar el mismo repositorio central OEM (un conjunto de tabla creadas en una base de datos) para realizar sus trabajos. Además, OEM incluye un planificador de tareas. Debemos tomar varias decisiones clave antes de instalar y configurar OEM. Necesitamos decidir donde se creará el repositorio OEM y cómo y cuándo se harán copias de respaldo para proteger este repositorio. Aunque podemos usar OEM como una interfaz para el Oracle Recovery Manager (RMAN), la información de recuperación puede ser almacenada en el repositorio OEM. Se puede crear una base de datos mínima

Page 326: Java y Oracle 11g

Oracle /326

independiente para almacenar el repositorio OEM. Deberemos asegurarnos de que esta base de datos sea respaldada con frecuencia de forma que la recuperación del repositorio mismo esté asegurada.

2.2. Iniciación y parado de la base de datos.

Para iniciar una base de datos se usa el comando STARTUP dentro de SQL*Plus, tal como se muestra en el siguiente ejemplo: CONNECT system/contraseña AS SYSDBA;

STARTUP OPEN MiBD;

Alternativamente, podemos montar la base de datos: CONNECT system/contraseña AS SYSDBA;

STARTUP MOUNT MiBD;

Cuando la base de datos es montada pero no abierta, podemos gestionar sus ficheros. Por ejemplo, si movemos alguno de los ficheros de la base de datos mientras ésta fue parada, necesitamos decirle a Oracle saber dónde encontrarlos antes de reiniciarla. Para obtener la nueva ubicación de los ficheros, podemos montar la base de datos y entonces usar el comando ALTER DATABASE para renombrar los antiguos ficheros a su nueva localización. Una vez que hemos finalizado de decirle a Oracle la nueva localización para los ficheros, podemos abrir la base de datos mediante al comando ALTER DATABASE OPEN. Hay cuatro opciones primarias para parar la base de datos. En una parada normal (SHUTDOWN NORMAL), Oracle espera a que todos los usuarios salgan de la base de datos antes de pararla. En una parada transaccional (SHUTDOWN TRANSACTIONAL), Oracle espera que las transacciones activas se completen antes de parar. En una parada inmediata (SHUTDOWN IMMEDIATE), Oracle da marcha atrás a las transacciones no confirmadas y finaliza la sesión de cualquier usuario). En una parada abortada (SHUTDOWN ABORT), la base de datos se para inmediatamente y cualquier transacción no confirmada se pierde.

Nota. Cuando la base de datos está en proceso de parar o iniciarse, no se permiten nuevos inicios de sesión.

Por ejemplo, para realizar una parada inmediata de la base de datos: CONNECT system/contraseña AS SYSDBA;

SHUTDOWN IMMEDIATE

2.3. Tamaño y gestión de las áreas de memoria.

Cuando iniciamos una base de datos, Oracle asignar un área de memoria (el System Global Area, o SGA) compartida por todos los usuarios de la base de datos. Las dos áreas más grandes del SGA son normalmente el búfer caché de la base de datos y el "pool" compartido; sus tamaños impactan directamente en los requerimientos de memoria de la base de datos y el rendimiento de sus operaciones. Sus tamaños son controlados por parámetros del fichero de inicialización de la base de datos. El búfer caché de la base de datos es un área del SGA usado para contener los bloques de datos que son leídos desde los segmentos de datos de la base de datos, como las tablas e índices. El tamaño del búfer caché de la base de datos es determinados por el parámetro DB_CACHE_SIZE (expresado en número de bytes) en el fichero de parámetros de inicialización de la base de datos. Por su parte, el tamaño por defecto para los bloques de la base de datos se asigna mediante el parámetro DB_BLOCK_SIZE. Gestionar el tamaño del búfer caché es una parte importante de controlar y afinar la base de datos. La base de datos tiene un tamaño de bloque por defecto, pero podemos establecer áreas de caché para diferentes tamaños de bloques y entonces crear tablespaces para que usen estas cachés. Por ejemplo, podemos crear un bloque de 4 KB con algunos tablespaces asignados a 8 KB. El tamaño de caché de 8 KB debería ser asignado mediante el parámetro DB_8K_CACHE_SIZE. Para crear tablespaces que usen esta caché debemos especificar BLOCKSIZE 8K como parte del comando CREATE TABLESPACE. Si el tamaño de bloque por defecto de la base de datos es 4 KB no deberíamos asignar un valor para DB_4K_CACHE_SIZE; el tamaño especificado por DB_CACHE_SIZE debería ser usado por la caché de 4 KB.

Nota. La caché para el tamaño de bloque debe existir antes de crear un tablespace que use este tamaño de bloque.

Las diferentes áreas de caché pueden ser redimensionadas mientras la base de datos se está ejecutando. Las cachés deben ser incrementadas o decrementadas en gránulos. Para una base de datos con un SGA menor de 128M, el tamaño de gránulo es 4M —así DB_8K_CACHE_SIZE puede ser 4M, 8M, 12M, y demás—. Si intentamos usar cualquier otro valor, Oracle lo redondeará al tamaño de gránulo mayor más próximo. El

Page 327: Java y Oracle 11g

Oracle /327

siguiente comando muestra cómo asignar el parámetro DB_8K_CACHE_SIZE. ALTER SYSTEM SET DB_8K_CACHE_SIZE = 8M;

Si creamos un tablespace que use un tamaño de bloque distinto del de por defecto, debemos asegurarnos de que el parámetro de tamaño de caché relacionado (como DB_8K_CACHE_SIZE) sea actualizado en nuestro fichero de parámetros de base de datos. Si estamos usando un fichero «init.ora», debemos actualizarlo con el nuevo valor. Si estamos usando un fichero de parámetros de sistema (el método preferido), se actualizará automáticamente cuando ejecutemos el comando ALTER SYSTEM con la cláusula SCOPE=BOTH.

Nota. No podemos alterar el valor de los parámetros SGA_MAX_SIZE o JAVA_POOL_SIZE mientras la base de datos esté abierta.

Oracle gestionará el espacio en el búfer caché usando un algoritmo que guarde los bloques usados más activos en lo que sea posible. Cuando es necesario espacio libre en la caché, los nuevos bloques intentarán usar el espacio ocupado por boques accedidos con poca frecuencia o el espacio ocupado por un bloque modificado una vez que ha sido escrito al disco. Si el SGA no es suficientemente grande para contener los datos más frecuentemente usados, diferentes objetos competirán por el espacio de la caché. La competición se producirá particularmente cuando varias aplicaciones usen la misma base de datos y compartan el mismo SGA. En este caso, las tablas e índices usados más recientemente para cada aplicación constantemente competirán por espacio en el SGA. Como resultado, solicitudes de datos provocan frecuentes lecturas/escrituras físicas de datos, provocando degradación en el rendimiento. El "pool" compartido almacena la caché del diccionario de datos (información sobre la estructura de base de datos) y la caché de librería (información sobre los comandos que se ejecutan sobre la base de datos). Mientras la caché de bloque de datos y la caché del diccionario permiten compartir la estructura e información de datos entre usuarios de la base de datos, la caché de biblioteca permite compartir los comando SQL más usados normalmente. El "pool" compartido contiene el plan de ejecución y árbol de reconocimiento de los comandos SQL que se ejecutan sobre la base de datos. La segunda vez que un comando SQL idéntico se ejecuta (por algún usuario), Oracle es capaz de tomar ventajas de la información del "pool" compartido para servir la ejecución del comando. El tamaño en bytes del "pool" compartido se asigna mediante el parámetro de inicialización SHARED_POOL_SIZE. Como con otras cachés, el "pool" compartido puede ser modificado mientras la base de datos está abierta. Desde Oracle Database 10g, podemos usar el Automatic Shared Memory Management (ASMM). Para activar el ASMM, se asigna un valor no cero al parámetro de inicialización SGA_TARGET. Después de asignar SGA_TARGET al tamaño de SGA deseado entonces debemos asignar los otros parámetros de caché relacionados (DB_CACHE_SIZE, SHARED_POOL_SIZE, JAVA_POOL_SIZE, y LARGE_POOL_SIZE) a cero. Debemos parar y reiniciar la base de datos para que estos cambios sean efectivos. Podemos supervisar el tamaño de las cachés mediante la vista de rendimiento dinámica V$SGASTAT.

Nota. Mientras la base de daos se esté ejecutando, podemos modificar el parámetro SGA_TARGET y Oracle modificará automáticamente el tamaño de las cachés relacionadas.

Si la carga de trabajo en la base de datos cambia, la base de datos modificará los tamaños de caché para reflejar las necesidades de la aplicación. Por ejemplo, si hay un proceso por lotes intensivo durante la noche y una transacción en línea intensiva durante el día, la base de datos puede modificar los tamaños de caché cada vez que cambie la carga. Estos cambios ocurren automáticamente, si la intervención del DBA.

2.4. Asignar y gestionar espacio para objetos.

Cuando una base de datos se crea, se divide en varias secciones lógicas llamadas tablespaces. El tablespace SYSTEM es el primero es ser creado, y el tablespace SYSAUX es creado como parte de cada base de datos de Oracle Database 11g. Podemos crear tablespaces adicionales para contener varios tipos de datos (como tablas, índices y segmentos de deshacer). Desde Oracle Database 10g, los tablespaces pueden ser renombrados mediante la cláusula RENAME del comando ALTER TABLESPACE. Cuando se crea un tablespace, se crean ficheros de datos para contener los datos. Estos ficheros son inmediatamente asignados al espacio especificado durante su creación. Cada fichero de datos puede estar en un único tablespace. Los ficheros de datos se pueden extender automáticamente cuando se ejecutan fuera del espacio asignado; podemos asignar el incremento por el cual se extiende y su tamaño máximo. Cada esquema

Page 328: Java y Oracle 11g

Oracle /328

de usuario es una colección de objetos lógicos de la base de datos, como tablas e índices, que referencian estructuras de datos físicas almacenadas en los tablespaces. Los objetos de un esquema de usuario pueden almacenar varios tablespaces, y un único tablespace puede contener objetos de varios esquemas. Cuando un objeto de base de datos (como una tabla o índice) se crea, es asignado a un tablespace mediante una asignación por defecto o especificándolo en la instrucción. Se crea un segmento en el tablespace para contener los datos asociados con el objeto. Un segmento está constituido por secciones llamadas extensiones (conjunto de bloques Oracle contiguos). Una vez las extensiones existentes no puede contener más datos, el segmento obtendrá otra extensión. Si el segmento está compuesto de varias extensiones, no hay garantías de que las extensiones sean contiguas. Los tablespaces pueden ser creados para gestionar localmente (por defecto) o por el diccionario. En un tablespace gestionado por diccionario, la información de localización de extensiones se almacena en el diccionario de datos. En un tablespace gestionado localmente, los datos son almacenados en las cabeceras de los ficheros de datos. Oracle recomienda el uso de tablespaces gestionados localmente. 2.4.1. Implicaciones de la cláusula «storage». La cantidad de espacio usado por un segmento está determinado por sus parámetros de almacenamiento. Estos parámetros se determinan en el momento de creación de los segmentos de base de datos; si no especificamos parámetros de almacenamiento en el comando CREATE TABLE, CREATE INDEX, o CREATE

CLUSTER, entonces la base de datos usará los parámetros de almacenamiento por defecto del tablespace el cual es segmento se almacena.

Nota. Podemos asignar un tablespace por defecto a los usuarios, y asignar cuotas de espacio dentro de estos tablespaces, mediante los comandos CREATE USER, ALTER USER, y GRANT.

Cuando creamos una tabla, índice u otro segmento, podemos usar los valores por defecto para tablespaces gestionados localmente (la opción recomendada) o especificar una cláusula STORAGE como parte del comando CREATE. También podemos especificar una cláusula TABLESPACE, habilitando que Oracle directamente almacene datos en un tablespace en particular. Por ejemplo, un comando CREATE TABLE en un tablespace gestionado por diccionario puede incluir las siguientes cláusulas: TABLESPACE USERS

STORAGE (INITIAL 1M NEXT 1M PCTINCREASE 0 MINEXTENTS 1 MAXEXTENTS 200)

Si no especificamos la cláusula STORAGE, los parámetros de almacenamiento por defecto del tablespace serán usados. Para un tablespace USERS gestionado localmente, el comando CREATE TABLE debería sólo necesitar incluir TABLESPACE USERS. Los parámetros de almacenamiento especifican el tamaño de extensión inicial, el tamaño de la extensión siguiente, el PCTINCREASE (un factor por el cual la siguiente extensión será geométricamente dimensionada), la máxima extensión (MAXEXTENTS), y el número mínimo de extensiones (MINEXTENTS). Después de creado el segmento, los valores INITIAL y MINEXTENTS no pueden modificarse a menos que realicemos una reorganización del objeto. Los valores por defecto de los parámetros de almacenamiento están disponibles en las vistas DBA_TABLESPACES y USER_TABLESPACES. Cuando creamos un tablespace, podemos especificar los valores por defecto de los parámetros de almacenamiento. El siguiente comando crea un tablespace gestionado localmente y especifica los parámetros por defecto: CREATE TABLESPACE Tablas_Codigos

DATAFILE '/u01/oracle/VLDB/codes_tables.dbf' SIZE 10M

EXTENT MANAGEMENT LOCAL UNIFORM SIZE 256K;

Cuando se crea un segmento, adquiere al menos una extensión. La extensión inicial será usada para almacenar datos hasta que no hay más espacio disponible (puede usarse la cláusula PCTFREE para reservar un porcentaje de espacio dentro de cada bloque en el segmento para actualizaciones de los registros existentes). Si creamos un tablespace gestionado por diccionario, los tamaños de extensiones pueden ser no uniformes; el segmento se extenderá obteniendo una segunda extensión del tamaño especificado por el parámetro NEXT. Asumiendo que en este ejemplo, el tamaño de bloque de una base de datos es de 4KB, y el tablespace es creado gestionado localmente con un tamaño uniforme de 256KB, cada bit en el mapa de bits describe 64 bloques (256/4). Si la cláusula UNIFORM SIZE se omite, el valor por defecto es AUTOALLOCATE. El SIZE por defecto para UNIFORM es 1MB.

Nota. Si especificamos LOCAL en un comando CREATE TABLESPACE, no podemos especificar la cláusula DEFAULT STORAGE, MINEXTENTS, o TEMPORARY.

Page 329: Java y Oracle 11g

Oracle /329

Los tablespaces gestionados localmente puede asumir algunas de las tareas de gestión de espacio realizadas por los DBA's en tablespaces gestionados por diccionario. A causa de su arquitectura, tiene menos probabilidades de fragmentarse, y sus objetos tienen menos probabilidades de tener problemas de espacio. Podemos crear un conjunto de tablespaces gestionados localmente con un pequeño número de parámetros de almacenamiento y resolver la mayoría de solicitudes de espacio en nuestra base de datos. Por ejemplo, podemos crear tres tablespaces DATA como sigue: CREATE TABLESPACE DATA_SMALL

DATAFILE '/u01/oracle/VLDB/data_small.dbf' SIZE 10M

EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M;

CREATE TABLESPACE DATA_MEDIUM

DATAFILE '/u01/oracle/VLDB/data_medium.dbf' SIZE 100M

EXTENT MANAGEMENT LOCAL UNIFORM SIZE 4M;

CREATE TABLESPACE DATA_LARGE

DATAFILE'/u01/oracle/VLDB/data_large.dbf' SIZE 1000M

EXTENT MANAGEMENT LOCAL UNIFORM SIZE 16M;

Si tenemos una tabla pequeña podemos ubicarla en el tablespace DATA_SMALL. Si aumenta significativamente en tamaño podemos moverla al tablespace DATA_MEDIUM o DATA_LARGE. 2.4.2. Segmentos de tabla. Los segmentos de tabla, también llamadas segmentos de datos, almacenan las filas de datos asociados con las tablas o clústeres. Cada segmento de datos contiene un bloque de cabecera que sirve como un espacio de directorio para el segmento. Una vez que un segmento de datos adquiere una extensión, mantiene esta extensión hasta que el segmento es borrado, trucando o comprimido mediante las opciones de Oracle Database 11g. Borrar filas de una tabla mediante el comando DELETE no afecta a la cantidad de espacio asignado a la tabla. El número de extensiones se incrementará hasta que (1) el valor de MAXEXTENTS se sobrepase (si está asignado), (2) la cuota de usuario en el tablespace se sobrepasa, o (3) el tablespace se ejecuta fuera del espacio (si los ficheros de datos no puede auto-extenderse). Para minimizar la cantidad de espacio gastado en un bloque de datos se puede afinar el parámetro PCTFREE. El parámetro PCTFREE especifica la cantidad de espacio que quedará libre dentro de cada bloque de datos. El espacio libre puede ser entonces usado cuando columnas con valores NULL sean actualizadas para tener valores, o cuando actualizaciones de otros valores en la fila fuercen que la longitud total de la fila se incremente. Podemos usar el comando ALTER TABLE para modificar la mayoría de parámetros de almacenamiento de una tabla existente. Podemos usar la opción MOVE del comando ALTER TABLE para cambiar la asignación del tablespace de una tabla.

3. Auditoría de Seguridad

Oracle tienen la capacidad de auditar todas las acciones que tienen lugar en la base de datos. Se pueden auditar tres tipos de acciones:

• Intentos de entrada en cuentas de la base de datos. • Accesos a los objetos de la base de datos. • Acciones sobre la base de datos.

La base de datos registra todos los intentos de acción, tanto los exitosos como los infructuosos, aunque es un parámetro configurable. Para habilitar la capacidad de auditoría se debe fijar el parámetro AUDIT_TRAIL en el fichero init.ora. Los registros de auditoría se almacenan en la tabla SYS.AUD$ o bien su gestión se deja al sistema operativo. Cuando se decide utilizar la tabla SYS.AUD$ ésta debe revisarse periódicamente, por si hiciera falta truncarla debido a que su aumento de tamaño puede causar problemas de espacio en el tablespace SYSTEM. Los valores del parámetro AUDIT_TRAIL son los que se exponen en la siguiente tabla:

Valor Descripción

NONE Deshabilita la auditoría. BD Habilita la auditoría, escribiendo en la tabla SYS.AUD$. OS Habilita la auditoría, dejando al sistema operativo su gestión.

Page 330: Java y Oracle 11g

Oracle /330

3.1. Auditando conexiones.

Todo intento de conexión con la base de datos será registrado. El comando para iniciar la auditoría es: AUDIT SESSION;

Para determinar si se deben registrar sólo los éxitos, o sólo los fracasos, se pueden utilizar los siguientes comandos: AUDIT SESSION WHENEVER SUCCESSFUL;

AUDIT SESSION WHENEVER NOT SUCCESSFUL;

Si los registros de auditoría se almacenan en la tabla SYS.AUD$, entonces pueden verse a través de la vista DBA_AUDIT_SESSION. SELECT

OS_USERNAME, /* nombre de usuario SO */

USERNAME, /* nombre de usuario BD */

TERMINAL,

DECODE(RETURNCODE,'0','Conectado',

'1005', 'Solo username, sin password',

'1017','Password incorrecto', returncode), /* comprobacion de error */

TO_CHAR(TIMESTAMP,'DD-MON-YY HH24:MI:SS'), /* hora de entrada */

TO_CHAR(LOGOFF_TIME,'DD-MON-YY HH24:MI:SS') /* hora de salida */

FROM DBA_AUDIT_SESSION;

Para deshabilitar la auditoria de las conexiones basta con ejecutar la siguiente sentencia: NOAUDIT SESSION;

3.2. Auditando Acciones

Se puede auditar cualquier acción que afecte a cualquier objeto de la base de datos. Para facilitar la gestión, las acciones a auditar se encuentran agrupadas según los grupos que se muestran en la siguiente tabla:

Grupo Comandos auditados

CLUSTER Todas las sentencias que afecten a clusters.

DATABASE LINK Todas las sentencias que afecten a enlaces de base de datos.

EXISTS Todas las sentencias que fallen porque ya existe un objeto en la base de datos.

INDEX Todas las sentencias que afecten a índices.

NOT EXISTS Todas las sentencias que fallen porque un determinado objeto no existe.

PROCEDURE Todas las sentencias que afecten a procedimientos.

PROFILE Todas las sentencias que afecten a perfiles.

PUBLIC DATABASE LINK Todas las sentencias que afecten a enlaces públicos de base de datos.

PUBLIC SINONYM Todas las sentencias que afecten a sinónimos públicos.

ROLE Todas las sentencias que afecten a roles.

ROLLBACK SEGMENT Todas las sentencias que afecten a segmentos de rollback.

SEQUENCE Todas las sentencias que afecten a secuencias.

SESSION Todas las sentencias de acceso a la base de datos.

SYNONYM Todas las sentencias que afecten a sinónimos.

SYSTEM AUDIT Todas las sentencias AUDIT y NOAUDIT.

SYSTEM GRANT Todas las sentencias afecten a privilegios.

TABLE Todas las sentencias que afecten a tablas.

TABLESPACE Todas las sentencias que afecten a espacios de tablas.

TRIGGER Todas las sentencias que afecten a disparadores.

USER Todas las sentencias que afecten a las cuentas de usuarios.

VIEW Todas las sentencias que afecten a vistas.

Por ejemplo, para auditar todas acciones que tienen que ver con las tablas se usa el siguiente comando: AUDIT TABLE;

Y para deshabilitar la auditoría se utilizará el siguiente comando: NOAUDIT TABLE;

También se puede afinar un poco más en la auditoría fijando un usuario concreto al que seguir la pista: AUDIT TABLE BY Usuario1;

Cada acción auditada recibe un código numérico al que se puede acceder a través de la vista AUDIT_ACTIONS. Una vez que conocemos el código de la acción, podemos utilizarlo para determinar cómo dicha acción ha afectado a un objeto, consultado la vista DBA_AUDIT_OBJECT.

Page 331: Java y Oracle 11g

Oracle /331

3.3. Auditando objetos.

Además de la auditoría de acciones sobre los objetos, se puede seguir el rastro a las operaciones de manipulación de tablas: SELECT, INSERT, UPDATE y DELETE. Estas auditorías se pueden hacer por sesión o por acceso. Un ejemplo de sentencias de auditorías sobre objetos se puede ver en el siguiente grupo de sentencias: AUDIT INSERT ON Usuario1.EMP;

AUDIT ALL ON Usuario1.EMP BY SESSION;

AUDIT DELETE ON Usuario1.EMP BY ACCESS;

Los registros de auditoría se pueden ver en la misma vista DBA_AUDIT_OBJECT anteriormente mencionada.

3.4. Protegiendo los registros de auditoría.

Los registros de la tabla SYS.AUD$ pueden ser objeto de intentos de acceso para ser eliminados, ya que pueden reflejar acciones no autorizadas en la base de datos. Así, resulta interesante reflejar ese tipo de acciones. Esto se consigue con el siguiente comando: AUDIT ALL ON SYS.AUD$ BY ACCESS;

De este modo, cualquier acción contra la tabla SYS.AUD$ quedará registrada. Además, las acciones contra la tabla SYS.AUD$ sólo pueden ser borradas por los usuarios que puedan conectarse como INTERNAL.