Está en la página 1de 96

Introducción

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. 

© Éditions ENI – Todos los derechos reservados - 1-


Los triggers de bases de datos
Un  trigger  es  un  bloque  PL/SQL  asociado  a  una  tabla.  Este  bloque  se  ejecutará  cuando  se  aplique  a  la  tabla  una 
instrucción DML (INSERT, UPDATE, DELETE). 

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. 

© Éditions ENI – Todos los derechos reservados - 1-


Principio de funcionamiento de los triggers instead of 

Sintaxis 

CREATE [OR REPLACE] TRIGGER nombre_trigger


{BEFORE/AFTER/INSTEAD OF}
{INSERT/UPDATE[OF col,...]/DELETE}
ON Nombre_tabla [FOR EACH ROW]
[FOLLOWS nombre_otro_trigger[,...]]
[ENABLE/DISABLE]
[WHEN (condition)]
Bloc PL/SQLv

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). 

INSERT/UPDATE [OF col,...]/DELETE 

Instrucción asociada a la activación del trigger. Varias instrucciones pueden activar un mismo trigger y se 
combinan mediante el operador OR. 

FOR EACH ROW 

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 

- 2- © Éditions ENI – Todos los derechos reservados


relativo de ejecución de estos triggers es indeterminado. Si el orden de ejecución de estos triggers es 
importante en su aplicación, puede utilizar la cláusula FOLLOWS disponible a partir de la versión 11. Esta 
cláusula permite indicar que el trigger debe ejecutarse después de los triggers enumerados. 

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: 

CREATE TRIGGER antes_elim_cli


BEFORE DELETE
ON FLORENCIO.CLIENTES
DECLARE
...
BEGIN
...
END;

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: 

create or replace trigger post_actprecio


after update of precio
on articulos
for each row
when (old.precio > new.precio)
declare
...

© Éditions ENI – Todos los derechos reservados - 3-


begin
...
end;

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: 

SQL> alter table pedidos


2 add (usuario varchar2(30));

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: 

SQL> create or replace trigger bf_ins_pedidos


2 before insert
3 on pedidos
4 referencing new as nuevo
5 for each row
6 begin
7 select user into :nuevo.usuario from dual;
8 end;
9 /

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: 

SQL> create table stat_util(


2 usuario varchar2(30),
3 numero integer);

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 

- 4- © Éditions ENI – Todos los derechos reservados


ejecuta después de cada inserción de fila por razones de optimización en caso de que se violen las restricciones de integridad. 

Código del trigger: 

SQL> create or replace trigger af_ins_pedidos


2 after insert
3 on pedidos for each row
4 declare
5 vnumero stat_util.numero%type;
6 begin
7 -- obtener el número del usuario actual
8 select numero into vnumero
9 from stat_util
10 where usuario=:new.usuario;
11 -- actualizar el valor
12 update stat_util
13 set numero=vnumero+1
14 where usuario=:new.usuario;
15 exception
16 when no_data_found then
17 insert into stat_util (usuario, numero)
18 values (:new.usuario,1);
19 end;
20 /

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: 

SQL> create or replace trigger af_ins_delete


2 after insert or delete
3 on pedidos for each row
4 declare
5 vnumero stat_util.numero%type;
6 vutil stat_util.usuario%type;
7 begin
8 select user into vutil from dual;
9 --conocer el número actual para el usuario actual
10 select numero into vnumero

© Éditions ENI – Todos los derechos reservados - 5-


11 from stat_util
12 where usuario=vutil;
13 --actualizar el valor
14 if (inserting) then
15 vnumero:=vnumero+1;
16 end if;
17 if (deleting) then
18 vnumero:=vnumero-1;
19 end if;
20 update stat_util
21 set numero=vnumero
22 where usuario=vutil;
23 exception
24 when no_data_found then
25 if (inserting) then
26 insert into stat_util(usuario, numero)
27 values (vutil,1);
28 else
29 null;
30 end if;
31 end;
32 /

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: 

SQL> create or replace view cliorense


2 as select numcli,nomcli,direccli
3 from clientes
4 where upper(ciudad)=’ORENSE’;

Vista creada.

SQL> insert into cliorense


2 values (13,’Pedro’,’Av. del deporte’);

1 fila creada.

SQL> select * from cliorense;

NUMCLI NOMCLI DIRECCLI


---------- -------------------- ------------
15 GÓMEZ S.A. C/ Mayor
35 MARTÍN JUAN C/ Menor

- 6- © Éditions ENI – Todos los derechos reservados


SQL>

Debe definirse por tanto un trigger de tipo  INSTEAD OF para resolver este fallo y garantizar que los elementos insertados en una 


vista estén visibles a través de la misma. 

Creación y prueba del trigger: 

SQL> create or replace trigger inst_ins_cliorense


2 instead of insert
3 on cliorense for each row
4 begin
5 insert into clientes (numcli, nomcli, direccli, codpostal, ciudad)
6 values (:new.numcli, :new.nomcli, :new.direccli, 32000,’Orense’);
7 end;
8 /

Trigger creado.

SQL> insert into cliorense


2 values (13,’Pedro’,’Av. del deporte’);

1 fila creada.
SQL> select * from cliorense;

NUMCLI NOMCLI DIRECCLI


---------- -------------------- ------------------
15 GÓMEZ S.A. C/ Mayor
35 MARTÍN JUAN C/ Menor
13 Pedro Av. del deporte

SQL>

Trigger de tipo INSTEAD OF asociado a una vista (VIEW) sobre las tablas PEDIDOS y LINEASPED: 

SQL> create or replace view vpedlin (numped, fechaped, numlin,


refart, cantped) as
2 select l.numped, fechaped, numlin, refart, cantped
3 from pedidos c, lineasped l
4 where c.numped=l.numped;

Vista creada.

SQL> create or replace trigger inst_del_vpedlin


2 instead of delete
3 on vpedlin
4 for each row
5 declare
6 vcantimpl number(5);
7 begin
8 delete from lineasped
9 where numped=:old.numped
10 and numlin=:old.numlin;

© Éditions ENI – Todos los derechos reservados - 7-


11 -- si es la última fila suprimir el pedido
12 select count(*) into vcantimpl
13 from lineasped
14 where numped=:old.numped;
15 exception
16 when no_data_found then
17 delete from pedidos where numped=:old.numped;
18 end;
19 /

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 

CREATE [OR REPLACE] TRIGGER nombre_trigger


FOR {INSERT/UPDATE [OF columna, ...]/DELETE}
ON nombre_tabla
[FOLLOWS nombre_otro_trigger[, ...]]
[ENABLE/DISABLE]
[WHEN (condición)]
COMPOUND TRIGGER
[declaraciones_comunes]
[BEFORE STATEMENT IS
[declaraciones_locales]
BEGIN
instrucciones
END BEFORE STATEMENT]
[BEFORE EACH ROW IS
[declaraciones_locales]
BEGIN
instrucciones
END BEFORE EACH ROW]
[AFTER EACH ROW IS
[declaraciones_locales]
BEGIN
instrucciones
END AFTER EACH ROW]
[AFTER STATEMENT IS
[declaraciones_locales]
BEGIN
instrucciones
END AFTER STATEMENT]

