Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Además de los bloques PL/SQL anónimos utilizados por SQL*Plus o por las herramientas de desarrollo (Oracle*Forms,
Oracle*Reports...), se puede emplear código PL/SQL en determinados objetos de la base de datos, como los
procedimientos almacenados (PROCEDURE, FUNCTION, PACKAGE) y los triggers de base de datos.
El bloque PL/SQL que constituye el trigger puede ejecutarse antes o después de la actualización y, por lo tanto, antes
o despues de la verificación de las restricciones de integridad.
Los triggers ofrecen una solución procedimental para definir restricciones complejas o que tengan en cuenta datos
procedentes de varias filas o de varias tablas, como por ejemplo para garantizar el hecho de que un cliente no pueda
tener más de dos pedidos no pagados. Sin embargo, los triggers no deben emplearse cuando sea posible establecer
una restricción de integridad. En efecto, las restricciones de integridad se definen en el nivel de tabla y forman parte
de la estructura de la propia tabla, por lo que la verificación de estas restricciones es mucho más rápida. Además, las
restricciones de integridad garantizan que todas las filas de las tablas respetan dichas restricciones, mientras que los
triggers no tienen en cuenta los datos ya contenidos en la tabla en el momento de definirlos.
El bloque PL/SQL asociado a un trigger se puede ejecutar para cada fila afectada por la instrucción DML (opción FOR
EACH ROW), o una única vez para cada instrucción DML ejecutada (opción predeterminada).
Ejecución antes o después de la comprobación de las restricciones de integridad para cada fila o cada sentencia
En los triggers BEFORE y FOR EACH ROW se pueden modificar los datos que van a insertarse en la tabla, de modo que
respeten las restricciones de integridad. También es posible ejecutar consultas de tipo SELECT sobre la tabla a la que
se aplica la instrucción DML, aunque únicamente en el marco de un trigger BEFORE INSERT. Todas estas operaciones
no se pueden realizar en los triggers AFTER, ya que después de la verificación de las restricciones de integridad no es
posible modificar los datos y, dado que la modificación (adición o eliminación) de la fila no se ha terminado, no es
posible ejecutar consultas de tipo SELECT sobre la tabla.
También se pueden incluir triggers en las vistas (VIEW) con el fin de capturar las instrucciones DML que se pueden
ejecutar sobre ellas. Estos triggers permiten controlar todas las operaciones realizadas sobre las vistas y, para el
usuario final, la vista es completamente similar a una tabla, ya que puede realizar sobre ella operaciones INSERT,
UPDATE y DELETE. Estos triggers son de tipo INSTEAD OF, es decir, que su ejecución va a reemplazar a la instrucción
DML a la que estén asociados. Este tipo de trigger solo puede definirse en vistas y es el único tipo de trigger que
puede implementarse en ellas.
Sintaxis
OR REPLACE
Reemplaza la descripción del trigger si ya existe.
BEFORE
El bloque PL/SQL se ejecuta antes de la verificación de las restricciones de tabla y de actualizar los
datos almacenados en la misma.
AFTER
El bloque PL/SQL se ejecuta después de la actualización de los datos contenidos en la tabla.
INSTEAD OF
El bloque PL/SQL siguiente reemplaza el procesamiento estándar asociado a la instrucción que ha
activado al trigger (solo por una vista).
Instrucción asociada a la activación del trigger. Varias instrucciones pueden activar un mismo trigger y se
combinan mediante el operador OR.
El trigger se ejecuta para cada fila tratada por la instrucción asociada.
FOLLOWS nombre_otro_trigger[,...]
Oracle permite definir varios triggers para la misma tabla y el mismo evento. En este caso, el orden
ENABLE/DISABLE
Esta cláusula permite indicar si el trigger está o no activo desde el momento de su creación; por defecto,
un trigger de nueva creación está activo. Crear un trigger desactivado permite verificar que se compila
correctamente antes de ponerlo realmente en servicio. Un trigger creado desactivado puede activarse
más adelante utilizando una sentencia ALTER TRIGGER...ENABLE.
WHEN (condición)
La condición especificada debe cumplirse para que se ejecute el código.
Los datos de la tabla a la que está asociado el trigger son inaccesibles desde las instrucciones del bloque. Solo la fila
que se está modificando es accesible a través de dos variables de tipo RECORD: OLD y NEW, las cuales poseen la
estructura de la tabla o de la vista asociada. Estas variables pueden utilizarse en la cláusula WHEN del trigger o en el
bloque de instrucciones. En este último caso se referencian como variables host mediante el prefijo
":" (:OLD.nombre_campo, :NEW.nombre_campo).
La palabra OLD permite conocer qué fila se va a eliminar en un trigger DELETE o la fila que se va a modificar en un
trigger UPDATE. La palabra NEW permite conocer cuál es la nueva fila insertada en un trigger INSERT o la fila tras su
modificación en un trigger UPDATE.
Los nombres OLD y NEW están definidos de manera predeterminada, aunque es posible utilizar otros nombres
empleando la cláusula REFERENCING OLD AS nuevo_nombre NEW AS nuevo_nombre. Esta cláusula se
incluye justo antes de la cláusula FOR EACH ROW (si existe) en la definición del trigger.
Ejemplo
Ejecución de un bloque PL/SQL antes de una eliminación en la tabla CLIENTES por parte del usuario FLORENCIO:
Ejecución de un bloque PL/SQL después de actualizar cada fila de la tabla ARTICULOS cuando el precio antiguo es mayor que el
nuevo:
Para cada pedido, se desea conocer el nombre del usuario de Oracle que lo ha introducido. La primera etapa consiste en añadir
una nueva columna a la tabla de pedidos. Esta columna debe aceptar el valor NULL ya que, para las filas de los pedidos
existentes, el nombre del usuario de Oracle es desconocido.
Modificación de la tabla PEDIDOS:
Tabla modificada.
SQL>
En el trigger se cambia el nombre de la nueva fila, y el trigger se ejecuta antes de la verificación de las restricciones de
integridad para cada fila insertada en la tabla de pedidos.
Definición del trigger:
Trigger creado.
SQL>
Se desea saber el número de pedidos introducido por el usuario de Oracle. Para evitar escribir una consulta que recorra la tabla
de pedidos completa, operación que puede resultar muy pesada, el trigger se limita a actualizar una tabla de estadísticas.
Creación de la tabla de estadísticas:
Tabla creada.
SQL>
El trigger debe asegurarse de que el usuario existe en la tabla de estadísticas y, si todavía no existe, debe crearlo. El trigger se
Código del trigger:
Trigger creado.
SQL>
La escritura de un trigger puede complicarse cuando existen ciertos tratamientos comunes, por ejemplo, las
instrucciones INSERT y UPDATE mientras que otros son específicos de la inserción o bien de la actualización de los
datos. Una primera solución consiste en escribir dos triggers, uno para la instrucción INSERT y otro para la instrucción
UPDATE. De ello resulta entonces una cierta redundancia de codificación nada deseable (mantenimiento más pesado
del código). Oracle propone los predicados INSERTING, UPDATING y DELETING que permiten saber dentro del trigger si
el código PL/SQL se ejecuta tras una instrucción INSERT, UPDATE o DELETE. Estos predicados devuelven un valor
booleano y se utilizan en la condición de test de una instrucción IF. De este modo, es posible escribir un trigger común
a las instrucciones INSERT y UPDATE, por ejemplo, conservando la ejecución condicional de ciertas instrucciones.
Ejemplos
En el ejemplo siguiente, el trigger será común para las instrucciones INSERT y DELETE para mantener al día el atributo CALCULA
que representa el número de pedidos de un cliente:
Trigger created.
SQL>
El siguiente ejemplo muestra el interés de los triggers de tipo instead of. Previamente, se crea la vista CLIORENSE. Esta vista
permite saber qué clientes viven en Orense. A continuación, a través de esta vista se añade un cliente, pero es posible
recuperarlo.
Creación de la vista y visualización del fallo:
Vista creada.
1 fila creada.
Creación y prueba del trigger:
Trigger creado.
1 fila creada.
SQL> select * from cliorense;
SQL>
Trigger de tipo INSTEAD OF asociado a una vista (VIEW) sobre las tablas PEDIDOS y LINEASPED:
Vista creada.
Trigger creado.
SQL>
En la versión 11, Oracle ha introducido la noción de trigger compuesto.
A diferencia de un trigger simple, en el que el momento de su activación es único (antes o después de la instrucción o
la actualización de una fila), un trigger compuesto puede tener hasta cuatro secciones, cada una de ellas
correspondiente a un instante de activación: antes de la instrucción, antes de cada fila, después de cada fila,
después de la instrucción.
El trigger compuesto presenta una ventaja en relación al uso de varios triggers separados: las diferentes secciones
pueden compartir las declaraciones comunes (variables, tipos, cursores, subprogramas, etc.).
Sintaxis
La cláusula FOR reemplaza a la cláusula BEFORE/AFTER de un trigger simple y permite definir el evento de
activación del trigger.
Las cláusulas FOLLOWS, ENABLE/DISABLE y WHEN son las mismas que para un trigger simple.
La cláusula opcional declaraciones_comunes permite definir las declaraciones comunes (variables, tipos,
cursores, subprogramas, etc.) a las diferentes secciones de código del trigger.
El código del trigger tiene hasta cuatro secciones de código, cada una de ellas correspondiente a un instante de
activación; cada sección tiene la estructura siguiente:
instante_activación IS
[declaraciones_locales]
BEGIN
instrucciones
END instante_activación
La cláusula opcional declaraciones_locales permite definir las declaraciones locales de cada sección de código.
Como ejemplo, vamos a crear un trigger que audita las modificaciones de salario de los empleados:
Este trigger compuesto define dos declaraciones comunes: una variable acumulado, para almacenar la cantidad
acumulada de las modificaciones de salario y un procedimiento mostrar, para visualizar la información por pantalla.
En la sección after statement el trigger solo muestra el valor de la variable global acumulado.
Ahora podemos verificar este trigger:
NUMEROEMPLEADO SALARIO
----------------------------- ----------
1 1200
2 1800
SQL> update EMPLEADO set SALARIO=SALARIO * 1.01;
1: 12
2: 18
Total: 30
2 filas actualizadas.
Los sucesos del sistema son el arranque y la desconexión de la instancia de Oracle (startup y shutdown) y el
tratamiento de los errores. Los triggers de arranque y de desconexión tienen como ámbito el conjunto de la instancia
de Oracle, mientras que el trigger de tratamiento de errores puede definirse en el nivel de esquema o en el nivel de
base de datos.
En el caso de sucesos de usuario, pueden existir triggers para las operaciones de inicio y cierre de sesión (logon y
logoff), y para supervisar y controlar la ejecución de las instrucciones DDL (CREATE, ALTER y DROP) y DML (INSERT,
UPDATE y DELETE).
Los triggers DML se asocian a una tabla y a una instrucción DML. Su definición y uso se ha detallado anteriormente en
este capítulo.
Al escribir estos triggers, es posible emplear atributos para identificar de forma precisa el origen del suceso y adaptar
el tratamiento necesario en consecuencia.
1. Atributos
ora_client_ip_address
Permite conocer la dirección IP del equipo cliente que actúa como origen de la conexión.
ora_database_name
Nombre de la base de datos.
ora_des_encrypted_password
Permite conocer las descripciones cifradas de contraseñas del usuario que se hayan creado o
modificado.
ora_dict_obj_name
Nombre del objeto sobre el que acaba de ejecutarse la operación DDL.
ora_dict_obj_name_list
Permite conocer la lista de todos los nombres de objetos que se han modificado.
ora_dict_obj_owner
Propietario del objeto sobre el que se aplica la operación DDL.
ora_dict_obj_owner_list
ora_dict_obj_type
Tipo del objeto esperado en la última operación DDL.
ora_grantee
Permite conocer qué usuarios tienen este privilegio.
ora_instance_num
Número de la instancia.
ora_is_alter_column
Devuelve TRUE si la columna que se pasa como parámetro se ha modificado.
ora_is_creating_nested_table
Permite saber si se ha creado una tabla anidada.
ora_is_drop_column
Permite saber si la columna que se pasa como parámetro se ha eliminado.
ora_is_servererror
Devuelve TRUE si el número de error que se pasa como parámetro se encuentra en la pila de errores.
ora_login_user
Permite conocer el nombre del usuario que ha iniciado la sesión.
ora_privileges_list
Permite conocer la lista de privilegios concedidos o revocados a un determinado usuario.
ora_revokee
Permite saber a qué usuarios se les ha revocado el privilegio.
ora_server_error
Devuelve el número de error de Oracle que se encuentra en la pila, cuya posición se ha pasado como
parámetro (la parte superior de la pila corresponde a la posición 1).
ora_server_error_depth
Número de errores en la pila de errores.
Mensaje de error almacenado en un índice dado en la pila de errores.
ora_server_error_num_params
Número de argumentos substituidos en el mensaje de error almacenado en un índice dado en la pila
de errores.
ora_server_error_param
Valor substituido para un número de argumentos determinado en el mensaje de error almacenado en
un índice dado de la pila de errores.
ora_sql_text
Consulta SQL que está en el origen de la ejecución del trigger.
ora_sysevent
Nombre del suceso del sistema que ha activado el trigger.
ora_with_grant_option
Devuelve TRUE si el privilegio se ha concedido con una opción de administración.
ora_space_error_info
Si el error es relativo a una falta de espacio, da información acerca del objeto afectado.
2. Sucesos del sistema
STARTUP
Este suceso se activa al abrir la instancia.
SHUTDOWN
Este suceso se activa justo antes de que el servidor inicie el proceso de desconexión de la instancia. Si
se produce una desconexión inesperada del servidor de base de datos, este suceso no se ejecutará.
SERVERERROR
Este suceso se activa cuando se produce un error de Oracle. No obstante, este trigger no se activa
para los errores ORA1034, ORA1403, ORA1422, ORA1423 y ORA4030, ya que son demasiado
graves como para que el proceso pueda continuar ejecutándose.
Sintaxis
Para los sucesos STARTUP y SERVERERROR solo están disponibles los triggers de tipo AFTER y para el suceso
SHUTDOWN solo está disponible el trigger de tipo BEFORE.
Ejemplo
Implementación de un trigger de gestión de errores: en el siguiente ejemplo, los datos relativos a cada error provocado en el
esquema del usuario que ha creado el trigger se almacenan en la tabla errores, que se ha creado previamente.
Trigger creado.
SQL>
3. Sucesos de usuario
AFTER LOGON
Se produce después de haber establecido una conexión con el servidor.
BEFORE LOGOFF
Se produce antes de interrumpir la conexión con el servidor.
BEFORE CREATE, AFTER CREATE
Se produce cuando se crea un objeto.
BEFORE ALTER, AFTER ALTER
Se produce cuando se modifica un objeto.
BEFORE DROP, AFTER DROP
Se produce cuando se elimina un objeto.
BEFORE ANALYZE, AFTER ANALYZE
Se produce cuando se ejecuta una instrucción de análisis.
BEFORE ASSOCIATE STATISTICS, AFTER ASSOCIATE STATISTICS
BEFORE AUDIT, AFTER AUDIT
Se produce cuando se activa una auditoría.
BEFORE NOAUDIT, AFTER NOAUDIT
Se produce cuando se anula una operación de auditoría.
BEFORE COMMENT, AFTER COMMENT
Se produce cuando se incluye un comentario.
BEFORE DDL, AFTER DDL
Se produce durante la ejecución de la mayoría de las instrucciones DDL, a excepción de ALTER
DATABASE, CREATE CONTROLFILE, CREATE DATABASE y todas las instrucciones DDL ejecutadas dentro
de un bloque PL/SQL.
BEFORE DISSOCIATE STATISTICS, AFTER DISSOCIATE STATISTICS
Se produce durante la disociación de las estadísticas de un objeto.
BEFORE GRANT, AFTER GRANT
Se produce durante la ejecución de una instrucción GRANT.
BEFORE RENAME, AFTER RENAME
Se produce durante la ejecución de una instrucción RENAME.
BEFORE REVOKE, AFTER REVOKE
Se produce durante la ejecución de la instrucción REVOKE.
BEFORE TRUNCATE, AFTER TRUNCATE
Se produce al truncar una tabla.
Sintaxis
Debe consultar la lista anterior para saber si el suceso puede tratarse mediante un trigger de tipo BEFORE o AFTER.
Ejemplo
Implementación de un trigger de supervisión de tablas: en el siguiente ejemplo, el trigger definido permite monitorizar todas las
Trigger creado.
SQL>
La instrucción ALTER TRIGGER permite desactivar y después reactivar los triggers. La desactivación puede planificarse
cuando vaya a realizarse una importación masiva de datos o una modificación importante.
La desactivación y la reactivación de triggers pueden realizarse trigger por trigger, o tabla por tabla. La instrucción
ALTER TABLE permite activar y desactivar todos los triggers aplicados sobre una tabla.
Sintaxis
Ejemplo
Desactivación seguida de una reactivación de triggers:
Trigger modificado.
Trigger modificado.
Tabla modificada.
Tabla modificada.
SQL>
Para obtener información sobre los triggers, es necesario consultar el diccionario de datos. Las tres vistas del
diccionario que hay que utilizar son: USER_TRIGGERS, ALL_TRIGGERS y DBA_TRIGGERS.
La columna BASE_OBJECT_TYPE permite saber si el trigger está basado en una tabla, una vista, un esquema o en la
base de datos completa.
La columna TRIGGER_TYPE permite saber si se trata de un trigger de tipo BEFORE, AFTER o INSTEAD OF; si su modo
de ejecución es FOR EACH ROW o no, y si se trata de un trigger basado en un suceso o no.
La columna TRIGGERING_EVENT permite saber qué suceso se ve afectado por el trigger.
Ejemplo
Consulta del diccionario: en el ejemplo siguiente, se consulta la vista USER_TRIGGERS para saber qué triggers están definidos
sobre el esquema del usuario actual.
SQL>
Sintaxis
OR REPLACE
Reemplaza la descripción del procedimiento, si existe.
parámetro
Especifica una variable pasada como parámetro que puede utilizarse en el bloque.
IN
El parámetro que se pasa es un dato de entrada para el procedimiento.
OUT
El procedimiento asigna un valor al parámetro especificado y lo devuelve al entorno que haya hecho la
llamada.
tipo
Tipo de variable (SQL o PL/SQL).
Ejemplo
Procedimiento para eliminar un artículo:
Procedimiento creado.
SQL>
Uso en SQL*Plus:
Uso en un bloque PL/SQL:
DECLARE
x char;
BEGIN
...
elim_art(x);
...
END;
Sintaxis
OR REPLACE
Si la función existe, se reemplaza su descripción.
parámetro
Especifica un parámetro que se pasa como dato de entrada y que se usa como una variable dentro del
bloque.
tipo
Tipo de parámetro (SQL o PL/SQL).
RETURN tipo
Tipo del valor devuelto por la función.
Ejemplo
Función factorial:
Uso en SQL*Plus:
FACTORIAL(5)
------------
120
SQL>
Para activar esta funcionalidad para una función, es suficiente con incluir la cláusula RESULT_CACHE en la definición de
la función.
Sintaxis
La cláusula opcional RELIES_ON permite especificar una o varias tablas o vistas de las que depende la función.
Cuando se modifican los datos de las tablas afectadas, la caché se invalida y se reconstruye en el momento de las
llamadas posteriores a la función.
Ejemplo
Función creada.
VALOR_PARAMETRO(1)
--------------------------------------
ENI
Terminado: 00:00:01.62
SQL> select valor_parametro(1) from dual;
VALOR_PARAMETRO(1)
Terminado: 00:00:00.15
SQL> insert into parametros(codigo,valor)
2 values(123,’UNO DOS TRES’);
1 fila creada.
Terminado: 00:00:00.12
SQL> commit;
Validación efectuada.
Terminado: 00:00:00.10
SQL>select valor_parametro(1) from dual;
VALOR_PARAMETRO(1)
--------------------------------------
ENI
Terminado: 00:00:01.60
SQL>select valor_parametro(1) from dual;
VALOR_PARAMETRO(1)
--------------------------------------
ENI
Terminado: 00:00:00:17
En la primera llamada con 1 como valor del parámetro, la función devuelve el resultado en poco más de 1,5 segundos.
En la segunda llamada con el mismo valor del parámetro, la función solo utiliza 15 centésimas de segundo para
ejecutarse. Después de la modificación de los datos de la tabla, una nueva llamada con 1 como valor del parámetro,
se ejecuta de nuevo en poco más de 1,5 segundos: la caché del resultado se ha invalidado, ya que la función
depende de la tabla modificada (clausula RELIES_ON).
El administrador de la base de datos dispone de varios parámetros para regular el funcionamiento de la caché del
resultado.
Desde la versión 12, es posible añadir la directiva UDF en la definición de una función para indicar al compilador que
esta función se destina principalmente para su uso en sentencias SQL. El compilador PL/SQL optimiza la compilación
para mejorar el rendimiento de la función en este contexto.
Ejemplo
Función creada.
COUNT(DISTINCTMAY(NOMBRE))
-----------------------
43092
Función creada.
COUNT(DISTINCTMAY(NOMBRE))
-----------------------
43092
En este ejemplo, vemos que la llamada a la función compilada con la directiva UDF es mucho más rápida.
Adicionalmente, desde la versión 12, es posible definir una función en la cláusula WITH de una consulta SELECT. Esta
función se comporta como una función local a la consulta que la puede llamar. En este caso, incluso la llamada
repetida a esta función local es más rápida que la llamada a una función almacenada.
Elemplo
COUNT(DISTINCTMAY(NOMBRE))
-----------------------
43092
Comparando ambos ejemplos, observamos que la llamada a la función definida en la cláusula WITH presenta un
mejor rendimiento que la llamada a una función almacenada «normal», y es ligeramente con menor rendimiento que
la llamada a la función compilada con la directiva UDF.
Los paquetes se dividen en dos partes: una cabecera o especificación y un cuerpo (body). La cabecera permite
describir el contenido del paquete y conocer el nombre y los parámetros de llamada de las funciones y
procedimientos. Pero el código no forma parte de la cabecera o especificación, sino que se incluye en el cuerpo del
paquete. Esta separación de especificaciones y código permite implantar un paquete sin que el usuario pueda
visualizar el código y permite, además, adaptar el código de forma sencilla para cumplir nuevas reglas.
Los paquetes ofrecen numerosas ventajas:
Modularidad
El hecho de agrupar de forma lógica elementos PL/SQL relacionados hace más fácil la comprensión de
los diferentes elementos del paquete y su uso se simplifica enormemente.
Simplificación del desarrollo
Durante el proceso de definición de una aplicación, los paquetes hacen posible definir en la primera
etapa del diseño únicamente la cabecera de los paquetes y realizar así las compilaciones. El cuerpo del
paquete solo será necesario para ejecutar la aplicación.
Datos ocultos
Con un paquete es posible hacer que determinados elementos no sean visibles para el usuario del
paquete. Esto permite crear elementos que solo pueden utilizarse dentro del paquete y que por tanto
simplifican la escritura del mismo.
Adición de funcionalidades
Las variables y cursores públicos del paquete existen durante toda la sesión, por lo que es un modo de
compartir información entre los diferentes subprogramas en una misma sesión.
Mejora del rendimiento
El paquete se encuentra en memoria desde que se produce una llamada a un elemento que forma parte
de él. El acceso a los diferentes elementos del paquete es entonces mucho más rápido que la llamada a
funciones y procedimientos independientes.
1. Cabecera
El ámbito de todos los elementos definidos en la especificación del paquete es global para el paquete y local para el
esquema del usuario.
La sección de especificación permite precisar qué recursos del paquete podrán emplear las aplicaciones. En esta
sección de especificación deben aparecer todos los datos que permitan saber cómo usar los recursos del paquete
(parámetros de llamada, tipo del valor devuelto).
Sintaxis
Ejemplo
Cabecera de un paquete de gestión de clientes:
Paquete creado.
SQL>
2. Cuerpo del paquete
El cuerpo del paquete (PACKAGE) contiene la implementación de los procedimientos y funciones descritos en la
cabecera. También contiene definiciones de tipos y declaraciones de variables cuyo ámbito está limitado al cuerpo
del paquete.
La especificación del cuerpo del paquete no es necesaria si la cabecera del mismo solo contiene definiciones de tipos
y declaraciones de variables.
Para asegurarse de que en el cuerpo del paquete todos los elementos necesarios en la especificación están bien
definidos, PL/SQL realiza una comparación punto por punto. De este modo, con la excepción de los espacios, la
declaración hecha en la especificación debe encontrar su equivalente exacto en el cuerpo. Si no es así, se generará
una excepción durante la compilación.
El cuerpo del paquete puede, además, contener definiciones locales de cursores, variables, tipos, funciones y
procedimientos que se emplearán de manera interna en el paquete. Estos elementos no serán accesibles desde
fuera del paquete.
Sintaxis
Ejemplo
Cuerpo del paquete para la gestión de clientes:
SQL>
3. Uso
Se hace referencia a los elementos de un paquete (variables, procedimientos, funciones) utilizando el nombre del
paquete y el operador ".".
Uso del paquete para la gestión de clientes:
SQL> begin
2 :V_NUMCLI := GESTION_CLIENTES.CRE_CLI
3 (’CESAR’,’Cava Baja’,13000,’MARBELLA’);
4 end;
5 /
V_NUMCLI
----------
2003
SQL> select NUMCLI, ltrim(NOMCLI) NOMCLI, CIUDAD
2 from CLIENTES order by NUMCLI
3 /
10 filas seleccionadas.
SQL> begin
2 GESTION_CLIENTES.ELIM_CLI(152);
3 end;
4 /
9 filas seleccionadas.
SQL>
4. Cursores
Es posible separar la declaración de un cursor (la especificación) de su definición (el cuerpo). Con esta técnica, será
posible cambiar la definición del cursor sin tener que modificar su declaración. La declaración del cursor contenida en
la cabecera del paquete debe respetar la sintaxis siguiente:
El valor devuelto debe ser un registro.
En el cuerpo del paquete, ahora será obligatorio definir la cláusula SELECT asociada al cursor. Por supuesto, las
columnas especificadas después de SELECT y el tipo de datos indicado para el valor devuelto en la sección de
especificación deben corresponderse de forma exacta.
La definición de los cursores dentro del paquete ofrece una mayor flexibilidad, ya que es posible cambiar su
definición sin estar obligados a modificar la sección de especificación del paquete.
Para utilizar dentro de un bloque PL/SQL un cursor definido en un paquete, es necesario preceder el nombre del
cursor con el nombre del paquete. La notación es la misma que la empleada para llamar a funciones o
procedimientos definidos en un paquete.
El ámbito de un cursor de paquete no está limitado al bloque en el que se abre, por lo que es posible mantener
abierto el cursor durante toda la sesión y cerrarlo al desconectarse.
Una transacción autónoma es una transacción independiente que se ejecuta después de otra transacción, la
transacción principal. Durante la ejecución de la transacción autónoma, la ejecución de la transacción principal se
detiene.
Las transacciones autónomas son totalmente independientes; esta independencia permite construir aplicaciones más
modulares. Por supuesto, las transacciones autónomas presentan las mismas características que las transacciones
regulares.
Para definir una transacción autónoma hay que emplear la directiva de compilación (pragma)
AUTONOMOUS_TRANSACTION. Esta directiva debe aparecer en la sección de declaración de variables de los bloques
PL/SQL anónimos, funciones, procedimientos y triggers. Generalmente, las directivas de compilación se incluyen al
principio de la sección de declaración de variables, lo que facilita la relectura del programa.
No se puede incluir la directiva de compilación pragma AUTONOMOUS_TRANSACTION en el nivel de paquete. Sin
embargo, cada función y procedimiento del paquete se puede declarar como transacción autónoma.
Ejemplo
Procedimiento que define una transacción autónoma: en el siguiente ejemplo, la función de actualización de clientes constituye
una transacción autónoma.
SQL>
Las modificaciones llevadas a cabo por la transacción autónoma son visibles para las restantes transacciones
Las transacciones autónomas se controlan mediante las instrucciones COMMIT, ROLLBACK y SAVEPOINT. En un bloque
PL/SQL definido como transacción autónoma se pueden llevar a cabo varias transacciones una tras otra. Para la
transacción principal que realiza la invocación, es este conjunto de transacciones del bloque invocado el que es
autónomo.
Ejemplo
Procedimiento que define una transacción autónoma; en el siguiente ejemplo se invoca el procedimiento de actualización de
clientes desde un bloque PL/SQL anónimo cuya transacción en curso se anula. Se puede comprobar que el trabajo realizado por el
procedimiento sigue siendo válido.
SQL> begin
2 insert into clientes(numcli,nomcli)
3 values (14,’pablo’);
4 nombre_correcto();
5 rollback;
6 end;
7 /
12 filas seleccionadas.
SQL>
También se pueden definir triggers como transacciones autónomas. Esta opción es interesante cuando los triggers se
usan para llevar un registro de las operaciones que se realizan sobre una tabla, incluso aunque la operación no sea
validada. El hecho de que el trigger sea autónomo permite asegurar que la operación llevada a cabo se registra en la
base de datos, incluso aunque se anule la operación que dio origen al trigger (ROLLBACK).
Además, a diferencia de los triggers tradicionales, los triggers autónomos pueden ejecutar instrucciones SQL DDL
utilizando instrucciones de SQL dinámico.
En el caso en que la instrucción INSERT se utilice para insertar una única línea de datos en la tabla es posible utilizar
la cláusula RETURNING para conocer por ejemplo el valor de una columna o bien el resultado de un cálculo. Esta
funcionalidad resulta especialmente práctica cuando una secuencia se asocia a la columna y la valoración de la
columna a partir de la secuencia se realiza desde un trigger de base de datos. La cláusula RETURNING permite
conocer el valor que se ha generado e insertado en la tabla. Sin esta cláusula, los accesos a la base se multiplican
para conocer el valor asignado a la columna.
Sintaxis
Ejemplo
En el ejemplo siguiente se implementa una función de creación de clientes. Esta función utiliza una secuencia de Oracle para fijar
el número de clientes. La función devuelve el valor del número de cliente.
Function created.
SQL>
También es posible utilizar esta cláusula RETURNING en una instrucción DELETE para obtener uno o más datos sobre
la línea borrada.
Sintaxis
Por último, es posible utilizar esta cláusula RETURNING con la instrucción de actualización UPDATE. Como para las
instrucciones INSERT y DELETE, la cláusula RETURNING solo es concebible en el caso en que la instrucción UPDATE
actualice una única fila de información en la base de datos.
Sintaxis
Debe recordarse que, en el código SQL estático, todos los datos son conocidos en el momento de la compilación y
que, por supuesto, las instrucciones SQL estáticas no cambian de una ejecución a otra. Esta solución ofrece sus
ventajas, ya que el éxito de la compilación garantiza que las instrucciones SQL hagan referencia a objetos válidos de
la base de datos. La compilación también verifica que se dispone de los privilegios necesarios para acceder y para
trabajar con los objetos de la base de datos. Además, el rendimiento del código SQL estático es mayor que el del SQL
dinámico.
Por estas razones, el SQL dinámico solo debe emplearse si el SQL estático no es capaz de responder a nuestras
necesidades o si la solución con código SQL estático es mucho más compleja que con SQL dinámico.
No obstante, el SQL estático tiene ciertas limitaciones que se superan mediante el SQL dinámico. Se utilizará el SQL
dinámico si, por ejemplo, no se conocen de antemano las instrucciones SQL que tienen que ejecutarse en el bloque
PL/SQL, o si el usuario debe proporcionar datos para construir las instrucciones SQL que hay que ejecutar.
Además, utilizando código SQL dinámico es posible ejecutar instrucciones DDL (CREATE, ALTER, DROP, GRANT y
REVOKE), así como los comandos ALTER SESSION y SET ROLE, dentro del código PL/SQL, lo que no es posible con el
código SQL estático.
Por tanto, el SQL dinámico se empleará en los siguientes casos:
l La instrucción SQL no es conocida en tiempo de compilación.
l La instrucción que se desea ejecutar no está soportada por el código SQL estático.
l Para ejecutar consultas construidas durante la ejecución.
l Para hacer referencia a un objeto de la base de datos que no existe en tiempo de compilación.
l Para optimizar la consulta durante su ejecución.
l Para crear bloques PL/SQL de forma dinámica.
l Para gestionar los permisos de usuario de forma dinámica.
El SQL dinámico ofrece un mejor rendimiento que el paquete DBMS_SQL y más posibilidades que éste.
1. EXECUTE IMMEDIATE
El comando EXECUTE IMMEDIATE permite verificar la sintaxis y ejecutar de forma dinámica una instrucción SQL o un
bloque anónimo PL/SQL.
Sintaxis
Representa la instrucción SQL o el bloque PL/SQL. A partir de la versión 11, el tamaño del código
dinámico ya no está limitado a 32 KB ; si es necesario, puede emplearse una variable de tipo CLOB.
variable
Es la variable que almacenará el valor de una columna seleccionada.
registro
Es una variable estructurada que contendrá una fila seleccionada.
argumento
Especifica los valores que se pasan a la instrucción SQL o al bloque PL/SQL. Estos argumentos pueden
representar valores de lectura/escritura.
Excepto consultas que devuelvan varias filas, es posible ejecutar cualquier instrucción SQL o bloque PL/SQL. Los
argumentos no pueden contener el nombre de objetos de la base de datos que vayan a utilizarse en las
instrucciones SQL o PL/SQL.
El comando INTO solo debe utilizarse para consultas SELECT que devuelvan una sola fila de valores. A cada columna
devuelta por el comando SELECT debe corresponder una variable o un campo del registro.
Todos los argumentos pueden especificarse detrás de la cláusula USING. El modo predeterminado es IN, es decir,
que proporcionan un valor a la instrucción dinámica. Los argumentos de tipo OUT pueden especificarse detrás de la
palabra clave RETURN INTO o RETURNING INTO. Los argumentos pueden contener valores de tipo numérico o
cadenas de caracteres, pero no se pueden especificar valores booleanos (TRUE o FALSE).
Ejemplo
SQL dinámico: el siguiente ejemplo muestra un posible uso del código SQL dinámico y las diferentes posibilidades de la
instrucción EXECUTE IMMEDIATE.
SQL> DECLARE
2 consulta VARCHAR2(200);
3 bloque_pl VARCHAR2(200);
4 vnumcli pedidos.numcli%type:=8;
5 vnumped pedidos.numped%type:=6;
6 vdia date := sysdate();
7 vestado char(2):=’EC’;
8 vclientes clientes%rowtype;
9 vprecio articulos.precio%type;
10 vrefart articulos.refart%type:=’ZZ01’;
11 BEGIN
12 -- ejecución de una instrucción DDL
13 EXECUTE IMMEDIATE ’CREATE TABLE clientes_fieles(numcli number(6), ca
number(8,2))’;
14 -- instrucción DML con paso de argumento de entrada
15 consulta:=’INSERT INTO pedidos(numped, numcli, fechaped, estadoped)
values(:1, :2, :3, :4)’;
16 EXECUTE IMMEDIATE consulta USING vnumcli, vnumped, vdia, vestado;
SQL>
Cuando una instrucción INSERT, UPDATE o DELETE incluye una cláusula de tipo RETURNING, los argumentos de
salida se pueden incluir en la cláusula USING o en la cláusula RETURNING INTO. En las nuevas aplicaciones debe
utilizarse la cláusula RETURNING INTO.
Uso de la cláusula RETURNING INTO:
SQL> DECLARE
2 consulta VARCHAR2(200);
3 vrefart articulos.refart%type:=’ZZ01’;
4 vdes articulos.descripcion%type;
5 vprecio articulos.precio%type:=150;
6 viva articulos.codiva%type;
7 BEGIN
8 consulta:=’UPDATE articulos set precio=:1 WHERE refart=:2
9 RETURNING descripcion, codiva into :3, :4’;
10 /* uso de la cláusula USING */
11 EXECUTE IMMEDIATE consulta USING vprecio, vrefart, OUT vdes, OUT viva;
12 /* uso de la cláusula RETURNING INTO */
13 EXECUTE IMMEDIATE consulta USING vprecio, vrefart RETURNING INTO vdes,
viva;
14 END;
15 /
SQL>
Al utilizar la cláusula USING no es obligatorio especificar el modo de uso de los parámetros de entrada, ya que el
modo predeterminado es IN. Con la cláusula RETURNING INTO no puede especificarse el modo de uso del parámetro
ya que, obligatoriamente, es OUT. Cuando sea necesario se puede especificar el modo de uso de los parámetros
como OUT o IN OUT, por ejemplo en la llamada a un procedimiento.
Definición de un procedimiento con parámetros en modo IN, OUT e IN OUT:
Procedimiento creado.
SQL>
En el siguiente ejemplo se llama de forma dinámica al procedimiento que acabamos de definir. El modo de uso de los
diferentes parámetros se define detrás de la cláusula USING.
Modos de uso de los parámetros:
SQL> DECLARE
2 bloque_pl varchar2(200);
3 vnumped pedidos.numped%type;
4 vnumcli pedidos.numcli%type;
5 BEGIN
6 bloque_pl :=’BEGIN agregar_ped(:a,:b); END;’;
7 EXECUTE IMMEDIATE bloque_pl
8 USING IN OUT vnumped, vnumcli;
9 END;
10 /
SQL>
2. OPEN FOR, FETCH y CLOSE
Estos tres comandos se emplean para tratar las consultas dinámicas que van a ejecutarse sobre varias filas de
datos. Como con los cursores tradicionales, en primer lugar hay que abrir el cursor con el comando OPEN FOR, que
corresponde a una consulta de tipo SELECT. Después, en el bloque PL/SQL, podrán extraerse todas las filas de
datos una por una usando el comando FETCH. Para terminar, cuando se hayan procesado todas las filas, habrá que
cerrar el cursor mediante el comando CLOSE.
a. Apertura de un cursor (OPEN FOR)
El comando OPEN FOR permite asociar a una variable de tipo cursor una consulta SELECT que devuelva varias filas
de datos. La consulta se ejecuta y el cursor se sitúa en la primera fila de datos. A diferencia del cursor estático, la
instrucción OPEN FOR de los cursores dinámicos dispone de una cláusula USING opcional que permite pasar
argumentos a la consulta.
Ejemplo
En el siguiente ejemplo se declara una variable de tipo cursor y, a continuación, se asocia a una instrucción SELECT que va a
recuperar datos de una tabla.
declare
type CliCurTyp is ref cursor;
ccli CliCurTyp; -- variable cursor
vnum clientes.numcli%type;
vciudad clientes.ciudad%type:=’Orense’;
begin
open ccli for ’Select numcli, ciudad from clientes where ciudad=:v’
using vciudad;
...
end;
/
Solo cuando el cursor está abierto se evalúan todos los argumentos de la consulta. Por tanto, para recuperar
datos correspondientes a diferentes valores del argumento es necesario abrir de nuevo el cursor pasándole un
nuevo valor para el parámetro.
A partir de la versión 11, el tamaño de la consulta dinámica ya no está limitada a 32 KB ; si es necesario, puede
emplearse una variable de tipo CLOB.
b. FETCH
El comando FETCH devuelve una fila de datos procedente del conjunto de resultados correspondiente a la
ejecución de la consulta SELECT después de haber abierto el cursor. Para cada una de las columnas especificadas
después del comando SELECT es necesario prever una variable en el entorno PL/SQL que ejecuta el cursor
dinámico.
Sintaxis
FETCH variable_cursor
INTO {variable, ...|registro}
Ejemplo
Puede completarse el ejemplo anterior añadiendo el código correspondiente para el procesamiento de las filas.
declare
type CliCurTyp is ref cursor;
ccli CliCurTyp; -- variable cursor
vnum clientes.numcli%type;
vciudad clientes.ciudad%type:=’Orense’;
La variable en la que se almacena un valor procedente de una columna debe corresponderse exactamente con la
definición de la columna (mismo tipo y misma precisión).
Se pueden utilizar cláusulas INTO diferentes en distintas instrucciones FETCH empleando el mismo cursor.
Cada ejecución del comando FETCH extrae una fila del conjunto de resultados y sitúa el cursor en la siguiente fila.
Si se ejecuta una instrucción FETCH sobre un cursor cerrado o que nunca se ha abierto se genera la excepción
INVALID_CURSOR.
c. CLOSE
El comando CLOSE desactiva una variable de tipo cursor. Después de ejecutar este comando, el conjunto de
resultados creado por el comando OPEN FOR ya no existe.
Sintaxis
CLOSE variable_cursor
Ejemplo
En este ejemplo, después de procesar la última fila, se cierra el cursor:
declare
type CliCurTyp is ref cursor;
ccli CliCurTyp; -- variable cursor
vnum clientes.numcli%type;
vciudad clientes.ciudad%type:=’Orense’;
begin
open ccli for ’Select numcli, ciudad from clientes where ciudad=:v’
using vciudad;
loop
fetch ccli into vnum, vciudad; --extraer la línea siguiente
exit when ccli%notfound; --salir del bucle si no hay más filas
--tratar los datos
end loop;
close ccli;
end;
3. Uso de cursores dinámicos
Existen algunas reglas que permiten sacar el mejor partido de los cursores dinámicos en términos de rendimiento y
de calidad de programación.
a. Mejora del rendimiento
En el fragmento de código que se detalla a continuación, Oracle tiene que establecer un nuevo cursor para cada
nueva apertura. Esta generación de cursores tan similares puede degradar el rendimiento del servidor.
La conversión del número de cliente contenido en la variable vnumcli como cadena de caracteres permite construir
adecuadamente el comando dinámico SQL.
La mejora del rendimiento se logra usando un argumento. Esta solución permite a Oracle reutilizar el mismo cursor
para diferentes valores del argumento.
b. Pasar el nombre de un objeto
El siguiente procedimiento elimina una tabla cualquiera de la base de datos. Utilizando código SQL dinámico, el
fragmento de código sería el siguiente:
Al ejecutarse, este procedimiento genera un error de tipo "nombre de tabla no válido". Esto se debe al hecho de
que los argumentos no pueden utilizarse para pasar nombres de objetos de la base de datos. En lugar de este
código, habría que escribir lo siguiente:
c. Uso del mismo argumento varias veces
Las variables que se incluyen en las instrucciones de SQL dinámico están asociadas a los argumentos que siguen
a la cláusula USING de acuerdo a su posición y no al nombre que sirve para identificarlos como argumentos. Por
tanto, si la misma variable aparece dos veces en la instrucción de SQL dinámico, entonces deberá aparecer dos
veces el mismo argumento detrás de la cláusula USING.
El código sería entonces el siguiente:
Sin embargo, si se emplea un bloque PL/SQL, no es necesario pasar dos veces el mismo argumento. En efecto, en
los bloques PL/SQL las variables se asocian con los argumentos de acuerdo con su orden de definición, pero si
aparece varias veces la misma variable, solo la primera aparición se asociará con un argumento; por tanto, puede
usarse el siguiente código, que es más sencillo de escribir:
d. Atributos de los cursores
Los cursores explícitos poseen los cuatro atributos siguientes: %FOUND, %NOTFOUND, %ISOPEN y %ROWCOUNT,
que permiten obtener información acerca de la correcta ejecución del cursor, con independencia de que provenga
de una instrucción SQL estática o dinámica. Para llevar a cabo correctamente sus operaciones, Oracle utiliza
cursores implícitos. Es posible conocer los atributos de estos cursores mediante la palabra clave SQL. De este
modo, el atributo %ROWCOUNT en los cursores implícitos permite conocer el correcto desarrollo, o no, de la última
instrucción SQL dinámica que haya sido ejecutada.
Ejemplo
Función que utiliza el atributo del cursor implícito para comprobar que la instrucción SQL dinámica se ha ejecutado
correctamente. En el siguiente ejemplo, después de la eliminación de filas se utiliza el atributo %ROWCOUNT para conocer el
número de filas que se han visto afectadas por esta instrucción.
Función creada.
SQL>
Es preciso probar la función desde un bloque PL/SQL, ya que no se puede ejecutar una función DML desde una
consulta SELECT.
e. Paso de valores NULL
En ocasiones, puede ser necesario pasar valores NULL como argumentos a instrucciones SQL dinámicas. La
solución más lógica sería escribir el comando siguiente:
Pero, como ocurre con frecuencia, el valor NULL no se gestiona de manera tan sencilla. En efecto, no se puede
utilizar el literal NULL detrás de la cláusula USING. Para poder salvar esta limitación debe inicializarse una variable
con el valor NULL y utilizarla igual que un parámetro.
declare
c_null char(1); --la variable se inicializa a NULL
begin
execute immediate ’UPDATE clientes set ciudad=:x’ using c_null;
end;
f. Permisos de usuario
Por omisión, un procedimiento almacenado se ejecuta usando los permisos del usuario de Oracle que define el
procedimiento y no con los permisos del usuario que llama al procedimiento. Determinados procedimientos están
vinculados al esquema sobre el que se han definido.
Por ejemplo, supongamos que el procedimiento siguiente está definido sobre el esquema del usuario scott:
Procedimiento creado.
SQL>
El procedimiento borrar permite eliminar cualquier objeto del esquema simplemente pasando como parámetro el
tipo de objeto que se desea borrar y su nombre. Con el fin de facilitar la gestión de los objetos en cada esquema,
Ejecución del procedimiento borrar desde el esquema del usuario María:
Llamada terminada.
SQL>
Dado que el nombre del objeto se ha pasado sin hacer referencia al esquema, en realidad se ha hecho referencia
a la tabla CLIENTES del esquema scott, que es el creador de este procedimiento que María intenta utilizar para
eliminar la tabla.
Para resolver este tipo de problemas y garantizar que el procedimiento almacenado se va a ejecutar en función de
los permisos del usuario que lo usa y no de los permisos del usuario propietario es preciso emplear la cláusula
AUTHID en la definición del procedimiento. De este modo, los nombres de objetos se resuelven en el esquema del
usuario del procedimiento y no en el de su propietario.
Redefinición del procedimiento usando la cláusula AUTHID:
Procedimiento creado.
SQL>
g. Directiva de compilación RESTRICT_REFERENCES
Una función llamada desde un comando SQL debe cumplir una determinada serie de reglas que permitan controlar
los efectos colaterales. Para eliminar estas restricciones se puede usar la directiva de compilación RESTRICT_
REFERENCES. Esta directiva indica que la función no se va a ejecutar en modo lectura/escritura en una tabla de la
base de datos o una variable de un paquete.
No obstante, si el cuerpo de la función contiene una instrucción dinámica de tipo INSERT, UPDATE o DELETE,
entonces la función viola las reglas de no escritura en la base de datos (Write No Database State: WNDS) y de no
lectura en la base de datos (Read No Database State: RNDS). Esto ocurre porque las reglas que afectan a las
instrucciones dinámicas solo se verifican en tiempo de ejecución. En un comando EXECUTE IMMEDIATE, solo la
cláusula INTO permite detectar una violación de tipo RNDS en tiempo de compilación.
h. Cómo evitar los interbloqueos
No es posible eliminar todos los interbloqueos que pueden producirse. Sin embargo, sí es posible tomar ciertas
4. El paquete DBMS_SQL
Además del SQL dinámico, Oracle proporciona el paquete DBMS_SQL, que permite ejecutar de forma dinámica
instrucciones SQL.
Para utilizar el SQL dinámico la base de datos debe ser compatible con la versión 8.1.0 o una versión superior del
servidor de base de datos.
El paquete DBMS_SQL es una biblioteca PL/SQL que permite la ejecución de instrucciones SQL construidas de forma
dinámica.
Las principales ventajas del SQL dinámico con respecto al paquete DBMS_SQL son:
l la facilidad de uso,
l las mejoras de rendimiento en la ejecución,
l el soporte para los tipos de datos definidos por el usuario.
Pero también el paquete DBMS_SQL presenta sus ventajas respecto del SQL dinámico:
l Está soportado en las aplicaciones cliente,
l Soporta el procedimiento DESCRIBE_COLUMNS, que permite conocer los datos relativos a las columnas de un cursor
abierto a través de DBMS_SQL.
Desde la versión 12, este paquete se puede usar para devolver un resultado directamente, de manera implícita, sin
pasar por un parámetro OUT o un resultado de función de tipo REF CURSOR (Implicit Statement Results). Esta
funcionalidad resulta interesante para facilitar la migración a partir de otras bases de datos que permitan devolver
un resultado de esta manera.
Ejemplo
Procedimiento creado.
SQL>
SQL> -- Llamada al procedimiento
SQL> EXECUTE lista_articulos
Resultados #1
REFART NOMBRE
------------ ---------------
CD50
AB22 ALFOMBRA PERSA
CD21 Pletina láser
CD50 CADENA HIFI
ZZZZ CANTIMPLORA
AA00 REGALO
ZZ01 LOTE ALFOMBRAS
7 filas seleccionadas.
Los resultados implícitos devueltos de esta manera se pueden explotar en diferentes entornos de programación
(Java, .NET, PHP, etc.).
El uso de tablas en PL/SQL no siempre es evidente a primera vista. En efecto, por qué obligar a utilizar esta
estructura cuando para almacenar una serie de datos es muy fácil crear una tabla temporal.
La razón es muy simple: al guardar todos los datos en formato de colección directamente en el bloque PL/SQL, todos
los tratamientos pueden efectuarse con el motor PL/SQL. Al limitar las consultas SQL, se limitan los accesos a la base
de datos, lo que permite acelerar el tiempo de tratamiento del bloque PL/SQL, pero se limita también la ocupación del
motor SQL y, en consecuencia, las consultas de los demás usuarios pueden tratarse más rápidamente.
Se observa pues que existen beneficios al trabajar con las colecciones incluso si en un primer momento el código
PL/SQL a implementar es algo más complicado. Hay que observar que la instrucción FORALL (que veremos más
adelante en este capítulo) permite facilitar considerablemente las etapas de codificación necesarias para poder
trabajar con las colecciones en un bloque PL/SQL.
1. Cómo hacer referencia a un elemento de una colección
Para poder trabajar con una colección en primer lugar hay que saber cómo acceder a un elemento de la misma.
Todas las referencias emplean la misma estructura: el nombre de la colección seguido de un índice especificado
entre paréntesis.
nombre_colección (índice)
El índice tiene que ser un número válido comprendido entre 231+1 y 2311.
En las colecciones de tipo tabla anidada (nested table) el rango normal de índices va desde 1 hasta 2311 y para las
tablas de tipo VARRAY el rango se define entre 1 y el tamaño máximo de la tabla.
Puede hacerse referencia a un elemento de una colección en cualquier lugar donde pueda emplearse una variable
PL/SQL.
Ejemplo
Declaración y uso de una colección:
DECLARE
TYPE lista IS TABLE OF VARCHAR2(15);
losnombres lista:=lista(’B Martín’,’M Burger’,’S Gatos’,’T Grueso’);
i BINARY_INTEGER;
BEGIN
...
IF losnombres(i) =’G Victor’ THEN
...
END IF;
...
END;
2. Asignación de un valor y comparación de colecciones
Es posible asignar una colección a otra siempre que ambas sean del mismo tipo. La asignación puede hacerse
mediante el operador := , con las instrucciones INSERT, UPDATE, FETCH y SELECT o haciendo una llamada a un
subprograma.
Además, es importante respetar ciertas restricciones, como se ilustra en el siguiente ejemplo:
DECLARE
TYPE Clientela IS VARRAY(100) OF Cliente;
TYPE Fiel IS VARRAY(100) OF Cliente;
grp1 Clientela :=Clientela(...);
grp2 Clientela :=Clientela(...);
grp3 Fiel :=Fiel(...);
BEGIN
grp2 :=grp1;
grp3 :=grp2;
-- Ilegal, ya que son de tipos diferentes
...
END ;
Si se asigna la colección NULL a otra colección, entonces la segunda colección será NULL y habrá que reinicializarla.
Es posible asignar un determinado valor a un elemento de una colección utilizando la siguiente sintaxis:
nombre_colección(índice) :=expresión
La expresión contiene un valor del mismo tipo que la colección.
Si el índice es NULL o no es un número entero, entonces PL/SQL genera la excepción VALUE_ERROR.
Cuando se intenta acceder a un elemento que no pertenece a la colección, PL/SQL genera la excepción
SUBSCRIPT_BEYOND_COUNT.
Si se intenta acceder a un elemento de una colección NULL, PL/SQL genera la excepción COLLECTION_IS_NULL.
El siguiente ejemplo ilustra los diferentes casos en los que pueden generarse las excepciones anteriormente
enumeradas.
DECLARE
TYPE tabla IS TABLE OF INTEGER;
mitabla tabla;
BEGIN
mitabla(1) :=5 ; -- excepción COLLECTION_IS_NULL
mitabla :=tabla(10,5,3,6)
mitabla(1) :=length(’Hello’) ;
mitabla(2) :=mitabla(3)*2 ;
A las colecciones de tipo NESTED TABLE y VARRAY se les asigna automáticamente el valor NULL en su declaración.
Por tanto, es posible comprobar su nulidad.
DECLARE
TYPE tabla IS TABLE OF INTEGER;
mitabla tabla;
BEGIN
...
IF mitabla IS NULL THEN
...
END IF;
...
END;
Sin embargo, las colecciones no se pueden comparar globalmente usando el concepto de igualdad (=) o desigualdad
(<,>, <=, >=, <>). Por ejemplo, al analizar la instrucción IF siguiente se produce un error de compilación.
DECLARE
TYPE tabla IS TABLE OF INTEGER;
mitabla tabla :=tabla(5,6,7);
mitabla2 tabla :=tabla(1,2,3);
BEGIN
...
Esta limitación se genera desde la versión 10 para las colecciones de tipo nested table (como veremos más
adelante).
3. Cómo trabajar con colecciones
Las colecciones añaden cierta flexibilidad al lenguaje procedimental PL/SQL para manipular y trabajar de forma
sencilla con datos procedentes de la base de datos.
a. Cómo trabajar con colecciones de tipo NESTED TABLE
En SQL*Plus se define el tipo Tipoalmacen:
Tipo creado.
SQL>
A continuación, se define el tipo Tabalmacen como una colección de elementos de tipo Tipoalmacen.
Definición de la tabla Tabalmacen:
Tipo creado.
SQL>
A continuación es posible definir la tabla Deposito del siguiente modo:
Tabla creada.
SQL>
Cada elemento existente en la columna almacen es una colección de tipo tabla anidada (nested table) que va a
permitir realmacenar los artículos existentes en cada depósito. La cláusula NESTED TABLE es necesaria en la
creación de la tabla Deposito, ya que la columna almacen es una colección.
Adición de datos a la tabla usando la instrucción INSERT. Pueden añadirse valores a la tabla Deposito de la siguiente forma:
SQL> BEGIN
2 INSERT INTO deposito
3 VALUES (1,’Madrid’,
4 tabalmacen(tipoalmacen(’ZZ01’,250),
5 tipoalmacen(’AB01’,500)));
6 INSERT INTO deposito
7 VALUES (2, ’ORENSE’,
8 tabalmacen(tipoalmacen(’ZZ01’,100),
9 tipoalmacen(’AB01’,300)));
10 END;
11 /
SQL>
Los datos contenidos en la tabla Deposito se pueden modificar usando la cláusula UPDATE:
SQL> DECLARE
2 nuevo_almacen Tabalmacen:=Tabalmacen(
3 Tipoalmacen(’ZZ21’,200),
4 Tipoalmacen(’AB01’,100));
5 BEGIN
6 UPDATE deposito
7 SET almacen=nuevo_almacen WHERE cod_deposito=1;
8 END;
9 /
Procedimiento PL/SQL terminado correctamente.
SQL>
Para hacer el trabajo con las colecciones aún más fácil, es posible basarse en la instrucción %ROWTYPE para definir una
colección que posea exactamente la misma estructura que la tabla.
DECLARE
TYPE collection_clientes
IS TABLE OF clientes%ROWTYPE;
losClientes Collection_clientes;
BEGIN
...
END;
/
En el ejemplo anterior se declara una colección de tipo idéntico a la tabla de clientes.
b. Cómo trabajar con tablas
También se pueden definir columnas basadas en una tabla dentro de otra tabla. Vamos a crear la tabla Factura
para ilustrar esta idea. Cada factura corresponde a un cliente y una factura puede corresponderse con varios
pedidos. El número y el importe de cada pedido incluido en la factura se almacenan en una columna de tipo tabla.
El primer paso consiste en crear un tipo formado por un número de pedido y el importe del mismo.
Creación del tipo ElPedido:
Tipo creado.
A continuación, se crea el tipo correspondiente a una tabla de 50 pedidos (número e importe).
Creación del tipo TabPedidos:
Tipo creado.
SQL>
Por último, creamos la tabla Factura.
Creación de la tabla Factura:
Tabla creada.
SQL>
Para trabajar con las columnas de tipo tabla se puede, como es lógico, utilizar las instrucciones SQL INSERT y
UPDATE como se ilustra en el siguiente ejemplo.
Ejemplo de utilización de las instrucciones INSERT y UPDATE:
SQL> DECLARE
2 nuevos_pedidos TabPedidos:=TabPedidos(
3 ElPedido(3,600),
4 ElPedido(10,800),
5 ElPedido(11,1680));
6 BEGIN
7 ------------------------------------ Adición
8 INSERT INTO Factura VALUES(
9 1,1, TabPedidos(
10 ElPedido(2,500),
11 ElPedido(12,600),
12 ElPedido(13,1500)));
13 INSERT INTO Factura VALUES(
14 2,2, TabPedidos(
15 ElPedido(3,550),
16 ElPedido(10,135)));
17 ------------------------------------ Modificación
18 UPDATE Factura SET LosPedidos=nuevos_pedidos
19 where numfact=2;
SQL>
4. Cómo manipular los elementos de las colecciones
Hasta el momento, las instrucciones que se han presentado simplemente permiten manipular las colecciones en su
globalidad, pero no permiten manipular un elemento concreto de la colección. Para llevar a cabo este tipo de
operaciones usando el lenguaje SQL es preciso usar la cláusula TABLE, cuya subconsulta devuelve una sola columna
cuyos datos van a poder manipularse mediante operaciones SQL clásicas. En el siguiente ejemplo se añade una fila
de datos a la colección almacén de la tabla Deposito y después se consulta la colección LosPedidos de la tabla
Factura para conocer el importe total de una factura.
Manipulación de una fila de la colección:
SQL> DECLARE
2 total number;
3 BEGIN
4 ----------------------->Adición de datos
5 INSERT INTO
6 TABLE(Select almacen from deposito where nombre=’Madrid’)
7 VALUES (’BICI’,2000);
8 -----------------------> Búsqueda de datos
9 SELECT SUM(importe) INTO total
10 FROM TABLE (SELECT LosPedidos
11 FROM Factura
12 WHERE numfact=1);
13 END;
14 /
SQL>
No solo se pueden definir colecciones en las tablas, sino que también pueden definirse localmente en un bloque
PL/SQL para facilitar la manipulación y el procesamiento de los datos dentro de dicho bloque. Incluso aunque la
lógica de manipulación de las colecciones sea la misma, es preciso emplear la palabra reservada CAST con el fin de
convertir la colección local en un tipo más preciso. En el siguiente ejemplo, el objetivo es comparar dos colecciones;
una de ellas está definida localmente en el bloque PL/SQL y la otra procede de una tabla. El valor devuelto se
corresponde con el número de desviaciones entre ambas colecciones.
Manipulación de una colección definida en el bloque PL/SQL:
SQL> DECLARE
2 stock_testigo TabStock:=Tabstock(
3 TipoStock(’AB21’,200),
4 TipoStock(’ZZ01’,300),
5 TipoStock(’VELA’,550));
SQL>
5. Métodos
Hay disponibles una serie de métodos que facilitan el trabajo con las colecciones. Los métodos son funciones o
procedimientos que solo operan sobre colecciones y que se invocan anteponiendo el nombre de la colección al
nombre del método.
Sintaxis
Los métodos no pueden utilizarse en instrucciones SQL.
Únicamente el método EXISTS puede emplearse en una colección NULL. El uso de cualquier otro método sobre una
colección NULL genera la excepción COLLECTION_IS_NULL.
a. EXISTS
Este método recibe como parámetro el índice de la colección que se está buscando. Por tanto,
nombre_colección.EXISTS(15) devuelve el valor TRUE si existe el elemento número 15 de la colección y devuelve
FALSE en caso contrario. El método EXISTS se aplica para asegurarse de que se van a llevar a cabo correctamente
las operaciones sobre la colección; por ejemplo, para evitar eliminar un elemento de la colección que no exista.
Cuando se consulta la existencia de un elemento que no pertenece a la colección, el método EXISTS devuelve
FALSE en lugar de generar la excepción SUBSCRIPT_OUTSIDE_LIMIT.
IF MisPedidos.EXISTS(5) THEN
MisPedidos(5):=nuevo_pedido;
END IF
b. COUNT
El método COUNT permite conocer el número de elementos de una colección. Este método se usa frecuentemente,
ya que el número de elementos contenidos en una colección no siempre se conoce de antemano, por ejemplo
cuando la colección está almacenada en la base de datos Oracle.
Debe prestar atención cuando aplique este método a una colección de tipo NESTED TABLE, ya que el número de
Contador:=1;
i:=1
WHILE(i<MisPedidos.COUNT) LOOP
IF MisPedidos.EXISTS(contador) THEN
...
i:=i+1;
END IF;
contador:=contador+1
END LOOP;
c. LIMIT
En las colecciones de tipo NESTED TABLE, que no tienen un número máximo de elementos, este método devuelve
NULL. En colecciones de tipo VARRAY el método LIMIT permite conocer el número máximo de elementos en la
colección.
Por ejemplo, el tipo TabPedidos es una colección de 50 elementos de tipo ElPedido; por tanto, si se define una
colección de tipo TabPedidos, se puede escribir el siguiente código:
i:=1
FOR contador IN 1 .. MisPedidos.LIMIT LOOP
WHILE(i<MisPedidos.COUNT) LOOP
IF MisPedidos.EXISTS(contador) THEN
...
i:=i+1;
END IF;
END LOOP;
END LOOP;
d. FIRST, LAST
Los métodos FIRST y LAST permiten conocer el índice más bajo y más alto de la colección respectivamente. Si la
colección no contiene elementos, los métodos FIRST y LAST devuelven el valor NULL. Por supuesto, si el índice
devuelto por estos dos métodos es el mismo, quiere decir que la colección solo contiene un elemento.
En una colección de tipo VARRAY, el método FIRST devuelve siempre 1 y el método LAST devuelve siempre un
valor que es igual al devuelto por COUNT.
e. PRIOR, NEXT
El método PRIOR(i) proporciona el índice del elemento anterior al elemento de índice i de la colección. El método
NEXT(i) proporciona el índice del elemento siguiente. Si el elemento de índice i es el primero, entonces llamar al
método PRIOR(i) devuelve el valor NULL. El resultado será el mismo si se llama al método NEXT estando en el
último elemento de la colección.
i:=MisPedidos.FIRST;
WHILE (i IS NOT NULL) LOOP
...
i:=MisPedidos.NEXT(i);
END LOOP;
f. EXTEND
Es posible aumentar el tamaño de una colección con el método EXTEND. Puede llamarse a este método de tres
maneras diferentes:
MiColección.EXTEND
Se añade un elemento NULL a la colección.
MiColección.EXTEND(n)
Se añaden n elementos NULL a la colección.
MiColección.EXTEND(n,i)
Se añaden n elementos a la colección. Cada elemento contiene una copia del valor contenido en el
elemento cuyo índice es i.
Ejemplo de uso de EXTEND:
SQL> DECLARE
2 TYPE LaLista IS TABLE OF VARCHAR2(50);
3 Compras LaLista;
4 BEGIN
5 Compras:=LaLista(’Tomates’,’Melón’,’Lechuga’);
6 Compras.EXTEND;
7 Compras(4):=’Jamón’;
8 END;
9 /
SQL>
g. TRIM
Este método permite eliminar uno o más elementos situados al final de la colección. Se puede usar de las dos
formas siguientes:
MiColección.TRIM
Elimina el último elemento de la colección.
Elimina los n últimos elementos de la colección.
Si el número de elementos que se intenta suprimir es mayor que el número de elementos existentes en la
colección (este valor puede conocerse a través del método COUNT) se genera la excepción SUB
SCRIPT_BEYOND_COUNT.
Ejemplo
Uso de TRIM: en el siguiente ejemplo se usa el método TRIM para eliminar los dos últimos elementos de la colección.
4 BEGIN
5 Compras:=LaLista(’Tomates’,’Melón’,’Lechuga’);
6 -- Eliminar el melón y la lechuga de la colección
7 Compras.TRIM(2);
8 -- Añadir el jamón a la colección
9 Compras.EXTEND;
10 Compras(2):=’Jamón’;
11 END;
12 /
SQL
h. DELETE
Este método, que puede invocarse de tres formas diferentes, permite eliminar un elemento, varios elementos o
todos los elementos de una colección.
MiColección.DELETE
Elimina todos los elementos de la colección.
MiColección.DELETE(n)
Elimina el elemento número n de la colección.
MiColección.DELETE(n,m)
Elimina todos los elementos de la colección cuyos índices están comprendidos entre n y m. Los
elementos con los índices n y m también se eliminan. Si n es mayor que m, no se elimina ningún
elemento.
En las colecciones de tipo VARRAY no se pueden borrar elementos situados en posiciones intermedias de la
colección. La única posibilidad es eliminar el último elemento.
Si el elemento que se ha especificado que se desea suprimir no existe, el método DELETE simplemente se salta
dicho elemento y no se genera ningún error.
Ejemplo de uso de DELETE:
SQL> DECLARE
2 TYPE LaLista IS TABLE OF VARCHAR2(50);
3 Compras LaLista;
4 BEGIN
5 Compras:=LaLista(’Tomates’,’Melon’,’Lechuga’);
6 -- Eliminar el melón
7 Compras.DELETE(2);
8 -- Añadir el jamón
9 Compras(2):=’Jamón’;
10 END;
11 /
SQL>
i. COLLECT
Esta función de cálculo de agregados permite extraer los datos de una columna y guardar el resultado en forma de
una colección. Así, es fácil trabajar con estos datos desde un bloque PL/SQL. Con esta función ya no es necesario
utilizar obligatoriamente un cursor para trabajar con datos procedentes de una consulta de extracción.
Para tener la posibilidad de trabajar con el resultado de esta función es necesario incluir la función COLLECT como
parámetro de la función CAST.
Creación de la tabla destinada a ser alimentada por COLLECT:
Type created.
SQL>
Asignación de valores de la tabla mediante la función COLLECT:
CAST(COLLECT(DESIGNACION)ASCATALOGO)
---------------------------------------------------------------
CATALOGO(’Tapiz persa’, ’Pletina láser’, ’Artículo chuchería’,
’Alfombra’, ’Lote Alfombras’, ’Alfombra china’)
6. Excepciones
En la mayoría de los casos, cuando se intenta acceder a un elemento de una colección que no existe, PL/SQL genera
una excepción predefinida. A continuación se enumeran las principales excepciones que pueden generarse:
COLLECTION_IS_NULL
La colección no está inicializada.
NO_DATA_FOUND
El elemento al que se intenta acceder no existe.
SUBSCRIPT_BEYOND_COUNT
El índice del elemento al que se intenta acceder ha sido eliminado.
SUBSCRIPT_OUTSIDE_LIMIT
El índice se encuentra fuera del rango de valores permitidos.
VALUE_ERROR
El índice es NULL o no puede convertirse en un entero.
El siguiente ejemplo ilustra la generación de estas excepciones en PL/SQL.
DECLARE
TYPE LaLista IS TABLE OF VARCHAR2(50);
Compras LaLista; -- se inicializa a NULL
BEGIN
Compras(1):=’Zanahorias’; -- excepción COLLECTION_IS_NULL
Compras:=LaLista(’Tomates’,’Melón’,’Lechuga’); --inicializa
la colección
Compras(NULL):=’Patatas’;-- excepción VALUE_ERROR
Compras(0):=’Peras’;-- excepción SUBSCRIPT_OUTSIDE_LIMIT
Compras(4):=’Kiwis’;-- excepción SUBSCRIPT_BEYOND_COUNT
Compras.DELETE(1);-- supresión del elemento 1
IF Compras(1)=’Col’ THEN-- excepción NO_DATA_FOUND
...
END IF;
END;
Cuando se pasa del motor PL/SQL al motor SQL, y viceversa, el servidor tiene una carga de trabajo mayor. Con el fin
de mejorar el rendimiento, es importante reducir el número de veces que es necesario cambiar de motor. Las copias
de datos por bloques ofrecen una solución que permite reducir el número de interacciones entre estos dos motores.
Reparto del trabajo entre los dos motores:
Con la copia por bloques, las instrucciones SQL podrán aplicarse a toda la colección y no solo de forma sucesiva a
cada uno de los elementos.
El ejemplo siguiente, en el que se insertan filas en una tabla, permite comparar el tiempo invertido con el
procesamiento clásico de las instrucciones SQL en los bloques PL/SQL y el procesamiento por bloques, el cual requiere
menos tiempo.
Creación de la tabla Componentes:
Tabla creada.
SQL>
La tabla creada en el ejemplo anterior se va a rellenar mediante un bloque PL/SQL. Se mide el tiempo de ejecución
para cada uno de los métodos de inserción de datos utilizado.
Ventajas del procesamiento por bloques:
SQL>
Para procesar todos los elementos de una colección hay que utilizar la palabra clave FORALL. Su uso se explica con
detalle más adelante.
El uso del paquete DBMS_OUTPUT se aborda más adelante en el libro. Para poder ejecutar este script correctamente
hay que configurar la variable de entorno SERVEROUTPUT a ON en SQL*Plus usando el siguiente comando: SET
SERVEROUTPUT ON.
1. FORALL
La palabra clave FORALL indica al motor de PL/SQL que debe trabajar por bloques con la colección, antes de enviar
el comando SQL al motor SQL. Aunque la palabra clave FORALL realiza un bucle de principio a fin, no puede incluirse
en ella un bucle FOR.
Sintaxis
instrucción_SQL;
La instrucción SQL tiene que ser un comando INSERT, UPDATE o DELETE, que se aplique a una colección. La
instrucción SQL puede de hecho trabajar con varias colecciones, como se ha mostrado en el ejemplo anterior. Deben
emplearse los mismos índices para los elementos de las distintas colecciones.
Ejemplo
En el siguiente ejemplo es posible observar los diferentes casos de uso de la instrucción FORALL.
SQL> DECLARE
2 TYPE tabla_numeros IS TABLE OF NUMBER(4);
3 TYPE tabla_nombres IS TABLE OF CHAR(5);
4 LosNumeros tabla_numeros:=tabla_numeros(1,2,3,4);
5 LosNombres tabla_nombres:=tabla_nombres(’Bici’,’Rueda’,
’Freno’,’Sillín’);
6 BEGIN
7 FORALL i in LosNumeros.FIRST..LosNumeros.LAST
8 INSERT INTO componentes values (LosNumeros(i),
LosNombres(i));
9 FORALL i in LosNumeros.FIRST..LosNumeros.LAST
10 DELETE FROM componentes WHERE numero=2*LosNumeros(i);
11 FORALL i in LosNumeros.FIRST..LosNumeros.LAST
12 INSERT INTO componentes values (100+LosNumeros(i),
LosNombres(i));
13 END;
14 /
SQL>
Las copias de datos por bloques pueden realizarse directamente en las colecciones de registros. Oracle9i ofrece
esta funcionalidad, la cual proporciona una mayor flexibilidad en el uso de los datos en un bloque PL/SQL.
El siguiente ejemplo muestra los diferentes usos posibles al trabajar con colecciones de registros y, especialmente, la definición
de la colección con datos procedentes de la base de datos mediante las instrucciones FOR..IN y FORALL.
declare
type tablaReg is table of tabla1%rowtype;
type tablaNumerica is table of number;
type tablaCaracteres is table of char(30);
cursor ctabla2 is select col1, col2 from tabla2;
tabreg tablaReg;
tabnum tablaNumerica:=tablaNumerica(2,3,5);
tabCar tablaCaracteres:=tablaCaracteres(’Godel’,’Escher’,’Bach’);
end;
/
select * from tabla1;
select * from tabla2;
drop table tabla1;
drop table tabla2;
a. Limitaciones
l El comando FORALL solo puede utilizarse en los programas del lado del servidor.
l Las instrucciones INSERT, UPDATE o DELETE deben hacer referencia a al menos una colección para poder sacar
partido de la instrucción FORALL.
l Para todos los valores de índices especificados en el comando FORALL deben existir elementos en la colección.
l No es posible expresar el índice utilizando un campo calculado.
b. Las transacciones y el comando FORALL
En un comando FORALL, si alguna de las instrucciones SQL provoca un error de ejecución, entonces todas las
instrucciones ejecutadas dentro del bucle FORALL quedan anuladas (ROLLBACK).
Sin embargo, si la excepción generada por una instrucción SQL se trata en el bloque PL/SQL, entonces las
operaciones realizadas en el bucle FORALL se anulan hasta un punto de salvaguarda (SAVEPOINT) de la
transacción que se establece de forma implícita después de cada instrucción SQL. Es decir, la única instrucción SQL
que se anula de forma automática es la que ha originado la excepción. En el código de tratamiento de la excepción
se decidirá si se conservan las modificaciones ya realizadas (COMMIT) o si se anula la instrucción FORALL completa
(ROLLBACK).
Para permitir a la instrucción FORALL continuar incluso en caso de error hay que añadir la cláusula SAVE
EXCEPTIONS en la instrucción FORALL.
Si una sentencia SQL falla, no se lanza ninguna excepción y la información relativa al error se almacena en la
colección SQL%BULK_EXCEPTIONS. Cuando la instrucción FORALL termina, se produce la excepción ORA24381.
Entonces es posible escribir un manejador para esta excepción que va a poder examinar el contenido de la
colección SQL%BULK_EXCEPTIONS para determinar la naturaleza de los errores y definir cómo terminar la
transacción (COMMIT o ROLLBACK).
La colección SQL%BULK_EXCEPTIONS es una colección de registros que da información acerca de los errores que
aparecen durante la ejecución de la instrucción FORALL. SQL%BULK_EXCEPTIONS.COUNT da el número de
sentencias SQL que han fallado y, para cada índice i incluido entre 1 y SQL%BULK_EXCEPTIONS.COUNT, tenemos
la siguiente información:
l SQL%BULK_EXCEPTIONS(i).ERROR_INDEX: número de la instrucción que ha fallado.
l SQL%BULK_EXCEPTIONS(i).ERROR_CODE: código del error.
c. Las cláusulas INDICES OF y VALUES OF
El recorrido de las listas no siempre es tan fácil como en el ejemplo anterior.
Para poder recorrer una lista sin necesidad de tener en cuenta el índice del primer y del último elemento es posible
utilizar la cláusula INDICES OF. Esta cláusula resultará mucho más interesante en la medida en que se encuentren
únicamente elementos de la colección aunque existan ubicaciones sin valor. La cláusula INDICES OF permite
garantizar el recorrido completo de la colección sin que se genere una excepción.
SQL> DECLARE
2 TYPE tabla_numero IS TABLE OF NUMBER(4);
3 TYPE tabla_nombre IS TABLE OF CHAR(5);
4 losNumeros
5 tabla_numero:=tabla_numero(1,2,3,4);
6 losnombres tabla_nombre :=
7 tabla_nombre(’Bici’, ’Rueda’, ’Freno’, ’Sillín’);
8 BEGIN
9 FORALL i IN INDICES OF losNumeros
10 INSERT INTO componentes values(losNumeros(i),
11 losNombres(i));
12 END;
13 /
SQL>
Por el contrario, si se quiere recorrer simplemente un subconjunto de una colección entonces es necesario utilizar
la cláusula VALUES OF. Esta cláusula permite recuperar los índices desde otra colección que debe ser de tipo
NESTED TABLE o bien una tabla asociada a un índice numérico. Esta colección es pues una colección de recorrido.
Durante la ejecución de las instrucciones SQL, el motor abre implícitamente un cursor. Pueden consultarse los
atributos %FOUND, %ISOPEN, %NOTFOUND y %ROWCOUNT para asegurarse del correcto funcionamiento de la
instrucción SQL.
El cursor implícito (SQL) posee un atributo más: %BULK_ROWCOUNT, que se usa con la instrucción FORALL. Este
atributo es en realidad una colección de tipo INDEX BY TABLE, para el que el elemento número i contiene el número
de filas afectadas por la ejecución de la instrucción SQL número i. Si ninguna fila se ha visto afectada por la
instrucción número i, entonces el atributo SQL%BULK_ROWCOUNT(i) devuelve el valor 0.
Ejemplo de uso del atributo %BULK_ROWCOUNT:
SQL>
3. BULK COLLECT
La palabra clave BULK COLLECT indica al motor SQL que los datos deben devolverse en una colección al volver al
motor PL/SQL. Este comando puede utilizarse con las cláusulas SELECT INTO, FETCH INTO y RETURNING INTO.
Sintaxis
Ejemplo
Uso de BULK COLLECT: en el siguiente ejemplo, los datos extraídos de la tabla clientes se guardan en la colección LosClientes.
SQL>
Por supuesto, se puede realizar el mismo tipo de operación con un cursor.
La mejora en el tiempo de ejecución de los bloques PL/SQL es espectacular para volúmenes de datos importantes si
se trabaja con las colecciones. Es fácil recuperar los datos de la tabla mediante la instrucción BULK COLLECT. A
continuación, el tratamiento de los datos se efectúa directamente en la colección y finalmente las modificaciones se
propagan en la tabla mediante la instrucción FORALL para recorrer la colección.
4. LIMIT
La cláusula opcional LIMIT, que solo se puede utilizar en operaciones de copia por bloques desde un cursor, permite
limitar el número de filas de datos extraídas en cada instrucción FETCH.
Sintaxis
El número de filas tiene que ser un número entero.
Ejemplo
Uso de LIMIT: en el siguiente ejemplo, las filas se extraen en paquetes de 10.
SQL> DECLARE
2 TYPE tabla_numeros IS TABLE OF clientes.numcli%type;
3 LosClientes tabla_numeros;
4 CURSOR ccli IS SELECT numcli FROM clientes;
5 BEGIN
6 OPEN ccli;
7 LOOP
8 FETCH ccli BULK COLLECT INTO LosClientes LIMIT 10;
9 EXIT WHEN CCLI%NOTFOUND;
10 END LOOP;
11 END;
12 /
SQL>
El trabajo con una colección es relativamente fácil. Pero, rápidamente, será necesario trabajar con dos o más
colecciones para comparar sus contenidos. Antes de la versión 10g de Oracle, para poder realizar este tipo de
trabajo era necesario escribir funciones propias de comparación. Este trabajo, además de ser reiterativo, presenta
el inconveniente de que la solución óptima se encuentra raramente de forma rápida, y especialmente el recorrido de
listas y los tests de comparación son fuentes de errores comunes incluso para programadores experimentados. Al
introducir instrucciones que permiten trabajar con las colecciones como conjuntos Oracle facilita el trabajo con este
tipo de estructura.
Para ilustrar el funcionamiento de cada una de estas instrucciones y comprender así su interés vamos a trabajar
sobre un pequeño ejemplo.
Ejemplos
Se creará un paquete pkg_test. Este paquete contendrá dos colecciones y un procedimiento que permite mostrar el
contenido de la colección que se pasa como parámetro.
La primera etapa consiste en definir el tipo que servirá de base para la colección.
Type created.
SQL>
A continuación, es necesario definir la cabecera del paquete.
Package created.
SQL>
SQL>
Ahora que la base está a punto, es posible ilustrar las instrucciones.
Por ejemplo, para conocer el conjunto de nuestros amigos es necesario realizar una unión entre las dos colecciones.
Para ello, se dispone de las instrucciones MULTISET UNION y MULTISET UNION DISTINCT. La distinción entre ambas
instrucciones consiste en que la primera, MULTISET UNION, devolverá el conjunto de valores contenidos en las dos
colecciones sin eliminar los duplicados, mientras que la segunda, MULTISET UNION DISTINCT, permitirá eliminar los
duplicados en la colección resultante.
SQL> DECLARE
2 nuestrosAmigos Nombres:=Nombres();
3 BEGIN
4 nuestrosAmigos:=pkg_test.misAmigos MULTISET UNION pkg_test.susAmigos;
5 pkg_test.mostrar(nuestrosAmigos);
6 END;
7 /
1-->Damian
2-->Duran
3-->Martin
4-->Damian
5-->Dalmau
6-->Sansos
7-->Luis
8-->Mariano
SQL>
El ejemplo anterior ilustra el resultado de la ejecución de la instrucción MULTISET UNION. Se observa que Damian aparece dos
veces, en primera y en cuarta posición.
Por el contrario, con la instrucción MULTISET UNION DISTINCT, los duplicados se eliminan.
SQL> DECLARE
2 nuestrosAmigos:=Nombres();
3 BEGIN
4 nuestrosAmigos:=pkg_test.misAmigos
5 MULTISET UNION DISTINCT
6 pkg_test.susAmigos;
7 pkg_test.mostrar(nuestrosAmigos);
8 END;
9 /
1-->Damian
2-->Duran
3-->Martin
4-->Dalmau
5-->Sansos
6-->Luis
7-->Mariano
SQL>
Para conocer los elementos comunes a ambas colecciones hay que utilizar MULTISET INTERSECT para definir la colección de
intersección.
SQL> DECLARE
2 nuestrosAmigos Nombres:=Nombres();
3 BEGIN
4 nuestrosAmigos:=pkg_test.misAmigos
5 MULTISET INTERSECT
6 pkg_test.susAmigos;
7 pkg_test.mostrar(nuestrosAmigos);
8 END;
9 /
1-->Damian
SQL>
El ejemplo anterior permite obtener la lista de amigos comunes.
Para poder realizar la diferencia entre dos colecciones es preciso utilizar la instrucción MULTISET EXCEPT.
SQL> DECLARE
2 losAmigos Nombres:Nombres();
3 BEGIN
4 losAmigos:=pkg_test.misAmigos;
5 MULTISET EXCEPT
6 pkg_test.susAmigos
SQL>
El ejemplo anterior permite conocer de entre la colección misAmigos solo los que no se conocen en la otra colección
(susAmigos).
Es evidente que, en esta instrucción, el orden en el que las colecciones se introducen respecto a la instrucción
posee una influencia directa en el resultado.
Por último, cuando se trabaja con colecciones de gran tamaño, es posible eliminar los duplicados produciendo una
nueva colección mediante la instrucción SET.
SQL> DECLARE
2 nuestrosAmigos Nombres:=Nombres();
3 resultado Nombres:=Nombres();
4 BEGIN
5 nuestrosAmigos:=pkg_test.misAmigos
6 MULTISET UNION
7 pkg_test.susAmigos;
8 resultado:=SET(nuestrosAmigos);
9 pkg_test.mostrar(resultado);
10 END;
11 /
1-->Damian
2-->Duran
3-->Martin
4-->Dalmau
5-->Sansos
6-->Luis
7-->Mariano
SQL>
La colección resultante de la unión de las dos colecciones iniciales posee valores duplicados. La colección resultante
producida por la instrucción SET, por su parte, no posee ninguna redundancia.
Con el fin de mejorar los tiempos de respuesta de estas funciones que devuelven conjuntos de datos, la cláusula
pipelined indica que los datos deben devolverse conforme se ejecuta la función. Además, con este tipo de función,
la gestión de los conjuntos de valores devueltos es más sencilla.
En la declaración de la función se añade la palabra clave PIPELINED en la cabecera y los datos se devuelven
mediante el comando PIPE ROW.
Estas funciones pueden aceptar como parámetro de entrada un conjunto de filas en forma de colección (por ejemplo
una tabla de tipo VARRAY) o en forma de una referencia a un cursor, REF CURSOR.
Ejemplo
El siguiente ejemplo muestra la implementación de una función que devuelve una colección de cifras en modo pipelined, es decir,
conforme se ejecuta la función:
Tipo creado.
Tipo creado.
Función creada.
SQL> -- uso de la extracción en modo pipelined
SQL> select sum(importe) as total
2 from table(LineaVal(CURSOR(select * from lineasped where
numped=1301)));
TOTAL
----------
750
SQL>
Wrap permite enmascarar el algoritmo utilizado, pero en ningún caso se cifran las cadenas de caracteres, números,
nombres de variables, columnas y tablas. Por tanto, esta utilidad no permite ocultar las contraseñas o los nombres de
las tablas.
Esta utilidad es completamente compatible dentro de Oracle. Sin embargo, la compatibilidad descendente no está
asegurada.
Esta utilidad recibe dos parámetros:
iname
Permite especificar el archivo que contiene el código PL/SQL que se va a cifrar.
oname (opcional)
Permite especificar el nombre del archivo que va a contener la versión codificada del archivo especificado
en iname. Por omisión, este archivo de salida utiliza la extensión plb.
Sintaxis
Ejemplo de uso de la utilidad Wrap
Los procedimientos PUT y PUT_LINE de este paquete permiten ubicar datos en un búfer que puede leerse desde otro
bloque PL/SQL, 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 se ignoran. El principal interés de este paquete es facilitar la depuración de los programas.
SQL*Plus dispone del parámetro SERVEROUTPUT que se activa con la instrucción SET SERVEROUTPUT ON y que
permite conocer los datos que se han escrito en el búfer.
1. ENABLE
Este procedimiento permite activar las llamadas a los procedimientos PUT, PUT_LINE, NEW_LINE, GET_LINE y
GET_LINES. Si el paquete DBMS_ OUTPUT no está activado, la llamada a este procedimiento se ignorará.
No es necesario llamar a este procedimiento cuando el parámetro SERVEROUTPUT se ha activado desde SQL*Plus.
Sintaxis
Cuando se especifica, el tamaño máximo del búfer es de 1.000.000 bytes y el mínimo de 2.000 bytes. El valor NULL
permite tener un búfer con tamaño ilimitado.
Si se realizan varias llamadas a este procedimiento, el tamaño máximo del búfer se mantiene.
2. DISABLE
Este procedimiento permite desactivar las llamadas a los procedimientos PUT, PUT_LINE, NEW_LINE, GET_LINE y
GET_LINES y elimina todos los datos contenidos en el búfer.
No es necesario llamar a este procedimiento cuando el parámetro SERVEROUTPUT se activa desde SQL*Plus.
Sintaxis
DBMS_OUTPUT.DISABLE
Este procedimiento no utiliza parámetros.
3. PUT y PUT_LINE
En todos los casos, los datos se convierten al formato de cadena de caracteres. Los datos de tipo numérico o de
fecha se formatean utilizando la función de conversión TO_CHAR y los formatos de conversión predeterminados. Si
se desean emplear otros formatos de conversión es necesario convertir dichos datos en cadenas de caracteres
antes de incluirlos en el búfer.
El procedimiento PUT_LINE permite insertar automáticamente el carácter de fin de línea después de cada adición de
datos al búfer. Por el contrario, si se usa el procedimiento PUT, habrá que incluir el carácter de fin de línea llamando
al procedimiento NEW_LINE. Los procedimientos GET_LINE y GET_LINES, que permiten leer los datos colocados en el
búfer, solo son capaces de leer las líneas de datos que terminan con un carácter de fin de línea.
Si el volumen de datos que hay que incluir en el búfer es mayor que el tamaño del mismo, entonces se genera un
error.
Sintaxis
DBMS_OUTPUT.PUT(elemento IN VARCHAR2);
DBMS_OUTPUT.PUT_LINE(elemento IN VARCHAR2);
4. NEW_LINE
Este procedimiento permite incluir un marcador de fin de línea en el búfer.
Sintaxis
DBMS_OUTPUT.NEW_LINE;
5. GET_LINE y GET_LINES
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 mediante los procedimientos GET_LINE o GET_LINES, todas las líneas que no se han leído y
que se encuentran en el búfer 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.
Sintaxis
Los parámetros son los siguientes:
línea
estado
Si la llamada se ha ejecutado correctamente, el estado toma el valor 0. Si toma el valor 1, significa que
el búfer no contiene más líneas de datos.
líneas
Es una tabla con tipos de datos VARCHAR2(32767).
num_líneas
Especifica el número de líneas de datos contenidas en la tabla.
En general, el paquete DBMS_OUTPUT se emplea para depurar funciones y procedimientos almacenados ya que,
desde SQL*Plus, la visualización de los datos incluidos en el búfer es automática.
Ejemplo
Procedimiento que utiliza el paquete DBMS_OUTPUT:
Procedimiento creado.
SQL>
A continuación, esta función se ejecuta desde SQL*Plus:
SQL> begin
2 cambiar_nombre;
3 end;
4 /
Operación realizada
SQL>
En el servidor, la ejecución del programa PL/SQL que hace uso del paquete UTL_FILE debe realizarse en un modo de
seguridad privilegiado, y existen otras opciones de seguridad que limitan las acciones llevadas a cabo a través del
paquete UTL_FILE.
Históricamente, en el lado del servidor, los directorios a los que el paquete UTL_FILE podían acceder deben
especificarse en el archivo de parámetros (INIT.ORA) usando el parámetro UTL_FILE_DIR.
Sintaxis
UTL_FILE_DIR=c:\temp.
Para hacer que UTL_FILE pueda acceder a todos los directorios del servidor hay que especificar el siguiente parámetro
en el archivo INIT.ORA: UTL_FILE_DIR=*
A partir de la versión 9i, es mejor utilizar el comando CREATE DIRECTORY para gestionar los directorios accesibles
desde el paquete UTL_FILE, en lugar del parámetro de inicialización UTL_FILE_DIR.
Es posible conocer la lista de directorios definidos en el servidor usando la vista ALL_DIRECTORIES.
Sintaxis
En el ejemplo de resumen del paquete se proporciona un ejemplo de cómo emplear esta instrucción.
1. FOPEN, FOPEN_NCHAR
Esta función permite abrir un archivo para llevar a cabo operaciones de lectura o de escritura. La ruta de acceso al
archivo debe corresponderse con un directorio válido, definido con CREATE DIRECTORY.
La ruta de acceso completa debe existir previamente y el comando FOPEN no puede crear directorios.
La función FOPEN devuelve un puntero al archivo. Este puntero debe especificarse para el conjunto de operaciones
de lectura/escritura que se realizarán a continuación.
No se pueden abrir más de 50 archivos simultáneamente.
Sintaxis
UTL_FILE.FOPEN(
ruta IN VARCHAR2,
nombre_archivo IN VARCHAR2,
modo_apertura IN VARCHAR2
tamaño_máximo_fila IN BINARY_INTEGER DEFAULT 1024)
ruta
Nombre del objeto DIRECTORY que corresponde al directorio del sistema operativo que contiene el
archivo que va a abrirse.
nombre_archivo
Nombre del archivo con su extensión, sin incluir ninguna información relativa a la ruta de acceso.
modo_apertura
Cadena de caracteres que especifica el modo de apertura del archivo. Los valores posibles son los
siguientes:
n r abre el archivo en modo lectura.
n w abre el archivo en modo escritura.
n a abre un archivo existente en modo de adición de datos.
La letra b se puede añadir al modo de apertura (rb, wb, ab) para trabajar en modo binario con el archivo.
Cuando se abre un archivo que no existe en modo de adición de datos (a), éste se crea y se abre en modo de
escritura (w).
tamaño_máximo_fila
Longitud máxima de una fila (incluido el carácter de fin de fila). Debe estar comprendida entre 1 y
32767 ; 1024 por defecto.
La función FOPEN puede generar las siguientes excepciones: INVALID_PATH, INVALID_MODE o INVALID_OPERATION.
La función FOPEN_NCHAR, que recibe los mismos parámetros que la función FOPEN, permite abrir un archivo en
modo UNICODE para realizar operaciones de lectura y de escritura. Con este método, se puede leer y escribir un
archivo en formato UNICODE en lugar de utilizar el juego de caracteres de la base de datos.
2. IS_OPEN
La función IS_OPEN tiene como objetivo comprobar si un puntero de archivo apunta a un archivo que está abierto y
que aún no se ha cerrado. La función IS_OPEN permite comprobar la validez en el nivel del paquete UTL_FILE y no
permite en ningún caso estar seguro de que la operación se llevará a cabo correctamente en el sistema operativo.
Si el puntero de archivo que se pasa como parámetro apunta a un archivo abierto, entonces la función IS_OPEN
devuelve el valor TRUE; en caso contrario, devuelve el valor FALSE.
Sintaxis
UTL_FILE.IS_OPEN(
ptr_archivo IN FILE_TYPE)
3. FCLOSE
Este procedimiento permite cerrar de manera correcta el flujo de datos a un archivo y asegurarse de que los datos
que se encuentran en el búfer de escritura están correctamente almacenados en el archivo antes de cerrar el flujo
hacia el mismo. Si todavía se encuentran datos en el búfer al cerrar el archivo, la función FCLOSE puede generar el
error WRITE_ERROR.
Sintaxis
4. FCLOSE_ALL
Este procedimiento permite cerrar todos los flujos de datos hacia archivos que se hayan abierto durante la sesión
actual. Este procedimiento solo debe ejecutarse en determinadas ocasiones, por ejemplo cuando se sale de un
bloque PL/SQL después de que se haya producido.
Además, la función FCLOSE_ALL no afecta al estado de otros punteros de archivo que esté empleando el mismo
usuario. Es decir, la función IS_OPEN seguirá devolviendo el valor TRUE al comprobar un cierto flujo de datos,
aunque cualquier intento de usar este flujo, bien en una operación de lectura, bien en una operación de escritura,
fallará.
Sintaxis
ULT_FILE.FCLOSE_ALL;
5. GET_LINE, GET_LINE_NCHAR, GET_RAW
Los procedimientos GET_LINE y GET_LINE_NCHAR permiten leer una línea completa de texto del archivo identificado
por el puntero que se ha pasado como parámetro, y devuelve esta línea de datos en una variable de búfer que
también se ha pasado como parámetro. En esta variable se almacena, por tanto, todo el texto, sin el marcador de
fin de línea o de fin de archivo (en el caso de que se solicite leer la última línea).
Este procedimiento solo se permite si el archivo se ha abierto en modo de lectura (r). En los demás casos, se genera
la excepción INVALID_OPERATION.
Si la línea es demasiado larga como para caber íntegramente en la variable búfer, entonces se genera una
excepción VALUE_ERROR. Por su parte, si se intenta leer el texto que se encuentra después del carácter de fin de
archivo, se genera una excepción de tipo NO_DATA_FOUND.
Dado que el carácter de fin de línea no se almacena en la variable de búfer, durante la lectura, una línea en blanco
se traduce en una cadena vacía.
Sintaxis
UTL_FILE.GET_LINE(
El procedimiento GET_LINE_NCHAR permite leer texto en formato UNICODE desde un archivo abierto mediante
FOPEN_NCHAR.
Sintaxis
UTL_FILE.GET_NCHAR(
ptr_archivo IN FILE_TYPE,
búfer OUT NVARCHAR2,
bytes_a_leer IN PLS_INTEGER DEFAULT NULL);
La operación GET_RAW permite leer datos de tipo RAW de un archivo.
Sintaxis
UTL_FILE.GET_LINE_RAW(
ptr_archivo IN FILE_TYPE,
búfer OUT NOCOPY RAW,
bytes_a_leer IN PLS_INTEGER DEFAULT NULL);
6. PUT, PUT_NCHAR, PUT_RAW
El procedimiento PUT permite escribir en el archivo el texto contenido en una variable de búfer. Por supuesto, el
archivo debe estar abierto en modo de escritura (w o a). El carácter de fin de línea no se añade de forma
automática. Es necesario llamar al procedimiento NEW_LINE para añadir dicho carácter o bien al procedimiento
PUT_LINE para completar la línea actual.
Sintaxis
UTL_FILE.PUT (
ptr_archivo IN FILE_TYPE,
búfer IN VARCHAR2);
El procedimiento PUT_NCHAR permite escribir una cadena de caracteres UNICODE en un archivo. El tamaño máximo
del búfer es de 32767 bytes. La sintaxis de este procedimiento es idéntica a la de PUT, excepto en que el búfer es
de tipo NVARCHAR2.
El procedimiento PUT_RAW posibilita la inserción de datos de tipo RAW en un archivo. Con el tercer parámetro se
puede especificar el vaciado automático del búfer.
Sintaxis
UTL_FILE.PUT_RAW(
ptr_archivo IN FILE_TYPE,
búfer IN RAW,
vaciadoauto IN BOOLEAN DEFAULT FALSE);
Este procedimiento permite añadir al archivo uno o más caracteres de fin de línea. Este procedimiento se emplea
para terminar una línea escrita mediante el procedimiento PUT. El número de caracteres de fin de línea que se
añaden de manera predeterminada es uno.
Sintaxis
UTL_FILE.NEW_LINE(
ptr_archivo IN FILE_TYPE,
número IN NATURAL :=1);
8. PUT_LINE, PUT_LINE_NCHAR
A diferencia del procedimiento PUT, PUT_LINE permite añadir una línea de datos y su marcador de fin de línea al
archivo. Este procedimiento se puede utilizar para escribir una línea completa o para terminar una línea cuya
escritura se ha comenzado con el procedimiento PUT.
Sintaxis
UTL_FILE.PUT_LINE(
ptr_archivo IN FILE_TYPE,
búfer IN VARCHAR2),
vaciado_auto IN BOOLEAN DEFAULT FALSE);
vaciado_auto
Indica si el búfer debe escribirse en disco immediatamente.
La función PUT_LINE_NCHAR hace lo mismo pero con una cadena de caracteres UNICODE.
9. PUTF, PUTF_NCHAR
El procedimiento PUTF se corresponde con el procedimiento PUT, pero permite dar formato a los datos. Su
funcionamiento es similar a la función printf del lenguaje C. Permite escribir cualquier texto en un archivo. Los
caracteres siguientes tienen un significado especial:
%s
Este carácter se reemplaza por el parámetro de tipo carácter que le corresponda.
\n
Este carácter incluye un marcador de fin de línea.
Sintaxis
UTL_FILE.PUTF(
ptr_archivo IN FILE_TYPE,
búfer_con_formato IN VARCHAR2[,
Los parámetros son opcionales y como máximo pueden ser cinco. El primer parámetro sustituye a la primera
aparición del carácter %s que se encuentre en el búfer, el segundo sustituye a la segunda aparición, y así
sucesivamente.
Ejemplo
DECLARE
-- TYPE ptr_archivo IS RECORD (id BINARY_INTEGER);
f_out UTL_FILE.FILE_TYPE;
BEGIN
-- abrir el archivo
f_out:=UTL_FILE.FOPEN(’directorio_archivo,’prueba.txt’,’w’);
-- escribir una línea
UTL_FILE.PUT(f_out,’Esto es un ejemplo’);
-- incluir un marcador de fin de línea
UTL_FILE.NEW_LINE(f_out);
El nombre del objeto DIRECTORY debe estar escrito en mayúsculas en la llamada a FOPEN.
El procedimiento PUTF_NCHAR permite escribir cadenas de caracteres UNICODE en el archivo de destino.
10. FFLUSH
Este procedimiento permite escribir físicamente los datos en los archivos, especialmente aquellos que se encuentran
en el búfer de escritura. En la práctica, normalmente todas las escrituras en archivo se hacen a través de un búfer
con el fin de optimizar los accesos al disco. El procedimiento FFLUSH permite vaciar este búfer. Naturalmente, los
datos deben terminar con un carácter de fin de línea.
11. FSEEK, FGETPOS
El procedimiento FSEEK permite desplazarse por el archivo hacia adelante o hacia atrás el número de bytes que se
desee. El desplazamiento puede ser absoluto, tomando el parámetro offset_absoluto el valor de la posición a la que
se desea desplazarse; el valor predeterminado es null.
En el caso de un desplazamiento relativo, es el tercer parámetro (offset_relativo) el que contiene el número de
Sintaxis
UTL_FILE.FSEEK(
ptr_archivo IN FILE_TYPE,
offset_absoluto IN PLS_INTEGER DEFAULT NULL,
offset_relativo IN PLS_INTEGER DEFAULT NULL);
La función FGETPOS permite conocer el desplazamiento correspondiente a la posición actual en el archivo.
Sintaxis
UTL_FILE.FGETPOS(ptr_archivo IN FILE_TYPE)
RETURN PLS_INTEGER;
12. FREMOVE, FCOPY, FRENAME
Estos procedimientos permiten realizar una serie de operaciones sobre los archivos del sistema operativo cuando se
ha concedido el acceso al directorio.
Estos procedimientos aportan una cierta flexibilidad en la gestión de archivos externos a la base de datos y
completan el concepto de directorio (DIRECTORY) que se ha introducido a partir de la versión 9i.
La ubicación es el nombre de un objeto de tipo DIRECTORY que se haya creado con la ayuda del método CREATE
DIRECTORY.
Sintaxis
UTL_FILE.FREMOVE(
ubicación IN VARCHAR2,
nom_archivo IN VARCHAR2);
UTL_FILE.FCOPY(
directorio_origen IN VARCHAR2,
nom_archivo_origen IN VARCHAR2,
directorio_destino IN VARCHAR2,
nom_archivo_destino IN VARCHAR2,
número_línea_inicio IN PLS_INTEGER DEFAULT 1,
número_línea_fin IN PLS_INTEGER DEFAULT NULL);
UTL_FILE_FRENAME(
directorio_origen IN VARCHAR2,
nom_archivo_origen IN VARCHAR2,
directorio_destino IN VARCHAR2,
nom_archivo_destino IN VARCHAR2,
sustituir IN BOOLEAN DEFAULT FALSE);
13. FGETATTR
Este procedimiento permite leer y devolver los atributos actuales de un archivo.
UTL_FILE.FGETATTR(
ubicación IN VARCHAR2,
nombre_archivo IN VARCHAR2,
existe OUT BOOLEAN,
tamaño_archivo OUT NUMBER,
tamaño_bloque OUT NUMBER);
14. Excepciones
Las principales excepciones que pueden generarse durante la ejecución de este paquete son:
INVALID_PATH
Ruta de acceso o nombre de archivo incorrectos.
INVALID_MODE
El modo de apertura del archivo especificado en FOPEN es incorrecto.
INVALID_FILEHANDLE
El descriptor de archivo no es válido.
INVALID_OPERATION
Se ha realizado una operación no válida con el archivo.
READ_ERROR
Se ha producido un error del sistema operativo durante una operación de lectura del archivo.
WRITE_ERROR
Se ha producido un error del sistema operativo durante una operación de escritura en el archivo.
INTERNAL_ERROR
Error PL/SQL no especificado.
CHARSETMISMATCH
Se ha abierto un archivo usando el método FOPEN_NCHAR, pero las operaciones siguientes utilizan las
funciones PUTF o GET_LINE y no su equivalente NCHAR.
FILE_OPEN
La operación ha fallado, ya que el archivo ya estaba abierto.
INVALID_MAXLINESIZE
INVALID_FILENAME
El nombre del archivo es incorrecto.
ACCESS_DENIED
Se ha denegado el permiso de acceso al archivo.
INVALID_OFFSET
Posición incorrecta. La posición pasada como parámetro del método FSEEK debe ser mayor que 0 e
inferior al número total de bytes del archivo.
DELETE_FAILED
Se ha producido un fallo al intentar borrar el archivo.
RENAME_FAILED
Se ha producido un fallo al intentar cambiar el nombre del archivo.
Los procedimientos del paquete también generan excepciones de Oracle predefinidas, como NO_DATA_FOUND o
VALUE_ERROR.
Trabajar con elementos de gran tamaño (BLOB: Binary LOB, CLOB: Character LOB, NCLOB: uNicode CLOB y BFILE:
archivo binario) no es tan sencillo como trabajar con datos de tipo más clásico (carácter, numérico, fecha). El lenguaje
PL/SQL permite trabajar con estos datos desde el momento en que se encuentran en la base de datos, pero las
operaciones de carga desde un archivo del sistema operativo, de comparación o de modificación solo podrán
realizarse mediante el paquete DBMS_LOB.
1. Constantes
Las siguientes constantes se definen en el paquete DBMS_LOB. Su uso permite aclarar el uso de las distintas
funciones y procedimientos del paquete.
2. APPEND
Este procedimiento permite añadir la totalidad de la variable LOB de origen a la variable LOB de destino.
Sintaxis
DBMS_LOB.APPEND(
destino IN OUT NOCOPY BLOB,
origen IN BLOB);
DBMS_LOB.APPEND(
destino IN OUT NOCOPY CLOB CHARACTER SET ANY_CS,
origen IN CLOB CHARACTER SET destino%CHARSET);
3. CLOSE
Este procedimiento permite cerrar un elemento LOB interno o externo que se haya abierto anteriormente.
Sintaxis
DBMS_LOB.CLOSE(
{lob_origen IN OUT NOCOPY BLOB
| lob_origen IN OUT NOCOPY CLOB CHARACTER SET ANY CS
| archivo_origen IN OUT NOCOPY BFILE);
4. COMPARE
Esta función permite comparar dos objetos LOB en su totalidad o parcialmente. Únicamente es posible comparar
La función COMPARE devuelve 0 si los dos elementos que hay que comparar son completamente idénticos o un valor
diferente de cero en caso contrario.
Sintaxis
DBMS_LOB.COMPARE(
lob1 IN {BLOB|CLOB CHARACTER SET ANY_CS|BFILE},
lob2 IN {BLOB|CLOB CHARACTER SET ANY_CS|BFILE},
número_bytes_que_comparar NUMBER :=DBMS_LOB.LOBMAXSIZE,
byte_origen1 IN INTEGER:=1,
byte_origen2 IN INTEGER:=1);
5. COPY
Este procedimiento permite realizar la copia total o parcial desde un objeto LOB de origen a un objeto LOB de
destino. En el caso de una copia parcial, es posible especificar la longitud (número de bytes) que se va a copiar, así
como las posiciones en el objeto LOB de origen y de destino.
Sintaxis
DBMS_LOB.COPY(
lob_destino IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS},
lob_origen IN{BLOB|CLOB CHARACTER SET lob_destino%CHARSET},
bytes_que_copiar IN INTEGER,
direcc_destino IN INTEGER:=1,
direcc_origen IN INTEGER:=1);
6. CREATETEMPORARY, FREETEMPORARY, ISTEMPORARY
El procedimiento CREATETEMPORARY permite crear un objeto CLOB o BLOB temporal. El espacio necesario se toma
del espacio de tablas temporal.
El parámetro caché permite indicar si el LOB debe leerse o no desde el búfer.
El tercer parámetro permite indicar si el elemento es temporal para la sesión (opción predeterminada) o para la
llamada.
Sintaxis
DBMS_LOB.CREATETEMPORARY(
lob IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS},
caché IN BOOLEAN,
validez IN VARIOS_INTEGER:=DBMS_LOB.SESSION);
El procedimiento FREETEMPORARY permite liberar un objeto temporal de tipo CLOB o BLOB que haya sido creado con
CREATETEMPORARY. Por supuesto, no es recomendable esperar a que termine la sesión o la llamada para eliminar el
objeto temporal, sino que es mejor liberar el espacio que ocupa en el espacio de tablas temporal lo antes posible
mediante el procedimiento FREETEMPORARY.
DBMS_LOB.FREETEMPORARY(
lob IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS});
Por último, la función ISTEMPORARY, que devuelve un valor booleano, permite saber si el elemento LOB que se pasa
como parámetro es temporal o no.
Sintaxis
DBMS_LOB.ISTEMPORARY(
lob IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS})
return INTEGER;
Ejemplo
El siguiente ejemplo muestra la implementación de estos tres métodos relativos a los objetos LOB temporales:
SQL> declare
2 b blob;
3 c clob;
4 begin
5 -- creación de los elementos temporales
6 dbms_lob.createtemporary(b, true);
7 dbms_lob.createtemporary(c,true);
8 -- prueba y eliminación
9 if (dbms_lob.istemporary(b)=1) then
10 dbms_lob.freetemporary(b);
11 end if;
12
13 if (dbms_lob.istemporary(c)=1) then
14 dbms_lob.freetemporary(c);
15 end if;
16 end;
17 /
7. ERASE
El objetivo de este procedimiento es borrar total o parcialmente un objeto LOB. En el caso de un borrado parcial,
debe especificarse el número de bytes que se desean borrar, así como la dirección de inicio.
Sintaxis
DBMS_LOB.ERASE(
lob IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS},
bytes_que_borrar IN OUT NOCOPY INTEGER,
El tamaño de un LOB no se reduce al realizar un borrado parcial. Para reducir su tamaño debe utilizarse el
procedimiento TRIM.
8. FILEOPEN, FILECLOSE, FILECLOSEALL e ISOPEN
El procedimiento FILEOPEN permite abrir un archivo con el fin de llevar a cabo operaciones de lectura. No es posible
escribir en un archivo abierto con este procedimiento.
Sintaxis
DBMS_LOB.FILEOPEN(
ptr_archivo IN OUT NOCOPY BFILE,
modo_apertura IN BINARY_INTEGER:=file_readonly);
Los procedimientos FILECLOSE y FILECLOSEALL permiten cerrar un determinado archivo o todos los archivos
correspondientes a objetos de gran tamaño (LOB) que se han utilizado en la sesión con ayuda del paquete
DBMS_LOB.
Sintaxis
Por último, la función ISOPEN, que devuelve un número entero, permite saber si un objeto LOB está abierto o no.
Sintaxis
DBMS_LOB.ISOPEN(
elemento_lob IN {BLOB|CLOB CHARACTER SET ANY_CS|BFILE})
RETURN INTEGER;
9. FILEEXISTS, FILEISOPEN
La función FILEEXISTS devuelve un valor entero que indica si el archivo existe o no. Es aconsejable llamar a esta
función antes de comenzar a trabajar con el archivo ya que, de este modo, se puede evitar generar algunas
excepciones. Esta función simplemente verifica la existencia física del archivo.
La función devuelve 0 si el archivo no existe físicamente y 1 en caso contrario.
Sintaxis
La función FILEISOPEN permite determinar si un puntero de archivo (objeto BFILE) corresponde a un archivo abierto
(el valor devuelto es 1) o no (el valor devuelto es 0).
Sintaxis
10. FILEGETNAME
Este procedimiento permite conocer la ubicación y el nombre del archivo correspondiente a un objeto de tipo BFILE.
Este procedimiento no especifica en ningún caso si el directorio y el archivo existen físicamente.
Sintaxis
DBMS_LOB.FILEGETNAME(
archivo IN BFILE,
directorio OUT VARCHAR2,
nombrearchivo OUT VARCHAR2);
Estos procedimientos permiten manipular fácilmente fragmentos de LOB:
l Eliminar un determinado número de bytes o de caracteres a partir de una posición dada;
l Insertar datos a partir de una posición dada;
l Mover un determinado número de bytes o caracteres a partir de una posición dada hasta una nueva posición;
l Sustituir un determinado número de bytes o caracteres a partir de una posición dada por nuevos datos.
Sintaxis
DBMS_LOB.FRAGMENT_DELETE(
lob_loc IN OUT NOCOPY {BLOB | CLOB CHARACTER SET ANY_CS},
número_bytes IN INTEGER,
dir_inicio IN INTEGER);
DBMS_LOB.FRAGMENT_INSERT(
lob_loc IN OUT NOCOPY {BLOB | CLOB CHARACTER SET ANY_CS},
número_bytes IN INTEGER,
dir_inicio IN INTEGER,
buffer {RAW | VARCHAR2 CHARACTER SET lob_loc%CHARSET);
DBMS_LOB.FRAGMENT_MOVE(
lob_loc IN OUT NOCOPY {BLOB | CLOB CHARACTER SET ANY_CS},
número_bytes IN INTEGER,
dir_inicio IN INTEGER,
dir_destino IN INTEGER);
DBMS_LOB.FRAGMENT_REPLACE(
lob_loc IN OUT NOCOPY {BLOB | CLOB CHARACTER SET ANY_CS},
número_bytes_antiguo IN INTEGER,
número_bytes_nuevo IN INTEGER,
dir_inicio IN INTEGER,
buffer {RAW | VARCHAR2 CHARACTER SET lob_loc%CHARSET);
La función GETLENGTH permite conocer el tamaño del elemento LOB, BLOB o BFILE que se pasa como parámetro. El
tamaño es un número entero que se corresponde con el número de bytes del elemento que se pasa como
parámetro.
Sintaxis
DBMS_LOB.GETLENGTH(
{lob_loc IN BLOB
| lob_loc IN CLOB CHARACTER SET ANY_CS
| file_loc IN BFILE}) RETURN INTEGER;
La función GETCHUNKSIZE permite conocer el tamaño real utilizado para almacenar los datos del objeto LOB en los
segmentos (CHUNK) de espacio físico asignados al objeto LOB. Este tamaño se expresa en bytes.
Sintaxis
DBMS_LOB.GETCHUNKSIZE(
{lob_loc IN BLOB
| lob_loc IN CLOB CHARACTER SET ANY_CS
| file_loc IN BFILE}) RETURN INTEGER;
13. INSTR
Esta función devuelve el valor entero correspondiente a la enésima aparición del elemento objetivo de la búsqueda.
El número de aparición se indica mediante el parámetro enésima y el objeto de la búsqueda se especifica en el
parámetro objeto_búsqueda. La búsqueda se inicia en el elemento LOB a partir de la dirección absoluta de inicio
especificada en el parámetro direcc_inicio y que define el número de bytes o caracteres de acuerdo con la
naturaleza del elemento LOB.
Sintaxis
DBMS_LOB.INSTR(
elemento_lob IN BLOB,
objeto_búsqueda IN RAW,
direcc_inicio IN INTEGER:=1,
enésima IN INTEGER:=1);
DBMS_LOB.INSTR(
elemento_lob IN CLOB CHARACTER SET ANY_CS,
objeto_búsqueda IN VARCHAR2 CHARACTER SET elemento_lob%CHARSET,
direcc_inicio IN INTEGER:=1,
enésima IN INTEGER:=1);
DBMS_LOB.INSTR(
elemento_lob IN BFILE,
objeto_búsqueda IN RAW,
direcc_inicio IN INTEGER:=1,
enésima IN INTEGER:=1);
Estos procedimientos permiten cargar elementos LOB contenidos en archivos en elementos LOB de la base de
datos. Se pueden especificar las direcciones absolutas donde iniciar el proceso de copia, en el caso de una carga
parcial, mediante los parámetros dir_inicio_origen y dir_inicio_destino. Estas direcciones se
expresan en bytes.
En cualquier caso, es necesario especificar el número de bytes que se van a leer del archivo para crear el elemento
LOB en memoria.
Sintaxis
DBMS_LOADFROMFILE(
lob_destino IN OUT NOCOPY BLOB,
archivo_origen IN BFILE,
número_bytes IN INTEGER,
dir_inicio_destino IN INTEGER:=1,
dir_inicio_origen IN INTEGER:=1);
DBMS_LOADBLOBFROMFILE(
lob_destino IN OUT NOCOPY BLOB,
archivo_origen IN BFILE,
número_bytes IN INTEGER,
dir_inicio_destino IN OUT INTEGER:=1,
dir_inicio_origen IN OUT INTEGER:=1);
DBMS_LOADCLOBFROMFILE(
lob_destino IN OUT NOCOPY BLOB,
archivo_origen IN BFILE,
número_bytes IN INTEGER,
dir_inicio_destino IN OUT INTEGER:=1,
dir_inicio_origen IN OUT INTEGER:=1,
juego_caracteres_org IN NUMBER,
contexto_idioma IN OUT INTEGER,
aviso OUT INTEGER);
Ejemplo
El siguiente ejemplo ilustra cómo utilizar los diferentes métodos del paquete para cargar imágenes en una tabla:
Tabla creada.
Secuencia creada.
SQL>
SQL> -- definición del directorio
SQL> create or replace directory directorio_imagen as ’C:\imagen’;
SQL>
SQL> -- carga de los datos
SQL> declare
2 archivo_in BFILE;
3 tamaño number;
4 dest_blob BLOB;
5 vid number;
6 begin
7 archivo_in:= bfilename(’DIRECTORIO_IMAGEN’,’BPR.gif’);
8
9 -- abrir el archivo
10 dbms_lob.fileopen(archivo_in, dbms_lob.file_readonly);
11
12 if (dbms_lob.fileexists(archivo_in)=1) then
13
14 -- tamaño del archivo
15 tamaño:=dbms_lob.getlength(archivo_in);
16 insert into misfotos values (seq_id_fotos.nextval, empty_blob())
17 return id,img into vid,dest_blob;
18
19 -- leer los datos y almacenarlos en una variable
20 dbms_lob.loadfromfile(dest_blob,archivo_in, tamaño);
21
22 -- insertar en la tabla
23 update misfotos set img=dest_blob where id=vid;
24 commit;
25
26 -- cerrar el archivo
27 dbms_lob.fileclose(archivo_in);
28
29 end if;
30
31 exception
32
33 when dbms_lob.invalid_argval then
34 raise_application_error(-20001,’Argumento erróneo’);
35
36 when dbms_lob.access_error then
37 raise_application_error(-20002,’Capacidad sobrepasada’);
38
39 when dbms_lob.noexist_directory then
40 raise_application_error(-20003,’El directorio no existe’);
41
42 when dbms_lob.nopriv_directory then
43 raise_application_error(-20004,’Privilegios insuficientes sobre
el directorio’);
44
45 when dbms_lob.unopened_file then
46 raise_application_error(-20007,Archivo no abierto’);
47
48 when others then
49 dbms_lob.fileclose(archivo_in);
SQL>
15. OPEN
Este procedimiento permite abrir un elemento LOB en el modo especificado (lob_readonly o lob_readwrite) en el
segundo parámetro.
Sintaxis
DBMS_LOB.OPEN(
lob_origen IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS},
modo_apertura IN BINARY_INTEGER);
16. READ
Este procedimiento permite leer todo o parte de un elemento LOB y escribir el resultado de dicha lectura en el búfer.
Sintaxis
DBMS_LOB.READ(
lob_origen IN {BLOB|BFILE},
número_bytes IN OUT NOCPY BINARY_INTEGER,
direcc_inicio INT INTEGER,
búfer OUT RAW);
DBMS_LOB.READ(
lob_origen IN CLOB CHARACTER SET ANY_CS,
número_bytes IN OUT NOCPY BINARY_INTEGER,
direcc_inicio INT INTEGER,
búfer OUT VARCHAR2 CHARACTER SET lob_origen%CHARSET);
17. SUBSTR
Esta función ofrece la posibilidad de extraer una subcadena del elemento LOB. Se trata del conjunto de bytes leídos
a partir de la dirección de inicio que se pasa como parámetro; también hay que pasar como parámetro el número de
bytes que hay que leer y que se devuelven.
Sintaxis
DBMS_LOB.SUBSTR(
lob_origen IN BLOB,
número_bytes IN INTEGER:=32767,
direcc_inicio IN INTEGER:=1) RETURN RAW;
18. TRIM
Este procedimiento permite reducir el tamaño de un elemento LOB al número de bytes o de caracteres especificado
en el parámetro nuevo_tamaño.
Sintaxis
DBMS_LOB.TRIM(
lob_origen IN OUT NOCOPY {BLOB|CLOB CHARACTER SET ANY_CS},
nuevo_tamaño IN INTEGER);
19. WRITE, WRITEAPPEND
El procedimiento WRITE permite escribir una serie de bytes en un elemento LOB interno a partir de la dirección de
inicio especificada. El procedimiento WRITE reemplaza todos los datos que ya existen en el elemento LOB objetivo a
partir de la dirección de inicio.
Sintaxis
DBMS_LOB.WRITE(
lob_destino IN OUT NOCOPY BLOB,
número_bytes IN BINARY INTEGER,
direcc_inicio IN INTEGER,
búfer IN RAW);
DBMS_LOB.WRITE(
lob_destino IN OUT NOCOPY CLOB CHARACTER SET ANY_CS,
número_bytes IN BINARY_INTEGER,
direcc_inicio IN INTEGER,
búfer IN VARCHAR2 CHARACTER SET lob_origen%CHARSET);
El procedimiento WRITEAPPEND permite escribir los datos del LOB que actualmente se encuentra en el búfer al final
del objeto LOB de destino.
Sintaxis
DBMS_LOB.WRITEAPPEND(
lob_destino IN OUT NOCOPY BLOB,
número_bytes IN BINARY_INTEGER,
búfer IN RAW);
DBMS_LOB.WRITEAPPEND(
lob_destino IN OUT NOCOPY CLOB CHARACTER SET ANY_CS,
número_bytes IN BINARY_INTEGER,
búfer IN VARCHAR2 CHARACTER SET lob_origen%CHARSET);
Las principales excepciones del paquete DBMS_LOB son:
INVALID_ARGVAL
Los argumentos de las funciones y procedimientos esperaban valores no nulos, pero algún argumento
ha tomado un valor nulo (NULL) o un valor que se encuentra fuera del dominio de definición del
argumento.
ACCESS_ERROR
Intento de escritura en un objeto LOB que sobrepasa el tamaño límite.
NOEXIST_DIRECTORY
El elemento de tipo DIRECTORY no existe para el archivo actual.
NOPRIV_DIRECTORY
El usuario no posee los permisos necesarios sobre el directorio (DIRECTORY) o sobre los archivos para
llevar a cabo las operaciones.
INVALID_DIRECTORY
La referencia al directorio (DIRECTORY) no es válida en el caso de un primer acceso, o bien el
administrador de bases de datos ha modificado su definición desde su último uso.
OPERATION_FAILED
La operación ha fallado.
UNOPENED_FILE
El archivo no está abierto.
OPEN_TOOMANY
El número de archivos abiertos simultáneamente ha alcanzado el límite superior, por lo que es
necesario cerrar algunos archivos para poder continuar.