- 8- © Éditions ENI – Todos los derechos reservados


END [nombre_trigger]

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. 

La cláusula  ON permite especificar la tabla afectada; a este nivel, la cláusula  FOR EACH ROW del trigger simple no 


está permitida. 

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

donde instante_activación es igual a una de las cláusulas siguientes:  BEFORE STATEMENT,  BEFORE EACH


ROW, AFTER STATEMENT y AFTER EACH ROW. 

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: 

SQL> create or replace trigger auditar_modificaciones_salariales


2 for update of salario on empleado
3 compound trigger
4 acumulado number:=0;
5 procedure mostrar(texto varchar2)
6 is
7 begin
8 dbms_output.put_line(texto);
9 end mostrar;
10 after each row is
11 incremento number;
12 begin
13 incremento:= :new.salario - :old.salario;
14 mostrar(:new.numeroempleado || ’ : ’ || incremento);
15 acumulado:= acumulado + incremento;
16 end after each row;
17 after statement is
18 begin
19 mostrar(’Total: ’ || acumulado);
20 end after statement;
21 end auditar_modificaciones_salariales
22 /

© Éditions ENI – Todos los derechos reservados - 9-


Trigger creado.

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 each row el trigger calcula la variación de salario para la fila actual, almacena este valor en 


una  variable  local  a  la  sección  (incremento)  y,  a  continuación,  la  muestra  antes  de  añadirla  a  la  variable  global 
acumulado. 

En la sección after statement el trigger solo muestra el valor de la variable global acumulado. 

Ahora podemos verificar este trigger: 

SQL> set serveroutput on


SQL> select NUMEROEMPLEADO,SALARIO from EMPLEADO;

NUMEROEMPLEADO SALARIO
----------------------------- ----------
1 1200
2 1800
SQL> update EMPLEADO set SALARIO=SALARIO * 1.01;
1: 12
2: 18
Total: 30

2 filas actualizadas.

- 10 - © Éditions ENI – Todos los derechos reservados


Triggers sobre sucesos del sistema o de usuario
Es  posible  utilizar  triggers  para  hacer  un  seguimiento  de  los  cambios  de  estado  del  sistema,  como  por  ejemplo  el 
arranque  y  el  apagado.  Estos  triggers  van  a  permitir  mejorar  la  gestión  de  la  base  de  datos  y  de  la  aplicación.  En 
efecto,  determinados  sucesos  del  sistema  o  del  usuario  tienen  una  repercusión  directa  sobre  el  rendimiento  de  la 
aplicación y/o la coherencia de los datos.  

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 

© Éditions ENI – Todos los derechos reservados - 1-


Permite conocer la lista de todos los propietarios de los objetos que se han modificado. 

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. 

- 2- © Éditions ENI – Todos los derechos reservados


ora_server_error_msg 

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 ORA­1034, ORA­1403, ORA­1422, ORA­1423 y ORA­4030, ya que son demasiado 
graves como para que el proceso pueda continuar ejecutándose. 

Sintaxis 

Create trigger nombre_trigger {AFTER|BEFORE} suceso_sistema


ON{DATABASE|SCHEMA}

© Éditions ENI – Todos los derechos reservados - 3-


Bloque PL/SQL

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. 

SQL> create or replace trigger srv_error


2 after SERVERERROR on schema
3 begin
4 insert into errores (usuario, tiempo, num_error)
5 values (ora_login_user, sysdate, ora_server_error(1));
6 end;
7 /

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 

- 4- © Éditions ENI – Todos los derechos reservados


Se produce cuando se ejecuta una asociación de estadísticas. 

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 

Create trigger nombre_trigger {AFTER|BEFORE}


suceso_usuario ON{DATABASE|SCHEMA}
Bloque PL/SQL

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 

© Éditions ENI – Todos los derechos reservados - 5-


creaciones de tablas. Todas las operaciones de creación se almacenan en la tabla INFO_TABLA que se ha creado previamente. 

SQL> create or replace trigger srv_table


2 after ddl
3 on database
4 begin
5 if (ora_dict_obj_type=’TABLE’) then
6 if (ora_sysevent=’CREATE’) then
7 insert into info_tabla(usuario,nom_tabla,creación)
8 values (ora_login_user, ora_dict_obj_name, sysdate);
9 end if;
10 end if;
11 end;
12 /

Trigger creado.

SQL>

- 6- © Éditions ENI – Todos los derechos reservados


Modificaciones en los triggers
Un trigger de base de datos no puede modificarse. Sin embargo, puede borrarse para volver a crearlo a continuación, 
o crearlo mediante la opción OR REPLACE.

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 

ALTER TRIGGER nombre_trigger {ENABLE|DISABLE};


ALTER TABLE nombre_trigger { ENABLE|DISABLE } ALL TRIGGERS;

Ejemplo 

Desactivación seguida de una reactivación de triggers: 

SQL> alter trigger bf_ins_pedidos DISABLE;

Trigger modificado.

SQL> ALTER TRIGGER bf_ins_pedidos ENABLE;

Trigger modificado.

SQL> ALTER TABLE pedidos DISABLE ALL TRIGGERS;

Tabla modificada.

SQL> ALTER TABLE pedidos ENABLE ALL TRIGGERS;

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. 

© Éditions ENI – Todos los derechos reservados - 1-


SQL> select substr(trigger_name,1,20) as nom_trigger,
2 trigger_type as type,
3 substr(base_object_type,1,8) as sobre,
4 substr(table_name,1,10) as nom_objeto,
5 substr(triggering_event,1,20) as suceso
6 from user_triggers;

NOM_TRIGGER TYPE SOBRE NOM_OBJETO SUCESO


-------------------- ---------------- --------- ---------- -------
AF_INS_PEDIDOS AFTER EACH ROW TABLE PEDIDOS INSERT
BF_INS_PEDIDOS BEFORE EACH ROW TABLE PEDIDOS INSERT
INST_INS_CLIORENSE INSTEAD OF VIEW CLIORENSE INSERT
SRV_TABLE AFTER EVENT DATABASE DDL
SRV_ERROR AFTER EVENT SCHEMA ERROR

SQL>

- 2- © Éditions ENI – Todos los derechos reservados


Procedimientos almacenados
Un procedimiento almacenado es un bloque de código PL/SQL nominado, almacenado en la base de datos y que se 
puede  ejecutar  desde  aplicaciones  u  otros  procedimientos  almacenados.  En  un  bloque  PL/SQL,  basta  con  hacer 
referencia al procedimiento por su nombre para ejecutarlo. En SQL*Plus, se puede utilizar la instrucción EXECUTE. 

Sintaxis 

CREATE [OR REPLACE] PROCEDURE nombre_procedimiento


[(parámetro {IN/OUT/IN OUT} tipo, ...)]
{IS/AS} bloque PL/SQL;

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: 

SQL> create or replace procedure elim_art (numero in char) is


2 begin
3 delete from lineasped where refart=numero;
4 delete from articulos where refart=numero;
5 end;
6 /

Procedimiento creado.

SQL>

Uso en SQL*Plus: 

© Éditions ENI – Todos los derechos reservados - 1-


SQL> execute elim_art (’AB01’)
Procedimiento PL/SQL terminado correctamente.

Uso en un bloque PL/SQL: 

DECLARE
x char;
BEGIN
...
elim_art(x);
...
END;

- 2- © Éditions ENI – Todos los derechos reservados


Funciones almacenadas
Al  igual  que  los  procedimientos,  una  función  es  un  fragmento  de  código  PL/SQL,  pero  la  función  devuelve  un  valor. 
Estas funciones almacenadas se utilizan como las funciones de Oracle. 

Sintaxis 

CREATE [OR REPLACE] FUNCTION nombre_función


[(parámetro [IN] tipo, …)]
RETURN tipo {IS/AS} Bloque PL/SQL;

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: 

CREATE FUNCTION factorial (n IN NUMBER)


RETURN NUMBER
IS BEGIN
if n = 0 then
return (1) ;
else
return ((n * factorial (n-1))) ;
end if ;
END ;

Uso en SQL*Plus: 

SQL> select factorial(5) from DUAL;

FACTORIAL(5)
------------
120

SQL>

© Éditions ENI – Todos los derechos reservados - 1-


A  partir  de  la  versión  11,  es  posible  indicar  a  Oracle  que  conserve  en  memoria  el  resultado  de  la  llamada  a  una 
función.  Cuando  se  activa  esta  funcionalidad  para  una  función,  cada  vez  que  se  llama  a  esta  función  con  valores 
diferentes  de  los  parámetros,  Oracle  almacena  en  caché  el  valor  de  los  parámetros  y  el  resultado  de  la  función. 
Cuando  más  adelante  se  llama  de  nuevo  a  esta  función  con  los  mismos  valores  de  los  parámetros,  se  recupera  el 
resultado de la caché en lugar de calcularlo de nuevo.   

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 

RESULT_CACHE [RELIES_ON (nombre_tabla_o_vista[,...])]

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 

SQL> create or replace function valor_parametro(n in number)


2 return varchar2
3 resultado_cache_relies_on (parametros)
4 is
5 resultado varchar2(40);
6 fin timestamp;
7 begin
8 -- código que simula un tiempo de ejecución largo
9 fin:= systimestamp + 2/24/60/60;
10 while systimestamp fin
11 loop
12 null;
13 end loop;
14 -- seleccionar y devolver el valor solicitado
15 select valor into resultado from parametros where codigo = n;
16 return resultado;
17 end;
18 /

Función creada.

SQL> set timing on


SQL> select valor_parametro(1) from dual;

VALOR_PARAMETRO(1)
--------------------------------------
ENI

Terminado: 00:00:01.62
SQL> select valor_parametro(1) from dual;

VALOR_PARAMETRO(1)

- 2- © Éditions ENI – Todos los derechos reservados


--------------------------------------
ENI

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 

SQL> -- creación de una función


SQL> create or replace function may(p varchar2)
2 return varchar2
3 is
4 begin
5 return upper(p);
6 end;

© Éditions ENI – Todos los derechos reservados - 3-


7 /

Función creada.

Tiempo transcurrido: 00:00:00.02


SQL>
SQL> -- Llamada a esta función en una sentencia SQL
SQL> select count(distinct may(nombre)) from employees;

COUNT(DISTINCTMAY(NOMBRE))
-----------------------
43092

Tiempo transcurrido: 00 :00 :02.05


SQL>
SQL> -- recreación de la función con la directiva UDF
SQL> create or replace function may(p varchar2)
2 return varchar2
3 is
4 pragma udf;
5 begin
6 return upper(p);
7 end;
8 /

Función creada.

Tiempo transcurrido: 00:00:00.02


SQL>
SQL> -- Llamada a la función modificada en una sentencia SQL
SQL> select count(distinct may(nombre)) from employees;

COUNT(DISTINCTMAY(NOMBRE))
-----------------------
43092

Tiempo transcurrido: 00:00:00.35

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 

SQL> -- llamada de una función definida en la cláusula WITH


SQL> with
2 function may(p varchar2)
3 return varchar2
4 is
5 begin
6 return upper(p);

- 4- © Éditions ENI – Todos los derechos reservados


7 end;
8 select count(distinct may(nombre)) from employees
9 /

COUNT(DISTINCTMAY(NOMBRE))
-----------------------
43092

Tiempo transcurrido: 00:00:00.48

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. 

© Éditions ENI – Todos los derechos reservados - 5-


Paquetes
Un paquete es un objeto del esquema que agrupa de forma lógica elementos PL/SQL relacionados tales como tipos 
de datos, funciones, procedimientos y cursores. 

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 

© Éditions ENI – Todos los derechos reservados - 1-


CREATE PACKAGE nombre_paquete AS
--Definición de tipos
--Declaraciones de variables públicas
--Prototipos de los cursores públicos
--Prototipos de los procedimientos y funciones públicas
END [nombre_paquete];

Ejemplo 

Cabecera de un paquete de gestión de clientes: 

SQL> create or replace package GESTION_CLIENTES as


2 type T_CLIREC is record (
3 NUMCLI number(4),
4 NOMBRE char(20),
5 DIREC char(20),
6 CODPOST number(5),
7 CIUDAD char(30));
8 cursor C_CLIENTES return T_CLIREC;
9 function CRE_CLI (NOMBRE char, DIREC char, CODPOST number,
10 CIUDAD char)
11 return number;
12 procedure Elim_CLI (NUMCLIENTE number);
13 end GESTION_CLIENTES;
14 /

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 

CREATE PACKAGE BODY nombre_paquete AS

- 2- © Éditions ENI – Todos los derechos reservados


--Definición de los tipos locales al paquete
--Declaraciones de las variables locales al paquete
--Implementación de los cursores públicos
--Cuerpo de los procedimientos y funciones locales al paquete
--Cuerpo de los procedimientos y funciones públicas
END [nombre_paquete];

Ejemplo 

Cuerpo del paquete para la gestión de clientes: 

SQL> create or replace package body GESTION_CLIENTES as


2 NUMERO_CLI integer; -- Definición de una variable local
3
4 -- Implementación del cursor
5 cursor C_CLIENTES return T_CLIREC is
6 select NUMCLI, NOMCLI, DIRECCLI, CODPOSTAL, CIUDAD
7 from CLIENTES
8 order by NUMCLI;
9
10 -- Función de creación de un nuevo cliente
11 function CRE_CLI (NOMBRE char, DIREC char, CODPOST number,
12 CIUDAD char)
13 return number
14 is
15 NUEVO_NUMCLI number;
16 begin
17 select C_NUMCLI.nextval into NUEVO_NUMCLI from DUAL;
18 insert into CLIENTES
19 values(NUEVO_NUMCLI, NOMBRE, DIREC, CODPOST, CIUDAD);
20 NUMERO_CLI := NUMERO_CLI + 1;
21 return NUEVO_NUMCLI;
22 end;
23
24 -- Procedimiento de eliminación de un cliente
25 procedure Elim_CLI (NUMCLIENTE number)
26 is
27 begin
28 delete from CLIENTES where NUMCLI = NUMCLIENTE;
29 end;
30 end GESTION_CLIENTES;
31 /

Cuerpo del paquete creado.

SQL>

3. Uso

Se hace referencia a los elementos de un paquete (variables, procedimientos, funciones) utilizando el nombre del 
paquete y el operador ".". 

© Éditions ENI – Todos los derechos reservados - 3-


Ejemplo 

Uso del paquete para la gestión de clientes: 

SQL> var V_NUMCLI number

SQL> begin
2 :V_NUMCLI := GESTION_CLIENTES.CRE_CLI
3 (’CESAR’,’Cava Baja’,13000,’MARBELLA’);
4 end;
5 /

Procedimiento PL/SQL terminado correctamente.

SQL> print V_NUMCLI

V_NUMCLI
----------
2003
SQL> select NUMCLI, ltrim(NOMCLI) NOMCLI, CIUDAD
2 from CLIENTES order by NUMCLI
3 /

NUMCLI NOMCLI CIUDAD


---------- ------------------------------ --------------------
13 PEDRO ORENSE
15 GÓMEZ S.A. ORENSE
20 M. GARCÍA TOLEDO
35 MARTÍN JUAN ORENSE
36 DEL PINO S.A. TOLEDO
37 E. LACALLE CACERES
100 NEWTON MADRID
138 MARTÍN JUAN MADRID
152 LACALLE CACERES
2003 CESAR MARBELLA

10 filas seleccionadas.

SQL> begin
2 GESTION_CLIENTES.ELIM_CLI(152);
3 end;
4 /

Procedimiento PL/SQL terminado correctamente.

SQL> select NUMCLI, ltrim(NOMCLI) NOMCLI, CIUDAD


2 from CLIENTES order by NUMCLI
3 /
NUMCLI NOMCLI CIUDAD
---------- ------------------------------ --------------------
13 PEDRO ORENSE
15 GÓMEZ S.A. ORENSE
20 M. GARCÍA TOLEDO
35 MARTÍN JUAN ORENSE

- 4- © Éditions ENI – Todos los derechos reservados


36 DEL PINO S.A. TOLEDO
37 E. LACALLE CACERES
100 NEWTON MADRID
138 MARTÍN JUAN MADRID
2003 CESAR MARBELLA

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: 

CURSOR nombre_cursor [(parámetro, ...)] RETURN


tipo_valor_devuelto

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.  

© Éditions ENI – Todos los derechos reservados - 5-


Transacciones autónomas
Una transacción es un conjunto de comandos SQL que constituye una unidad lógica de tratamiento. La totalidad de 
las  instrucciones  que  definen  la  unidad  deben  ejecutarse  correctamente,  o  no  se  ejecutará  ninguna  instrucción.  En 
determinadas aplicaciones es preciso ejecutar una transacción dentro de otra. 

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> create or replace procedure nombre_correcto as


2 PRAGMA AUTONOMOUS_TRANSACTION;
3 begin
4 update clientes set nomcli=initcap(nomcli);
5 commit;
6 end;
7 /
Procedimiento creado.

SQL>

Las  modificaciones  llevadas  a  cabo  por  la  transacción  autónoma  son  visibles  para  las  restantes  transacciones 

© Éditions ENI – Todos los derechos reservados - 1-


inmediatamente  después  de  su  validación  (COMMIT),  incluso  aunque  la  transacción  principal  que  haya  llamado  a  la 
transacción  autónoma  no  haya  terminado.  Las  modificaciones  también  serán  visibles  para  la  transacción  principal. 
Para que la transacción principal no pueda conocer las modificaciones realizadas, es necesario especificar el nivel de 
aislamiento de la transacción utilizando el siguiente comando: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE. 

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 /

Procedimiento PL/SQL terminado correctamente.

SQL> select numcli,nomcli from clientes;


NUMCLI NOMCLI
---------- ------------------------------
15 Gómez S.A.
20 M. García
35 Martín Juan
36 Del Pino S.A.
152 Lacalle
138 Martín Juan
37 E. Lacalle
10 Torres
12 Toni
11 Torres
100 Newton
13 Pedro

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. 

- 2- © Éditions ENI – Todos los derechos reservados


La cláusula RETURNING 

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 

INSERT INTO tabla[(columna, ...)] VALUES (expresión, ...)


RETURNING columna INTO variable

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. 

SQL> create function crearCliente(vnom char, vdir char, vcodpostal number,


vciudad char)
2 return number is
3 vnumero number(6);
4 begin
5 insert into clientes(numcli, nomcli, dircli, cod_postal, ciudad)
6 values (seq_cli.nextval, vnom, vdir, vcodpostal, vciudad)
7 returning numcli into vnumero;
8 return vnumero;
9 end;
10 /

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 

DELETE FROM tabla[WHERE ...] RETURNING columna INTO variable

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 

UPDATE tabla SET columna=valor [,... WHERE ...] RETURNING


columna[,...] INTO variable [,...]

© Éditions ENI – Todos los derechos reservados - 3-


SQL dinámico
El  SQL  dinámico  es  una  técnica  que  permite  crear  las  sentencias  SQL  de  forma  dinámica  durante  la  ejecución  del 
código PL/SQL. El SQL dinámico permite crear aplicaciones más flexibles, ya que los nombres de objeto utilizados por 
un bloque PL/SQL pueden ser desconocidos en el momento de la compilación. Por ejemplo, un procedimiento puede 
utilizar una tabla cuyo nombre sea desconocido antes de ejecutar dicho procedimiento. 

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 

EXECUTE IMMEDIATE cadena_dinámica


[ INTO {variable, ...| registro}]
[ USING [IN|OUT|IN OUT] argumento ...]
[ {RETURNING|RETURN } INTO argumento, ...]

© Éditions ENI – Todos los derechos reservados - 1-


cadena_dinámica 

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;

- 2- © Éditions ENI – Todos los derechos reservados


17 --construcción de un bloque PL/SQL anónimo
18 bloque_pl:=’BEGIN UPDATE articulos set precio=precio*0.99; END;’;
19 EXECUTE IMMEDIATE bloque_pl;
20 -- uso de la palabra clave INTO
21 consulta:=’SELECT * FROM clientes WHERE numcli=:1’;
22 EXECUTE IMMEDIATE consulta INTO vclientes USING vnumcli;
23 -- uso de la cláusula RETURNING INTO
24 consulta:=’UPDATE articulos set precio=200 WHERE refart=:1 RETURNING
precio INTO :2’;
25 EXECUTE IMMEDIATE consulta USING vrefart RETURNING INTO vprecio;
26 END;
27 /

Procedimiento PL/SQL terminado correctamente.

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 /

Procedimiento PL/SQL terminado correctamente.

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: 

© Éditions ENI – Todos los derechos reservados - 3-


SQL> CREATE OR REPLACE PROCEDURE agregar_ped(
2 vnumped IN OUT number,
3 vnumcli IN number) AS
4 BEGIN
5 SELECT seq_ped.NEXTVAL
6 INTO vnumped
7 FROM dual;
8 INSERT INTO pedidos (numped, numcli, fechaped, estadoped)
9 VALUES (vnumped, vnumcli, sysdate, ’EC’);
10 END;
11 /

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 /

Procedimiento PL/SQL terminado correctamente.

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. 

- 4- © Éditions ENI – Todos los derechos reservados


Sintaxis 

OPEN {variable_cursor | :variable_host} FOR


consulta_dinámica
[USING argumento, ...]

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’;

© Éditions ENI – Todos los derechos reservados - 5-


begin
open ccli for ’Select numcli, ciudad from clientes where ciudad=:v’
using vciudad;
loop
fetch ccli into vnum, vciudad; --extraer la siguiente fila
exit when ccli%notfound; --salir del bucle si no hay más filas
--tratar los datos
end loop;
...
end;

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;

- 6- © Éditions ENI – Todos los derechos reservados


Cuando se intenta cerrar un cursor que ya está cerrado, PL/SQL genera la excepción INVALID_CURSOR. 

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. 

create or replace procedure borrar_clientes(vnumcli number) as


begin
execute immediate ’DELETE FROM clientes WHERE numcli=’||
to_char(vnumcli);
end;

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. 

create or replace procedure borrar_clientes(vnumcli number) as


begin
execute immediate ’DELETE FROM clientes WHERE numcli= :v’
using vnumcli;
end;

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: 

create or replace procedure borrar_tabla(nombre_tabla in varchar2) as


begin
execute immediate ’DROP TABLE :t’ using nombre_tabla;
end;

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: 

© Éditions ENI – Todos los derechos reservados - 7-


CREATE PROCEDURE borrar_tabla(nombre_tabla IN VARCHAR2) AS
BEGIN
EXECUTE IMMEDIATE ’DROP TABLE ’|| nombre_tabla;
END;

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: 

consulta:=’insert into pedidos values (:x, :x, :y, :z)’;


execute immediate consulta using a,a,b,c;

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: 

bloque_plsql:=’begin agregar_ped(:x, :x, :y, :z); end;’;


execute immediate bloque_plsql using a,b,c;

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. 

SQL> CREATE OR REPLACE FUNCTION borrar_filas(


2 nombre_tabla varchar2,
3 condicion varchar2) RETURN INTEGER AS
4 BEGIN
5 EXECUTE IMMEDIATE
6 ’DELETE FROM ’||nombre_tabla||’ WHERE ’|| condicion;
7 -- se devuelve el número de filas borradas
8 RETURN SQL%ROWCOUNT;
9 END;

- 8- © Éditions ENI – Todos los derechos reservados


10 /

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:

EXECUTE_IMMEDIATE ’UPDATE clientes SET ciudad= :x’ USING NULL;

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:

SQL> CREATE OR REPLACE PROCEDURE borrar


2 (tipo VARCHAR2,
3 nombre VARCHAR2) AS
4 BEGIN
5 EXECUTE IMMEDIATE ’DROP ’||tipo||’ ’||nombre;
6 END;
7 /

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, 

© Éditions ENI – Todos los derechos reservados - 9-


se ha concedido el privilegio EXECUTE al usuario María. Cuando este usuario desee borrar la tabla CLIENTES de su 
esquema, tendrá que llamar al procedimiento borrar de la siguiente forma: 

Ejecución del procedimiento borrar desde el esquema del usuario María: 

SQL> CALL borrar(’VIEW’,’CLIORENSE’);

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: 

SQL> CREATE OR REPLACE PROCEDURE borrar


2 (tipo VARCHAR2,
3 nombre VARCHAR2)
4 AUTHID CURRENT_USER AS
5 BEGIN
6 EXECUTE IMMEDIATE ’DROP ’||tipo||’ ’||nombre;
7 END;
8 /

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

- 10 - © Éditions ENI – Todos los derechos reservados


precauciones  con  el  fin  de  evitar  que  el  usuario  se  bloquee  a  sí  mismo.  En  concreto,  nunca  debe  intentarse 
modificar  (ALTER)  o  borrar  (DROP)  un  subprograma  o  un  paquete  que  se  esté  utilizando,  como  en  el  ejemplo 
siguiente. 

create procedure calculo_ca (numcli number) as


begin
...
execute immediate ’DROP PROCEDURE calculo_ca’;
...
end;

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 

SQL> -- Definición de un procedimiento que devuelve


SQL> -- un resultado implícito
SQL> CREATE OR REPLACE PROCEDURE lista_articulos
2 IS
3 cur_articulos SYS_REFCURSOR;
4 BEGIN
5 OPEN cur_articulos FOR

© Éditions ENI – Todos los derechos reservados - 11 -


6 SELECT refart,nombre
7 FROM articulos;
8 dbms_sql.return_result(cur_articulos);
9 END lista_articulos;
10 /

Procedimiento creado.

SQL>
SQL> -- Llamada al procedimiento
SQL> EXECUTE lista_articulos

Procedimiento PL/SQL terminado con éxito.

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.). 

- 12 - © Éditions ENI – Todos los derechos reservados


Colecciones y registros
En este capítulo ya se ha visto la declaración e inicialización de colecciones y registros. Este tipo de variables son muy 
prácticas y un buen conocimiento de su uso permite ahorrar tiempo a la hora de escribir código PL/SQL. 

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 231­1. 

En las colecciones de tipo tabla anidada (nested table) el rango normal de índices va desde 1 hasta 231­1 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;

© Éditions ENI – Todos los derechos reservados - 1-


Cuando  una  función  devuelve  una  colección,  se  puede  hacer  referencia  a  un  elemento  de  la  colección  utilizando  la 
siguiente sintaxis: nombre_función(parámetro)(índice) 

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 ;

- 2- © Éditions ENI – Todos los derechos reservados


mitabla(’H’) :=10 ; -- excepción VALUE_ERROR
mitabla(10) :=1 ; --excepción SUBSCRIPT_BEYOND_COUNT
END;

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
...

-- la línea siguiente provoca un error de compilación


IF mitabla=mitabla2 THEN
...
END IF;
...
END;

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:

SQL> CREATE TYPE Tipoalmacen AS OBJECT(

© Éditions ENI – Todos los derechos reservados - 3-


2 refart char(4),
3 cantidad number(5)
4 );
5 /

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: 

SQL> CREATE TYPE TabAlmacen AS TABLE OF Tipoalmacen;


2 /

Tipo creado.

SQL>

A continuación es posible definir la tabla Deposito del siguiente modo: 

SQL> CREATE TABLE deposito(


2 cod_deposito integer,
3 nombre varchar2(30),
4 almacen tabalmacen)
5 NESTED TABLE almacen STORE AS almacen_tab;

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 /

- 4- © Éditions ENI – Todos los derechos reservados


Procedimiento PL/SQL terminado correctamente.

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:

SQL> CREATE TYPE ElPedido AS OBJECT(


2 numped number(9),
3 importe number(10,2));
4 /

Tipo creado.

© Éditions ENI – Todos los derechos reservados - 5-


SQL>

A continuación, se crea el tipo correspondiente a una tabla de 50 pedidos (número e importe). 

Creación del tipo TabPedidos: 

SQL> CREATE TYPE TabPedidos AS VARRAY(50) OF ElPedido;


2 /

Tipo creado.

SQL>

Por último, creamos la tabla Factura. 

Creación de la tabla Factura: 

SQL> CREATE TABLE Factura(


2 numfact number(9),
3 numcli number(4),
4 LosPedidos TabPedidos);

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;

- 6- © Éditions ENI – Todos los derechos reservados


20 END;
21 /

Procedimiento PL/SQL terminado correctamente.

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 /

Procedimiento PL/SQL terminado correctamente.

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));

© Éditions ENI – Todos los derechos reservados - 7-


6 descarte integer;
7 BEGIN
8 SELECT count(*) INTO descarte
9 FROM TABLE(CAST(stock_testigo AS TabStock)) as testigo,
10 TABLE(SELECT stock FROM deposito WHERE nombre=’BARCELONA’) as test
11 WHERE testigo.refart!=test.refart;
12 END;
13 /
Procedimiento PL/SQL terminado correctamente.

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 

nombre_colección.nombre_método [(parámetro, ...)]

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

- 8- © Éditions ENI – Todos los derechos reservados


elementos puede ser inferior al índice del elemento mayor, dado que este tipo de colecciones aceptan elementos 
NULL y eliminaciones. Por tanto, antes de acceder a un elemento, será necesario comprobar que existe mediante 
el comando EXISTS. 

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. 

© Éditions ENI – Todos los derechos reservados - 9-


El siguiente ejemplo muestra cómo recorrer todos los elementos de una 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 /

Procedimiento PL/SQL terminado correctamente.

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. 

- 10 - © Éditions ENI – Todos los derechos reservados


MiColección.TRIM(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 /

Procedimiento PL/SQL terminado correctamente.

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.  

© Éditions ENI – Todos los derechos reservados - 11 -


PL/SQL  conserva  el  espacio  de  memoria  utilizado  por  los  elementos  eliminados,  por  lo  que  es  posible 
reemplazarlos asignándoles un nuevo valor. 

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 /

Procedimiento PL/SQL terminado correctamente.

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: 

SQL> create type catalogo as table of varchar2(30);


2 /

Type created.

SQL>

Asignación de valores de la tabla mediante la función COLLECT: 

SQL> select cast(collect(designacion) as catalogo)


2 from articulos;

CAST(COLLECT(DESIGNACION)ASCATALOGO)
---------------------------------------------------------------
CATALOGO(’Tapiz persa’, ’Pletina láser’, ’Artículo chuchería’,
’Alfombra’, ’Lote Alfombras’, ’Alfombra china’)

- 12 - © Éditions ENI – Todos los derechos reservados


SQL>

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;

© Éditions ENI – Todos los derechos reservados - 13 -


Copia de datos por bloques
Completamente  integrados  con  el  sistema  RDBMS  de  Oracle,  el  motor  procedimental  de  PL/SQL  procesa  todas  las 
instrucciones  procedimentales  y  el  motor  de  SQL  procesa  todas  las  instrucciones  SQL.  Estos  dos  motores 
interaccionan con frecuencia, ya que el código PL/SQL trabaja con datos procedentes de la base de datos y los extrae 
mediante instrucciones SQL. 

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: 

SQL> CREATE TABLE Componentes(


2 numero number(5),
3 nombre char(5));

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: 

© Éditions ENI – Todos los derechos reservados - 1-


SQL> DECLARE
2 TYPE tabla_numeros IS TABLE OF NUMBER(4) INDEX BY BINARY_INTEGER;
3 TYPE tabla_nombres IS TABLE OF CHAR(5) INDEX BY BINARY_INTEGER;
4 LosNumeros tabla_numeros;
5 LosNombres tabla_nombres;
6 t1 number(5);
7 t2 number(5);
8 t3 number(5);
9 PROCEDURE top (t out number) IS
10 BEGIN SELECT TO_CHAR(SYSDATE,’SSSSS’) INTO t FROM DUAL; END;
11 BEGIN
12 -- Rellenar las tablas
13 FOR i in 1..5000 LOOP
14 LosNumeros(i):=i; LosNombres(i):=TO_CHAR(i);
15 END LOOP;
16 TOP(t1);
17 FOR i IN 1..5000 LOOP
18 INSERT INTO componentes VALUES(LosNumeros(i), LosNombres(i));
19 END LOOP;
20 TOP(t2);
21 FORALL i IN 1..5000
22 INSERT INTO componentes VALUES(LosNumeros(i), LosNombres(i));
23 TOP(t3);
24 DBMS_OUTPUT.PUT_LINE(’Tiempo de ejecución en segundos’);
25 DBMS_OUTPUT.PUT_LINE(’FOR ’||TO_CHAR(t2-t1));
26 DBMS_OUTPUT.PUT_LINE(’FORALL ’||TO_CHAR(t3-t2));
27 END;
28 /
Tiempo de ejecución en segundos
FOR 11
FORALL 1

Procedimiento PL/SQL terminado correctamente.

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 

- 2- © Éditions ENI – Todos los derechos reservados


FORALL índice IN límite_inferior..límite_superior

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 /

Procedimiento PL/SQL terminado correctamente

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. 

create table tabla1(col1 number, col2 char(30));


create table tabla2(col1 number, col2 char(30));

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’);

© Éditions ENI – Todos los derechos reservados - 3-


begin
-- adición de datos a la tabla1
forall i in 1..3
insert into tabla1 values (tabnum(i), tabcar(i));
-- adición de datos a la tabla de registros
select col1,col2 bulk collect into tabreg from tabla1;

-- inserción de datos en la tabla2


forall i in tabreg.first..tabreg.last
insert into tabla2 values tabreg(i);

-- actualización de los datos de tabla de registros


for i in tabreg.first..tabreg.last loop
tabreg(i).col1:=tabreg(i).col1*2;
end loop;

-- uso del cursor


open ctabla2;
fetch ctabla2 bulk collect into tabreg;
close ctabla2;

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.

- 4- © Éditions ENI – Todos los derechos reservados


Sintaxis 

FORALL índice IN límite_inferior..límite_superior SAVE EXCEPTIONS


comando_SQL;

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  ORA­24381. 
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 /

PL/SQL procedure successfully completed.

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. 

© Éditions ENI – Todos los derechos reservados - 5-


2. El atributo %BULK_ROWCOUNT

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> SET SERVEROUTPUT ON


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 UPDATE componentes set nombre=LosNombres(i) WHERE numero=LosNumeros(i);
9 DBMS_OUTPUT.PUT_LINE(’Instrucción1: ’||TO_CHAR(SQL%BULK_ROWCOUNT(1))||’
filas modificadas’);
10 END;
11 /
Instrucción1: 9 filas modificadas

Procedimiento PL/SQL terminado correctamente.

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 

... BULK COLLECT INTO nombre_colección [,nombre_colección, ...]

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> SET SERVEROUTPUT ON


SQL> DECLARE
2 TYPE tabla_numeros IS TABLE OF NUMBER(4);
3 LosClientes tabla_numeros;
4 BEGIN
5 SELECT numcli

- 6- © Éditions ENI – Todos los derechos reservados


6 BULK COLLECT INTO LosClientes
7 FROM clientes;
8 DBMS_OUTPUT.PUT_LINE (’Un número: ’||TO_CHAR(LosClientes(1)));
9 END;
10 /
Un número: 15

Procedimiento PL/SQL terminado correctamente.

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 

FETCH ... BULK COLLECT INTO ... [LIMIT número_filas]

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 /

Procedimiento PL/SQL terminado correctamente.

SQL>

© Éditions ENI – Todos los derechos reservados - 7-


5. Comparar las colecciones

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. 

Las instrucciones introducidas son:  MULTISET UNION,  MULTISET  UNION  DISTINCT, MULTISET  INTERSECT, MULTISET 


EXCEPT  y  SET.  Estas  instrucciones  representan  los  operadores  disponibles  sobre  los  conjuntos  para  efectuar  la 
unión, la intersección, la diferencia y extraer los elementos distintos. 

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. 

SQL> create or replace type Nombres


2 is table of varchar2(80);
3 /

Type created.

SQL>

A continuación, es necesario definir la cabecera del paquete. 

SQL> CREATE OR REPLACE PACKAGE pkg_test IS


2 misAmigos Nombres:=Nombres(’Damian’,
3 ’Duran’,
4 ’Martin’);
5 susAmigos Nombres:=Nombres(’Damian’,
6 ’Dalmau’,
7 ’Sansos’,
8 ’Luis’,
9 ’Mariano’);
10 procedure mostrar(lista in Nombres);
11 end;
12 /

Package created.

SQL>

- 8- © Éditions ENI – Todos los derechos reservados


A  continuación,  hay  que  definir  el  cuerpo  del  paquete  para  dar  la  definición  del  procedimiento  mostrar.  Este  procedimiento 
corresponde a un simple recorrido de colección solicitando la visualización de cada uno de los valores. 

SQL> CREATE OR REPLACE PACKAGE BODY pkg_test IS


2 PROCEDURE mostrar(lista IN Nombres) IS
3 i NUMBER;
4 BEGIN
5 FOR i IN lista.FIRST..lista.LAST LOOP
6 DBMS _OUTPUT.PUT_LINE(TO_CHAR(i)||’--’||lista(i));
7 END LOOP;
8 END;
9 END;
10 /

Package body created.

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

PL/SQL procedure successfully completed.

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. 

© Éditions ENI – Todos los derechos reservados - 9-


Esta funcionalidad se ilustra mediante el ejemplo siguiente en el que el nombre Damian solo aparece una vez. 

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

PL/SQL procedure successfully completed.

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

PL/SQL procedure successfully completed.

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

- 10 - © Éditions ENI – Todos los derechos reservados


7 pkg_test.mostrar(losAmigos);
8 END;
9 /
1-->Duran
2-->Martin

PL/SQL procedure successfully completed.

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

PL/SQL procedure successfully completed.

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. 

© Éditions ENI – Todos los derechos reservados - 11 -


Funciones y conjuntos de filas
Ahora es posible definir en PL/SQL funciones que aceptan parámetros de entrada y/o que devuelven no un solo valor, 
sino un conjunto de filas. La ventaja de estas funciones es que ya no es necesario almacenar los datos en una tabla 
temporal antes de llamar a la función para que se ejecute. Estas funciones se pueden emplear en todos los casos en 
que es posible hacer referencia a un nombre de tabla y, especialmente, en la cláusula FROM de una consulta SELECT. 

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: 

SQL> -- creación del tipo


SQL> create type ImpLinea as object(
2 numlin number(2),
3 importe number(12,2)
4 );
5 /

Tipo creado.

SQL> -- creación de la tabla


SQL> create type ImpLineasTab as table of ImpLinea;
2 /

Tipo creado.

SQL> -- ejemplo de extracción en modo pipelined


SQL> create or replace function LineaVal (clin in SYS_REFCURSOR)
2 return ImpLineasTab pipelined is
3 out_mt ImpLinea:=ImpLinea(NULL, NULL);
4 vlin lineasped%rowtype;
5 vprecio number(12,2);
6 vimporte number(12,2);
7 BEGIN
8 loop
9 fetch clin into vlin;
10 exit when clin%notfound;
11 -- precio de cada artículo
12 select precio into vprecio
13 from articulos
14 where refart=vlin.refart;
15 -- cálculo del importe de la línea

© Éditions ENI – Todos los derechos reservados - 1-


16 vimporte:=vlin.cantped*vprecio;
17 -- creación del valor de retorno
18 out_mt.numlin:=vlin.numlin;
19 out_mt.importe:=vimporte;
20 -- devolver el valor
21 pipe row(out_mt);
22 end loop;
23 return;
24 END;
25 /

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>

- 2- © Éditions ENI – Todos los derechos reservados


La utilidad Wrap
La  utilidad  Wrap  es  un  programa  que  permite  cifrar  el  código  fuente  PL/SQL.  De  este  modo,  es  posible  distribuir 
código PL/SQL sin que los usuarios puedan tener acceso al código fuente. 

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 

wrap iname=archivo_entrada [oname=archivo_salida]

Ejemplo de uso de la utilidad Wrap 

© Éditions ENI – Todos los derechos reservados - 1-


DBMS_OUTPUT
El paquete DBMS_OUTPUT permite enviar mensajes desde un procedimiento, una función, un paquete o un trigger de 
la base de datos. 

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 

DBMS_OUTPUT.ENABLE (tamaño_búfer IN INTEGER DEFAULT 20000) ;

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

© Éditions ENI – Todos los derechos reservados - 1-


Es posible incluir una fila de datos en el búfer mediante el comando PUT_LINE, o bien se pueden crear las filas de 
datos elemento por elemento, colocando los datos unos a continuación de otros en el búfer mediante el comando 
PUT.  Ambos  procedimientos  permiten  incluir  en  el  búfer  datos  de  tipo  carácter  (VARCHAR2),  de  tipo  numérico 
(NUMBER) o de tipo fecha (DATE). 

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 

DBMS_OUTPUT.GET_LINE(línea OUT VARCHAR2, estado IN OUT


INTEGER)
DBMS_OUTPUT.GET_LINES(líneas OUT CHARARR, num_líneas IN OUT
INTEGER)

Los parámetros son los siguientes: 

línea 

- 2- © Éditions ENI – Todos los derechos reservados


Almacena una única línea de datos procedente del búfer. El marcador de fin de línea no se incluye en la 
fila devuelta. La longitud máxima de esta línea es de 32767 caracteres.  

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: 

SQL> CREATE OR REPLACE PROCEDURE cambiar_nombre


2 AS
3 BEGIN
4 UPDATE CLIENTES set nomcli=UPPER(nomcli);
5 DBMS_OUTPUT.PUT_LINE(’Operación realizada’);
6 END;
7 /

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

Procedimiento PL/SQL terminado correctamente.

SQL>

© Éditions ENI – Todos los derechos reservados - 3-


El paquete UTL_FILE
El  paquete  PL/SQL  UTL_FILE  permite  a  los  programas  PL/SQL  llevar  a  cabo  operaciones  de  lectura  y  escritura  en 
archivos  de  texto  del  sistema  de  archivos.  El  flujo  de  entrada/salida  hacia  el  sistema  operativo  se  limita  a  trabajar 
únicamente con estos archivos.  

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 

CREATE OR REPLACE DIRECTORY nombre_directorio AS ’ruta’;

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)

© Éditions ENI – Todos los derechos reservados - 1-


RETURN UTL_FILE.FILE_TYPE;

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)

- 2- © Éditions ENI – Todos los derechos reservados


RETURN BOOLEAN;

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 

UTL_FILE.FCLOSE(ptr_archivo IN OUT FILE_TYPE);

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(

© Éditions ENI – Todos los derechos reservados - 3-


ptr_archivo IN FILE_TYPE,
búfer OUT NVARCHAR2,
bytes_a_leer IN PLS_INTEGER DEFAULT NULL);

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);

- 4- © Éditions ENI – Todos los derechos reservados


7. NEW_LINE

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[,

© Éditions ENI – Todos los derechos reservados - 5-


param1 IN VARCHAR2 DEFAULT NULL,
...
param5 IN VARCHAR2 DEFAULT NULL]);

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 

create directory directorio_archivo as ’c:\temp’;

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);

-- escribir una línea completa


UTL_FILE.PUT_LINE(f_out,’línea 2’);
-- escritura de 2 líneas con formato
UTL_FILE.PUTF(f_out,’Buenos días\n Ejemplo del %s\n’,’procedimiento PUTF’);
UTL_FILE.FFLUSH(f_out);
-- cerrar el archivo
UTL_FILE.FCLOSE(f_out);
END;
/

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 

- 6- © Éditions ENI – Todos los derechos reservados


bytes que hay que desplazarse. Si este número es positivo, el desplazamiento es hacia adelante; si el número es 
negativo, el desplazamiento se realizará hacia atrás.  

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. 

© Éditions ENI – Todos los derechos reservados - 7-


Sintaxis 

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 

- 8- © Éditions ENI – Todos los derechos reservados


El valor del parámetro MAX_LINESIZE durante la ejecución de FOPEN es incorrecto. Este parámetro 
debe tener un valor comprendido entre 1 y 32767. 

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. 

© Éditions ENI – Todos los derechos reservados - 9-


El paquete DBMS_LOB
La abreviatura LOB (Large OBjet) identifica a los objetos de gran tamaño. 

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.  

file_readonly CONSTANT BINARY_INTEGER :=0;


lob_readonly CONSTANT BINARY_INTEGER :=0;
lob_readwrite CONSTANT BINARY_INTEGER :=1;
lobmaxsize CONSTANT INTEGER :=18446744073709551615;
call CONSTANT PLS_INTEGER :=12;
session CONSTANT PLS_INTEGER :=10;

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 

© Éditions ENI – Todos los derechos reservados - 1-


objetos  LOB  del  mismo  tipo  (BLOB,  CLOB  o  BFILE).  Con  archivos  (BFILE),  antes  de  llevar  a  cabo  la  operación  de 
comparación, los elementos deben abrirse con FILEOPEN. 

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. 

- 2- © Éditions ENI – Todos los derechos reservados


Sintaxis 

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 /

Procedimiento PL/SQL terminado correctamente.

SQL> spool off

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,

© Éditions ENI – Todos los derechos reservados - 3-


direcc_inicio IN INTEGER:=1);

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 

DBMS_LOB.FILECLOSE(archivo IN OUT NOCOPY BFILE);


DBMS_LOB.FILECLOSEALL();

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 

DBMS_LOB.FILEEXISTS(ptr_archivo IN BFILE) RETURN INTEGER;

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 

- 4- © Éditions ENI – Todos los derechos reservados


DBMS_LOB.FILEISOPEN(ptr_archivo IN BFILE) RETURN INTEGER;

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);

11. FRAGMENT_DELETE, FRAGMENT_INSERT,  FRAGMENT_MOVE, FRAGMENT_REPLACE

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);

© Éditions ENI – Todos los derechos reservados - 5-


12. GETLENGTH, GETCHUNKSIZE

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);

- 6- © Éditions ENI – Todos los derechos reservados


14. LOADFROMFILE, LOADBLOBFROMFILE,  LOADCLOBFROMFILE

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: 

SQL> -- creación de la tabla de destinos


SQL> create table misfotos (
2 id number,
3 img blob);

Tabla creada.

SQL> create sequence seq_id_fotos;

Secuencia creada.
SQL>
SQL> -- definición del directorio
SQL> create or replace directory directorio_imagen as ’C:\imagen’;

© Éditions ENI – Todos los derechos reservados - 7-


Directorio creado.

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);

- 8- © Éditions ENI – Todos los derechos reservados


50 raise_application_error(-20008,’ERR’||SQLCODE||’ ’||SQLERRM);
51
52 end;
53 /

Procedimiento PL/SQL terminado correctamente.

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;

© Éditions ENI – Todos los derechos reservados - 9-


DBMS_LOB.SUBSTR(
lob_origen IN CLOB CHARACTER SET ANY_CS,
número_bytes IN INTEGER:=32767,
direcc_inicio IN INTEGER:=1)
RETURN VARCHAR2 CHARACTER SET lob_origen%CHARSET;

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);

- 10 - © Éditions ENI – Todos los derechos reservados


20. Excepciones

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. 

© Éditions ENI – Todos los derechos reservados - 11 -

También podría gustarte