Guía Completa de PL/SQL
Guía Completa de PL/SQL
12/01/2006
Introducción a PLSQL
12/01/2006
Programación con PL/SQL
Introducción
31/03/2006
Fundamentos de PL/SQL
Primeros pasos con PL/SQL
31/03/2006
Tipos de datos en PL/SQL
31/03/2006
Operadores en PL/SQL
01/04/2006
Estructuras de control en PL/SQL
Estrcuturas de control de flujo
Sentencia GOTO
Bucles
04/03/2006
Bloques PL/SQL
Estructura de un Bloque
Sección de Declaración de Variables
23/05/2006
Cursores en PL/SQL
Introducción a cursores PL/SQL
24/05/2006
Cursores Implicitos en PL/SQL
Declaración de cursores implicitos.
Excepciones asociadas a los cursores implicitos.
24/05/2006
Cursores Explicitos en PL/SQL
Declaración de cursores explicitos
Atributos de cursores
Manejo del cursor
01/06/2006
Cursores de actualización en PL/SQL
Declaración y utiización de cursores de actualización.
28/06/2006
Excepciones en PL/SQL
Manejo de excepciones
Excepciones predefinidas
Excepciones definidas por el usuario
Reglas de Alcance
La sentencia RAISE
Uso de SQLCODE y SQLERRM
17/10/2006
Excepciones personalizadas en PL/SQL
RAISE APPLICATION ERROR
28/06/2006
Propagacion de excepciones en PL/SQL
10/07/2006
Subprogramas en PL/SQL
28/06/2006
Procedimientos almacenados en PL/SQL
10/07/2006
Funciones en PL/SQL28/06/2006
Triggers en PL/SQL
Declaración de triggers
Orden de ejecución de los triggers
Restricciones de los triggers
Utilización de :OLD y :NEW
Utilización de predicados de los triggers: INSERTING, UPDATING y DELETING
10/07/2006
Subprogramas en bloques anónimos
13/07/2006
Paquetes en PL/SQL
14/07/2006
Registros PL/SQL
Declaración de un registro.
Declaración de registros con el atributo %ROWTYPE
14/07/2006
Tablas PL/SQL
Declaración de tablas de PL/SQL
Tablas PL/SQL de registros
Funciones para el manejo de tablas PL/SQL
17/07/2006
Tipo VARRAY
Definición de VARRAYS
Varrays en la base de datos
21/02/2007
BULK COLLECT
20/07/2006
Transacciones con PL/SQL
24/07/2006
Transacciones autónomas
24/07/2006
SQL Dinamico
Sentencias DML con SQL dinamico
Cursores con SQL dinámico
21/07/2006
Funciones integradas de PL/SQL
SYSDATE
NVL
DECODE
TO_DATE
TO_CHAR
TO_NUMBER
TRUNC
LENGTH
INSTR
REPLACE
SUBSTR
UPPER
LOWER
ROWIDTOCHAR
RPAD
LPAD
RTRIM
LTRIM
TRIM
MOD
26/07/2006
Secuencias
26/07/2006
PL/SQL y Java
Creacion de Objetos Java en la base de datos ORACLE.
Ejecución de programas Java con PL/SQL
PL SQL
SQL es un lenguaje de consulta para los sistemas de bases de datos relaciónales, pero que no
posee la potencia de los lenguajes de programación.
Para abordar el presente tutorial con mínimo de garantias es necesario conocer previamente
SQL.
PL/SQL amplia SQL con los elementos caracteristicos de los lenguajes de programación,
variables, sentencias de control de flujo, bucles ...
Cuando se desea realizar una aplicación completa para el manejo de una base de datos
relacional, resulta necesario utilizar alguna herramienta que soporte la capacidad de consulta
del SQL y la versatilidad de los lenguajes de programación tradicionales. PL/SQL es el lenguaje
de programación que proporciona Oracle para extender el SQL estándar con otro tipo de
instrucciones.
Para poder seguir este tutorial correctamente necesitaremos tener los siguientes elementos:
Programación con PL/SQL
Introducción
SQL es un lenguaje de consulta para los sistemas de bases de datos relaciónales, pero que no
posee la potencia de los lenguajes de programación. No permite el uso de variables, estructuras
de control de flujo, bucles ... y demás elementos caracteristicos de la programación. No es de
extrañar, SQL es un lenguaje de consulta, no un lenguaje de programación.
Sin embargo, SQL es la herramienta ideal para trabajar con bases de datos. Cuando se desea
realizar una aplicación completa para el manejo de una base de datos relacional, resulta
necesario utilizar alguna herramienta que soporte la capacidad de consulta del SQL y la
versatilidad de los lenguajes de programación tradicionales. PL/SQL es el lenguaje de
programación que proporciona Oracle para extender el SQL estándar con otro tipo de
instrucciones y elementos propios de los lenguajes de programación .
Con PL/SQL vamos a poder programar las unidades de programa de la base de datos
ORACLE, están son:
Procedimientos almacenados
Funciones
Triggers
Scripts
Pero además PL/SQL nos permite realizar programas sobre las siguientes herramientas de
ORACLE:
Oracle Forms
Oracle Reports
Oracle Graphics
Oracle Aplication Server
Fundamentos de PL/SQL
Primeros pasos con PL/SQL
Como introducción vamos a ver algunos elementos y conceptos básicos del lenguaje.
-- Linea simple
/*
Conjunto de Lineas
*/
PL/SQL proporciona una variedad predefinida de tipos de datos . Casi todos los tipos de
datos manejados por PL/SQL son similares a los soportados por SQL. A continuación se
muestran los TIPOS de DATOS más comunes:
CHAR (Caracter): Almacena datos de tipo caracter con una longitud maxima de 32767
y cuyo valor de longitud por default es 1
-- CHAR [(longitud_maxima)]
nombre CHAR(20);
/* Indica que puede almacenar valores alfanuméricos de 20
posiciones */
-- VARCHAR2 (longitud_maxima)
nombre VARCHAR2(20);
/* Indica que puede almacenar valores alfanuméricos de hasta 20
posicones */
/* Cuando la longitud de los datos sea menor de 20 no se
rellena con blancos */
hay_error BOOLEAN;
Existen por supuesto más tipos de datos, la siguiente tabla los muestra:
Tipo de dato /
Oracle 8i Oracle 9i Descripción
Sintáxis
Donde p es la precisión y e la escala.
Operadores en PL/SQL
IF (expresion) THEN
-- Instrucciones
ELSIF (expresion) THEN
-- Instrucciones
ELSE
-- Instrucciones
END IF;
Sentencia GOTO
PL/SQL dispone de la sentencia GOTO. La sentencia GOTO desvia el flujo de ejecució a una
determinada etiqueta.
En PL/SQL las etiquetas se indican del siguiente modo: << etiqueta >>
DECLARE
flag NUMBER;
BEGIN
flag :=1 ;
IF (flag = 1) THEN
GOTO paso2;
END IF;
<<paso1>>
dbms_output.put_line('Ejecucion de paso 1');
<<paso2>>
dbms_output.put_line('Ejecucion de paso 2');
END;
Bucles
El bucle LOOP, se repite tantas veces como sea necesario hasta que se fuerza su salida con
la instrucción EXIT. Su sintaxis es la siguiente
LOOP
-- Instrucciones
IF (expresion) THEN
-- Instrucciones
EXIT;
END IF;
END LOOP;
-- Instrucciones
END LOOP;
Bloques PL/SQL
Un programa de PL/SQL está compuesto por bloques. Un programa está compuesto como
mínimo de un bloque.
Bloques anónimos
Subprogramas
Estructura de un Bloque
Los bloques PL/SQL presentan una estructura específica compuesta de tres partes bien
diferenciadas:
La sección declarativa en donde se declaran todas las constantes y variables que se van
a utilizar en la ejecución del bloque.
La sección de ejecución que incluye las instrucciones a ejecutar en el bloque PL/SQL.
La sección de excepciones en donde se definen los manejadores de errores que
soportará el bloque PL/SQL.
Cada una de las partes anteriores se delimita por una palabra reservada, de modo que un
bloque PL/SQL se puede representar como sigue:
[ declare | is | as ]
/*Parte declarativa*/
begin
/*Parte de ejecucion*/
[ exception ]
/*Parte de excepciones*/
end;
DECLARE
/*Parte declarativa*/
nombre_variable DATE;
BEGIN
/*Parte de ejecucion
* Este código asigna el valor de la columna "nombre_columna"
* a la variable identificada por "nombre_variable"
*/
SELECT SYSDATE
INTO nombre_variable
FROM DUAL;
EXCEPTION
/*Parte de excepciones*/
WHEN OTHERS THEN
dbms_output.put_line('Se ha producido un error');
END;
En esta parte se declaran las variables que va a necesitar nuestro programa. Una variable se
declara asignandole un nombre o "identificador" seguido del tipo de valor que puede contener.
También se declaran cursores, de gran utilidad para la consulta de datos, y excepciones
definidas por el usuario. También podemos especificar si se trata de una constante, si puede
contener valor nulo y asignar un valor inicial.
tipo_dato: es el tipo de dato que va a poder almacenar la variable, este puede ser
cualquiera de los tipos soportandos por ORACLE, es
decir NUMBER , DATE , CHAR , VARCHAR, VARCHAR2, BOOLEAN ... Además para
algunos tipos de datos (NUMBER y VARCHAR) podemos especificar la longitud.
La cláusula CONSTANT indica la definición de una constante cuyo valor no puede ser
modificado. Se debe incluir la inicialización de la constante en su declaración.
La cláusula NOT NULL impide que a una variable se le asigne el valor nulo, y por tanto
debe inicializarse a un valor diferente de NULL.
Las variables que no son inicializadas toman el valor inicial NULL.
La inicialización puede incluir cualquier expresión legal de PL/SQL, que lógicamente
debe corresponder con el tipo del identificador definido.
Los tipos escalares incluyen los definidos en SQL más los tipos VARCHAR y BOOLEAN.
Este último puede tomar los valores TRUE, FALSE y NULL, y se suele utilizar para almacenar el
resultado de alguna operación lógica. VARCHAR es un sinónimo de CHAR.
También es posible definir el tipo de una variable o constante, dependiendo del tipo de
otro identificador, mediante la utilización de las cláusulas %TYPE y %ROWTYPE. Mediante la
primera opción se define una variable o constante escalar, y con la segunda se define una
variable fila, donde identificador puede ser otra variable fila o una tabla. Habitualmente se
utiliza %TYPEpara definir la variable del mismo tipo que tenga definido un campo en una tabla
de la base de datos, mientras que%ROWTYPE se utiliza para declarar varibales utilizando
cursores.
Ejemplos:
DECLARE
/* Se declara la variable de tipo VARCHAR2(15) identificada por v_location y se le asigna
el valor "Granada"*/
/*Se declara la variable del mismo tipo que tenga el campo nombre de la tabla
tabla_empleados
identificada por v_nombre y no se le asigna ningún valor */
v_nombre tabla_empleados.nombre%TYPE;
BEGIN
/*Parte de ejecucion*/
EXCEPTION
/*Parte de excepciones*/
END;
Estructura de un subprograma:
/*Se declara la variable del mismo tipo que tenga el campo nombre de la tabla
tabla_empleados
identificada por v_nombre y no se le asigna ningún valor */
v_nombre tabla_empleados.nombre%TYPE;
BEGIN
/*Parte de ejecucion*/
EXCEPTION
/*Parte de excepciones*/
END;
Cursores en PL/SQL
Introducción a cursores PL/SQL
declare
cursor c_paises is
SELECT CO_PAIS, DESCRIPCION
FROM PAISES;
begin
/* Sentencias del bloque ...*/
end;
Para procesar instrucciones SELECT que devuelvan más de una fila, son necesarios cursores
explicitos combinados con un estructura de bloque.
Un cursor admite el uso de parámetros. Los parámetros deben declararse junto con el cursor.
declare
cursor c_paises (p_continente IN VARCHAR2) is
SELECT CO_PAIS, DESCRIPCION
FROM PAISES
Deben tenerse en cuenta los siguientes puntos cuando se utilizan cursores implicitos:
declare
vdescripcion VARCHAR2(50);
begin
SELECT DESCRIPCION
INTO vdescripcion
from PAISES
WHERE CO_PAIS = 'ESP';
end;
Los cursores implicitos sólo pueden devolver una fila, por lo que pueden producirse
determinadas excepciones. Las más comunes que se pueden encontrar
son no_data_found y too_many_rows. La siguiente tabla explica brevemente estas
excepciones.
Excepcion Explicacion
NO_DATA_FOUND Se produce cuando una sentencia SELECT intenta recuperar datos pero ninguna fila satisface sus
condiciones. Es decir, cuando "no hay datos"
TOO_MANY_ROWS Dado que cada cursor implicito sólo es capaz de recuperar una fila , esta excepcion detecta la
existencia de más de una fila.
Cursores de actualización
Declaración y utiización de cursores de actualización.
Los cursores de actualización se declarán igual que los cursores explicitos, añadieno FOR
UPDATE al final de la sentencia select.
CURSOR nombre_cursor IS
instrucción_SELECT
FOR UPDATE
Para actualizar los datos del cursor hay que ejecutar una sentencia UPDATE especificando la
clausula WHERE CURRENT OF<cursor_name>.
DECLARE
CURSOR cpaises IS
select CO_PAIS, DESCRIPCION, CONTINENTE
from paises
FOR UPDATE;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
FETCH cpaises INTO co_pais,descripcion,continente;
WHILE cpaises%found
LOOP
UPDATE PAISES
SET CONTINENTE = CONTINENTE || '.'
WHERE CURRENT OF cpaises;
END;
Cuando trabajamos con cursores de actualización debemos tener en cuenta las siguientes
consideraciones:
Excepciones en PL/SQL
Manejo de excepciones
Las excepciones se controlan dentro de su propio [Link] estructura de bloque de una
excepción se muestra a continuación.
DECLARE
-- Declaraciones
BEGIN
-- Ejecucion
EXCEPTION
-- Excepcion
END;
Cuando ocurre un error, se ejecuta la porción del programa marcada por el
bloque EXCEPTION, transfiriéndose el control a ese bloque de sentencias.
DECLARE
-- Declaraciones
BEGIN
-- Ejecucion
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Se ejecuta cuando ocurre una excepcion de tipo NO_DATA_FOUND
WHEN ZERO_DIVIDE THEN
-- Se ejecuta cuando ocurre una excepcion de tipo ZERO_DIVIDE
END;
Si existe un bloque de excepcion apropiado para el tipo de excepción se ejecuta dicho
bloque. Si no existe un bloque de control de excepciones adecuado al tipo de excepcion se
ejecutará el bloque de excepcion WHEN OTHERS THEN (si existe!). WHEN OTHERS debe
ser el último manejador de excepciones.
Las excepciones pueden ser definidas en forma interna o explícitamente por el usuario.
Ejemplos de excepciones definidas en forma interna son la división por cero y la falta de
memoria en tiempo de ejecución. Estas mismas condiciones excepcionales tienen sus propio
tipos y pueden ser referenciadas por ellos: ZERO_DIVIDE y STORAGE_ERROR.
Las excepciones definidas por el usuario deben ser alcanzadas explícitamente utilizando la
sentencia RAISE.
Con las excepciones se pueden manejar los errores cómodamente sin necesidad de
mantener múltiples chequeos por cada sentencia escrita. También provee claridad en el
código ya que permite mantener las rutinas correspondientes al tratamiento de los errores de
forma separada de la lógica del negocio.
Excepciones predefinidas
PL/SQL proporciona un gran número de excepciones predefinidas que permiten controlar las
condiciones de error más habituales.
Las excepciones predefinidas no necesitan ser declaradas. Simplemente se utilizan cuando
estas son lanzadas por algún error determinado.
La siguiente es la lista de las excepciones predeterminadas por PL/SQL y una breve
descripción de cuándo son accionadas:
PL/SQL permite al usuario definir sus propias excepciones, las que deberán ser declaradas y
lanzadas explícitamente utilizando la sentencia RAISE.
DECLARE
-- Declaraciones
MyExcepcion EXCEPTION;
BEGIN
-- Ejecucion
EXCEPTION
-- Excepcion
END;
Reglas de Alcance
Una excepcion es válida dentro de su ambito de alcance, es decir el bloque o programa
donde ha sido declarada. Las excepciones predefinidas son siempre válidas.
Como las variables, una excepción declarada en un bloque es local a ese bloque y global a
todos los sub-bloques que comprende.
La sentencia RAISE
DECLARE
-- Declaramos una excepcion identificada por VALOR_NEGATIVO
VALOR_NEGATIVO EXCEPTION;
valor NUMBER;
BEGIN
-- Ejecucion
valor := -1;
IF valor < 0 THEN
RAISE VALOR_NEGATIVO;
END IF;
EXCEPTION
-- Excepcion
Estas funciones son muy útiles cuando se utilizan en el bloque de excepciones, para aclarar
el significado de la excepción OTHERS.
Estas funciones no pueden ser utilizadas directamente en una sentencia SQL, pero sí se
puede asignar su valor a alguna variable de programa y luego usar esta última en alguna
sentencia.
DECLARE
err_num NUMBER;
err_msg VARCHAR2(255);
result NUMBER;
BEGIN
SELECT 1/0 INTO result
FROM DUAL;
EXCEPTION
WHEN OTHERS THEN
err_num := SQLCODE;
err_msg := SQLERRM;
DBMS_OUTPUT.put_line('Error:'||TO_CHAR(err_num));
DBMS_OUTPUT.put_line(err_msg);
END;
DECLARE
msg VARCHAR2(255);
BEGIN
msg := SQLERRM(-1403);
DBMS_OUTPUT.put_line(MSG);
END;
RAISE_APPLICATION_ERROR(<error_num>,<mensaje>);
Siendo:
DECLARE
v_div NUMBER;
BEGIN
SELECT 1/0 INTO v_div FROM DUAL;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'No se puede dividir por cero');
END;
En el caso de que no se encuentre ningún manejador válida el control del programa se
desplaza hasta el bloque EXCEPTION del bloque que ha realizado la llamada PL/SQL.
Observemos el siguiente bloque de PL/SQL (Notese que se ha añadido una clausula WHERE
1=2 para provocar una excepcionNO_DATA_FOUND).
DECLARE
fecha DATE;
FUNCTION fn_fecha RETURN DATE
IS
fecha DATE;
BEGIN
SELECT SYSDATE INTO fecha
FROM DUAL
WHERE 1=2;
RETURN fecha;
EXCEPTION
WHEN ZERO_DIVIDE THEN
dbms_output.put_line('EXCEPCION ZERO_DIVIDE CAPTURADA
EN fn_fecha');
END;
BEGIN
fecha := fn_fecha();
dbms_output.put_line('La fecha es '||TO_CHAR(fecha, 'DD/MM/YYYY'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('EXCEPCION NO_DATA_FOUND CAPTURADA EN
EL BLOQUE PRINCIPAL');
END;
Subprogramas en PL/SQL
Como hemos visto anteriormente los bloques de PL/SQL pueden ser bloques anónimos
(scripts) y subprogramas.
Los subprogramas son bloques de PL/SQL a los que asignamos un nombre identificativo y
que normalmente almacenamos en la propia base de datos para su posterior ejecución.
Procedimientos almacenados.
Funciones.
Triggers.
Subprogramas en bloques anonimos.
Procedimientos almacenados
Un procedimiento es un subprograma que ejecuta una acción especifica y que no devuelve
ningún valor. Un procedimiento tiene un nombre, un conjunto de parámetros (opcional) y un
bloque de código.
Debemos especificar el tipo de datos de cada parámetro. Al especificar el tipo de dato
del parámetro no debemos especificar la longitud del tipo.
Los parámetros pueden ser de entrada (IN), de salida (OUT) o de entrada salida (IN OUT).
El valor por defecto es IN, y se toma ese valor en caso de que no especifiquemos nada.
CREATE OR REPLACE
PROCEDURE Actualiza_Saldo(cuenta NUMBER,
new_saldo NUMBER)
IS
-- Declaracion de variables locales
BEGIN
-- Sentencias
UPDATE SALDOS_CUENTAS
SET SALDO = new_saldo,
FX_ACTUALIZACION = SYSDATE
WHERE CO_CUENTA = cuenta;
END Actualiza_Saldo;
También podemos asignar un valor por defecto a los parámetros, utilizando la
clausula DEFAULT o el operador de asiganción (:=) .
CREATE OR REPLACE
PROCEDURE Actualiza_Saldo(cuenta NUMBER,
new_saldo NUMBER DEFAULT 10 )
IS
-- Declaracion de variables locales
BEGIN
-- Sentencias
UPDATE SALDOS_CUENTAS
SET SALDO = new_saldo,
FX_ACTUALIZACION = SYSDATE
WHERE CO_CUENTA = cuenta;
END Actualiza_Saldo;
Una vez creado y compilado el procedimiento almacenado podemos ejecutarlo. Si el sistema
nos indica que el procedimiento se ha creado con errores de compilación podemos ver estos
errores de compilacion con la orden SHOW ERRORS en SQL *Plus.
BEGIN
Actualiza_Saldo(200501,2500);
COMMIT;
END;
BEGIN
Actualiza_Saldo(cuenta => 200501,new_saldo => 2500);
COMMIT;
END;
Funciones en PL/SQL
Una función es un subprograma que devuelve un valor.
return(result);
[EXCEPTION]
-- Sentencias control de excepcion
END [<fn_name>];
Ejemplo:
CREATE OR REPLACE
FUNCTION fn_Obtener_Precio(p_producto VARCHAR2)
RETURN NUMBER
IS
result NUMBER;
BEGIN
SELECT PRECIO INTO result
FROM PRECIOS_PRODUCTOS
WHERE CO_PRODUCTO = p_producto;
return(result);
EXCEPTION
WHEN NO_DATA_FOUND THEN
return 0;
END ;
Si el sistema nos indica que el la función se ha creado con errores de compilación podemos
ver estos errores de compilacion con la orden SHOW ERRORS en SQL *Plus.
DECLARE
Valor NUMBER;
BEGIN
Valor := fn_Obtener_Precio('000100');
END;
SELECT CO_PRODUCTO,
DESCRIPCION,
fn_Obtener_Precio(CO_PRODUCTO)
FROM PRODUCTOS;
Triggers
Declaración de triggers
Un trigger es un bloque PL/SQL asociado a una tabla, que se ejecuta como consecuencia de
una determinada instrucción SQL (una operación DML: INSERT, UPDATE o DELETE) sobre
dicha tabla.
Los triggers pueden definirse para las operaciones INSERT, UPDATE o DELETE, y
pueden ejecutarse antes o después de la operación. El modificador BEFORE AFTER indica que
el trigger se ejecutará antes o despues de ejecutarse la sentencia SQL definida por DELETE
INSERT UPDATE. Si incluimos el modificador OF el trigger solo se ejecutará cuando la
sentencia SQL afecte a los campos incluidos en la lista.
El alcance de los disparadores puede ser la fila o de orden. El modificador FOR EACH ROW
indica que el trigger se disparará cada vez que se realizan operaciones sobre una fila de la
tabla. Si se acompaña del modificador WHEN, se establece una restricción; el trigger solo
actuará, sobre las filas que satisfagan la restricción.
Valor Descripción
INSERT, DELETE, UPDATE Define qué tipo de orden DML provoca la activación del disparador.
BEFORE , AFTER Define si el disparador se activa antes o después de que se ejecute la orden.
Los disparadores con nivel de fila se activan una vez por cada fila afectada por la
orden que provocó el disparo. Los disparadores con nivel de orden se activan sólo
FOR EACH ROW
una vez, antes o después de la orden. Los disparadores con nivel de fila se
identifican por la cláusula FOR EACH ROW en la definición del disparador.
La cláusula WHEN sólo es válida para los disparadores con nivel de fila.
Dentro del ambito de un trigger disponemos de las variables OLD y NEW . Estas variables se
utilizan del mismo modo que cualquier otra variable PL/SQL, con la salvedad de que no
es necesario declararlas, son de tipo %ROWTYPE y contienen una copia del registro antes
(OLD) y despues(NEW) de la acción SQL (INSERT, UPDATE, DELTE) que ha ejecutado el
trigger. Utilizando esta variable podemos acceder a los datos que se están insertando,
actualizando o borrando.
Una misma tabla puede tener varios triggers. En tal caso es necesario conocer el orden en el
que se van a ejecutar.
El cuerpo de un trigger es un bloque PL/SQL. Cualquier orden que sea legal en un bloque
PL/SQL, es legal en el cuerpo de un disparador, con las siguientes restricciones:
Dentro del ambito de un trigger disponemos de las variables OLD y NEW . Estas variables se
utilizan del mismo modo que cualquier otra variable PL/SQL, con la salvedad de que no
es necesario declararlas, son de tipo %ROWTYPE y contienen una copia del registro antes
(OLD) y despues(NEW) de la acción SQL (INSERT, UPDATE, DELTE) que ha ejecutado el
trigger. Utilizando esta variable podemos acceder a los datos que se están insertando,
actualizando o borrando.
ACCION
OLD NEW
SQL
No definido; todos los campos toman valor Valores que serán insertados cuando se complete la
INSERT
NULL. orden.
Valores originales de la fila, antes de la Nuevos valores que serán escritos cuando se complete la
UPDATE
actualización. orden.
DELETE Valores, antes del borrado de la fila. No definidos; todos los campos toman el valor NULL.
Los registros OLD y NEW son sólo válidos dentro de los disparadores con nivel de fila.
Podemos usar OLD y NEW como cualquier otra variable PL/SQL.
Dentro de un disparador en el que se disparan distintos tipos de órdenes DML (INSERT,
UPDATE y DELETE), hay tres funciones booleanas que pueden emplearse para determinar de
qué operación se trata. Estos predicados son INSERTING, UPDATING y DELETING.
Este tipo de subprogramas son menos conocidos que los procedimientos almacenados,
funciones y triggers, pero son enormemente útiles.
DECLARE
idx NUMBER;
FUNCTION fn_multiplica_x2(num NUMBER)
RETURN NUMBER
IS
result NUMBER;
BEGIN
result := num *2;
return result;
END fn_multiplica_x2;
BEGIN
FOR idx IN 1..10
LOOP
dbms_output.put_line
('Llamada a la funcion ... '||TO_CHAR(fn_multiplica_x2(idx)));
END LOOP;
END;
Notese que se utiliza la funcion TO_CHAR para convertir el resultado de la función
fn_multiplica_x2 (numérico) en alfanumérico y poder mostrar el resultado por pantalla.
Packages en PL/SQL
Un paquete es una estructura que agrupa objetos de PL/SQL compilados(procedures,
funciones, variables, tipos ...) en la base de datos. Esto nos permite agrupar la funcionalidad de
los procesos en programas.
Lo primero que debemos tener en cuenta es que los paquetes están formados por dos
partes: la especificación y el cuerpo. La especificación del un paquete y su cuerpo se crean
por separado.
La especificación es la interfaz con las aplicaciones. En ella es posible declarar los tipos,
variables, constantes, excepciones, cursores y subprogramas disponibles para su uso posterior
desde fuera del paquete. En la especificación del paquete sólo se declaran los objetos
(procedures, funciones, variables ...), no se implementa el código. Los objetos declarados en la
especificación del paquete son accesibles desde fuera del paquete por otro script de PL/SQL o
programa. Haciendo una analogía con el mundo de C, la especificación es como el archivo de
cabecera de un programa en C.
El cuerpo el laimplementación del paquete. El cuerpo del paquete debe implementar lo que
se declaró inicialmente en la especificación. Es el donde debemos escribir el código de los
subprogramas. En el cuerpo de un package podemos declarar nuevos subprogramas y tipos,
pero estos seran privados para el propio package.
Es posible modificar el cuerpo de un paquete sin necesidad de alterar por ello la
especificación del mismo.
Los paquetes pueden llegar a ser programas muy complejos y suelen almacenar gran parte
de la lógica de negocio.
Registros PL/SQL
Cuando vimos los tipos de datos, omitimos intencionadamente ciertos tipos de datos.
Estos son:
Registros
Tablas de PL
VARRAY
Declaración de un registro.
Un registnslpwdro es una estructura de datos en PL/SQL, almacenados en campos, cada uno
de los cuales tiene su propio nombre y tipo y que se tratan como una sola unidad lógica.
Los campos de un registro pueden ser inicializados y pueden ser definidos como NOT NULL.
Aquellos campos que no sean inicializados explícitamente, se inicializarán a NULL.
El siguiente ejemplo crea un tipo PAIS, que tiene como campos el código, el nombre y el
continente.
);
Los registros son un tipo de datos, por lo que podremos declarar variables de dicho tipo de
datos.
DECLARE
*/
miPAIS PAIS;
BEGIN
/* Asignamos valores a los campos de la variable.
*/
miPAIS.CO_PAIS := 27;
[Link] := 'ITALIA';
[Link] := 'EUROPA';
END;
Los registros pueden estar anidados. Es decir, un campo de un registro puede ser de un tipo
de dato de otro registro.
DECLARE
TYPE PAIS IS RECORD
(CO_PAIS NUMBER ,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20)
);
TYPE MONEDA IS RECORD
( DESCRIPCION VARCHAR2(50),
PAIS_MONEDA PAIS );
miPAIS PAIS;
miMONEDA MONEDA;
BEGIN
/* Sentencias
*/
END;
Pueden asignarse todos los campos de un registro utilizando una sentencia SELECT. En este
caso hay que tener cuidado en especificar las columnas en el orden conveniente según la
declaración de los campos del registro. Para este tipo de asignación es muy frecuente el uso del
atributo %ROWTYPE que veremos más adelante.
Puede asignarse un registro a otro cuando sean del mismo tipo:
DECLARE
miPAIS.CO_PAIS := 27;
[Link] := 'ITALIA';
[Link] := 'EUROPA';
otroPAIS := miPAIS;
END;
Declaración de registros con el atributo %ROWTYPE
Se puede declarar un registro basándose en una colección de columnas de una tabla, vista o
cursor de la base de datos mediante el atributo %ROWTYPE.
DECLARE
miPAIS PAISES%ROWTYPE;
BEGIN
/* Sentencias ... */
END;
De esta forma se crea el registro de forma dinamic y se podrán asignar valores a los campos
de un registro a través de un select sobre la tabla, vista o cursor a partir de la cual se creo el
registro.
Tablas PL/SQL
Declaración de tablas de PL/SQL
Las tablas de PL/SQL son tipos de datos que nos permiten almacenar varios valores del
mismo tipo de datos.
Es similar a un array
Tiene dos componenetes: Un índice de tipo BINARY_INTEGER que permite acceder a
los elementos en la tabla PL/SQL y una columna de escalares o registros que contiene
los valores de la tabla PL/SQL
Puede incrementar su tamaño dinámicamente.
Una vez que hemos definido el tipo, podemos declarar variables y asignarle valores.
DECLARE
/* Definimos el tipo PAISES como tabla PL/SQL */
TYPE PAISES IS TABLE OF NUMBER INDEX BY BINARY_INTEGER ;
/* Declaramos una variable del tipo PAISES */
tPAISES PAISES;
BEGIN
tPAISES(1) := 1;
tPAISES(2) := 2;
tPAISES(3) := 3;
END;
No es posible inicializar las tablas en la inicialización.
El rango de binary integer es –2147483647.. 2147483647, por lo tanto el índice puede ser
negativo, lo cual indica que el índice del primer valor no tiene que ser necesariamente el cero.
Es posible declarar elementos de una tabla PL/SQL como de tipo registro.
DECLARE
tPAISES(1).CO_PAIS := 27;
tPAISES(1).DESCRIPCION := 'ITALIA';
tPAISES(1).CONTINENTE := 'EUROPA';
END;
Cuando trabajamos con tablas de PL podemos utilizar las siguientes funciones:
DECLARE
TYPE ARR_CIUDADES IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := 'MADRID';
misCiudades(2) := 'BILBAO';
misCiudades(3) := 'MALAGA';
FOR i IN [Link]..[Link]
LOOP
dbms_output.put_line(misCiudades(i));
END LOOP;
END;
EXISTS(i). Utilizada para saber si en un cierto índice hay almacenado un valor.
Devolverá TRUE si en el índice i hay un valor.
DECLARE
TYPE ARR_CIUDADES IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := 'MADRID';
misCiudades(3) := 'MALAGA';
FOR i IN [Link]..[Link]
LOOP
IF [Link](i) THEN
dbms_output.put_line(misCiudades(i));
ELSE
dbms_output.put_line('El elemento no existe:'||TO_CHAR(i));
END IF;
END LOOP;
END;
DECLARE
TYPE ARR_CIUDADES IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := 'MADRID';
misCiudades(3) := 'MALAGA';
/* Devuelve 2, ya que solo hay dos elementos con valor */
dbms_output.put_line(
'El número de elementos es:'||[Link]);
END;
DECLARE
TYPE ARR_CIUDADES IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := 'MADRID';
misCiudades(3) := 'MALAGA';
/* Devuelve 1, ya que el elemento 2 no existe */
dbms_output.put_line(
'El elemento previo a 3 es:' || [Link](3));
END;
DECLARE
TYPE ARR_CIUDADES IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
misCiudades ARR_CIUDADES;
BEGIN
misCiudades(1) := 'MADRID';
misCiudades(3) := 'MALAGA';
/* Devuelve 3, ya que el elemento 2 no existe */
dbms_output.put_line(
'El elemento siguiente es:' || [Link](1));
END;
VARRAYS
Definición de VARRAYS.
Un varray se manipula de forma muy similar a las tablas de PL, pero se implementa de forma
diferente. Los elementos en el varray se almacenan comenzando en el índice 1 hasta la longitud
máxima declarada en el tipo varray.
Una consideración a tener en cuenta es que en la declaración de un varray el tipo de datos
no puede ser de los siguientes tipos de datos:
BOOLEAN
NCHAR
NCLOB
NVARCHAR(n)
REF CURSOR
TABLE
VARRAY
DECLARE
/* Declaramos el tipo VARRAY de cinco elementos VARCHAR2*/
TYPE t_cadena IS VARRAY(5) OF VARCHAR2(50);
/* Asignamos los valores con un constructor */
v_lista t_cadena:= t_cadena('Aitor', 'Alicia', 'Pedro','','');
BEGIN
v_lista(4) := 'Tita';
v_lista(5) := 'Ainhoa';
END;
El tamaño de un VARRAY podrá aumentarse utilizando la función EXTEND, pero nunca con
mayor dimensión que la definida en la declaración del tipo. Por ejemplo, la variable v_lista que
sólo tiene 3 valores definidos por lo que se podría ampliar hasta cinco elementos pero no más
allá.
Un VARRAY comparte con las tablas de PL todas las funciones válidas para ellas, pero añade
las siguientes:
Los VARRAYS pueden almacenarse en las columnas de la base de datos. Sin embargo, un
varray sólo puede manipularse en su integridad, no pudiendo modificarse sus elementos
individuales de un varray.
Para poder crear tablas con campos de tipo VARRAY debemos crear el VARRAY como un
objeto de la base de datos.
CREATE [OR REPLACE]
TYPE <nombre_tipo> IS VARRAY (<tamaño_maximo>) OF <tipo_elementos>;
Una vez que hayamos creado el tipo sobre la base de datos, podremos utilizarlo como un
tipo de datos más en la creacion de tablas, declaración de variables ....
BULK COLLECT
PL/SQL nos permite leer varios registros en una tabla de PL con un único acceso a través
de la instrucción BULK COLLECT.
Esto nos permitirá reducir el número de accesos a disco, por lo que optimizaremos el
rendimiento de nuestras aplicaciones. Como contrapartida el consumo de memoria será mayor.
DECLARE
TYPE t_descripcion IS TABLE OF [Link]%TYPE;
TYPE t_continente IS TABLE OF [Link]%TYPE;
v_descripcion t_descripcion;
v_continente t_continente;
BEGIN
SELECT DESCRIPCION,
CONTINENTE
BULK COLLECT INTO v_descripcion, v_continente
FROM PAISES;
FOR i IN v_descripcion.FIRST .. v_descripcion.LAST LOOP
dbms_output.put_line(v_descripcion(i) || ', ' || v_continente(i));
END LOOP;
END;
/
DECLARE
TYPE PAIS IS RECORD (CO_PAIS NUMBER ,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20));
TYPE t_paises IS TABLE OF PAIS;
v_paises t_paises;
BEGIN
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
BULK COLLECT INTO v_paises
FROM PAISES;
FOR i IN v_paises.FIRST .. v_paises.LAST LOOP
dbms_output.put_line(v_paises(i).DESCRIPCION ||
', ' || v_paises(i).CONTINENTE);
END LOOP;
END;
/
Tambien podemos utilizar el atributo ROWTYPE.
DECLARE
TYPE t_paises IS TABLE OF PAISES%ROWTYPE;
v_paises t_paises;
BEGIN
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
BULK COLLECT INTO v_paises
FROM PAISES;
FOR i IN v_paises.FIRST .. v_paises.LAST LOOP
dbms_output.put_line(v_paises(i).DESCRIPCION ||
', ' || v_paises(i).CONTINENTE);
END LOOP;
END;
/
Transacciones
Una transacción es un conjunto de operaciones que se ejecutan en una base de datos, y que
son tratadas como una única unidad lógica por el SGBD.
Es decir, una transacción es una o varias sentencias SQL que se ejecutan en una base de
datos como una única operación, confirmandose o deshaciendose en grupo.
No todas las operaciones SQL son transaccionales. Sólo son transaccionales las operaciones
correspondiente al DML, es decir, sentencias SELECT, INSERT, UPDATE y DELETE
En una transacción los datos modificados no son visibles por el resto de usuarios hasta que
se confirme la transacción.
DECLARE
importe NUMBER;
ctaOrigen VARCHAR2(23);
ctaDestino VARCHAR2(23);
BEGIN
importe := 100;
ctaOrigen := '2530 10 2000 1234567890';
ctaDestino := '2532 10 2010 0987654321';
UPDATE CUENTAS SET SALDO = SALDO - importe
WHERE CUENTA = ctaOrigen;
UPDATE CUENTAS SET SALDO = SALDO + importe
WHERE CUENTA = ctaDestino;
INSERT INTO MOVIMIENTOS
(CUENTA_ORIGEN, CUENTA_DESTINO,IMPORTE, FECHA_MOVIMIENTO)
VALUES
(ctaOrigen, ctaDestino, importe*(-1), SYSDATE);
INSERT INTO MOVIMIENTOS
(CUENTA_ORIGEN, CUENTA_DESTINO,IMPORTE, FECHA_MOVIMIENTO)
VALUES
(ctaDestino,ctaOrigen, importe, SYSDATE);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Error en la transaccion:'||SQLERRM);
dbms_output.put_line('Se deshacen las modificaciones);
ROLLBACK;
END;
Si alguna de las tablas afectadas por la transacción tiene triggers, las operaciones que realiza
el trigger están dentro del ambito de la transacción, y son confirmadas o deshechas
conjuntamente con la transacción.
Durante la ejecución de una transacción, una segunda transacción no podrá ver los cambios
realizados por la primera transacción hasta que esta se confirme.
Transacciones autónomas
En ocasiones es necesario que los datos escritos por parte de una transacción sean
persistentes a pesar de que la transaccion se deshaga con ROLLBACK.
DECLARE
producto PRECIOS%TYPE;
BEGIN
producto := '100599';
INSERT INTO PRECIOS
(CO_PRODUCTO, PRECIO, FX_ALTA)
VALUES
(producto, 150, SYSDATE);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
Grabar_Log(SQLERRM);
ROLLBACK;
/* Los datos grabados por "Grabar_Log" se escriben en la base
de datos a pesar del ROLLBACK, ya que el procedimiento está
marcado como transacción autonoma.
*/
END;
Es muy común que, por ejemplo, en caso de que se produzca algún tipo de error
queramos insertar un registro en una tabla de log con el error que se ha produccido y hacer
ROLLBACK de la transacción. Pero si hacemos ROLLBACK de la transacción tambien lo hacemos
de la insertción del log.
SQL Dinamico
Sentencias DML con SQL dinamico
Podemos obtener información acerca de número de filas afectadas por la instrucción
ejecutada por EXEXUTE IMMEDIATE utilizandoSQL%ROWCOUNT.
DECLARE
ret NUMBER;
FUNCTION fn_execute RETURN NUMBER IS
sql_str VARCHAR2(1000);
BEGIN
sql_str := 'UPDATE DATOS SET NOMBRE = ''NUEVO NOMBRE''
WHERE CODIGO = 1';
EXECUTE IMMEDIATE sql_str;
RETURN SQL%ROWCOUNT;
END fn_execute ;
BEGIN
ret := fn_execute();
dbms_output.put_line(TO_CHAR(ret));
END;
Podemos además parametrizar nuestras consultas a través de variables host. Una variable
host es una variable que pertenece al programa que está ejecutando la sentencia SQL dinámica
y que podemos asignar en el interior de la sentencia SQL con la palabra claveUSING . Las
variables host van precedidas de dos puntos ":".
El siguiente ejemplo muestra el uso de variables host para parametrizar una sentencia SQL
dinamica.
DECLARE
ret NUMBER;
FUNCTION fn_execute (nombre VARCHAR2, codigo NUMBER) RETURN NUMBER
IS
sql_str VARCHAR2(1000);
BEGIN
sql_str := 'UPDATE DATOS SET NOMBRE = :new_nombre
WHERE CODIGO = :codigo';
EXECUTE IMMEDIATE sql_str USING nombre, codigo;
RETURN SQL%ROWCOUNT;
END fn_execute ;
BEGIN
ret := fn_execute('Devjoker',1);
dbms_output.put_line(TO_CHAR(ret));
END;
DECLARE
str_sql VARCHAR2(255);
l_cnt VARCHAR2(20);
BEGIN
str_sql := 'SELECT count(*) FROM PAISES';
EXECUTE IMMEDIATE str_sql INTO l_cnt;
dbms_output.put_line(l_cnt);
END;
Trabajar con cursores explicitos es también muy fácil. Únicamente destacar el uso de REF
CURSOR para declarar una variable para referirnos al cursor generado con SQL dinamico.
DECLARE
TYPE CUR_TYP IS REF CURSOR;
c_cursor CUR_TYP;
fila PAISES%ROWTYPE;
v_query VARCHAR2(255);
BEGIN
v_query := 'SELECT * FROM PAISES';
OPEN c_cursor FOR v_query;
LOOP
FETCH c_cursor INTO fila;
EXIT WHEN c_cursor%NOTFOUND;
dbms_output.put_line([Link]);
END LOOP;
CLOSE c_cursor;
END;
DECLARE
TYPE cur_typ IS REF CURSOR;
c_cursor CUR_TYP;
fila PAISES%ROWTYPE;
v_query VARCHAR2(255);
codigo_pais VARCHAR2(3) := 'ESP';
BEGIN
SYSDATE
NVL
Devuelve el valor recibido como parámetro en el caso de que expresión sea NULL,o
expresión en caso contrario.
NVL(<expresion>, <valor>)
El siguiente ejemplo devuelve 0 si el precio es nulo, y el precio cuando está informado:
DECODE
Es muy común escribir la función DECODE identada como si se tratase de un bloque IF.
TO_DATE
Convierte una expresión al tipo fecha. El parámetro opcional formato indica el formato de
entrada de la expresión no el de salida.
TO_DATE(<expresion>, [<formato>])
En este ejemplo convertimos la expresion '01/12/2006' de tipo CHAR a una fecha (tipo
DATE). Con el parámetro formato le indicamos que la fecha está escrita como día-mes-año para
que devuelve el uno de diciembre y no el doce de enero.
SELECT TO_DATE('01/12/2006',
'DD/MM/YYYY')
FROM DUAL;
TO_CHAR
Convierte una expresión al tipo CHAR. El parámetro opcional formato indica el formato
de salida de la expresión.
TO_CHAR(<expresion>, [<formato>])
TO_NUMBER
TO_NUMBER(<expresion>, [<formato>])
TRUNC
Si el parámetro recibido es una fecha elimina las horas, minutos y segundos de la misma.
SELECT TRUNC(SYSDATE)FROM DUAL;
LENGTH
INSTR
REPLACE
El siguiente ejemplo reemplaza la palabra 'HOLA' por 'VAYA' en la cadena 'HOLA MUNDO'.
SUBSTR
Obtiene una parte de una expresion, desde una posición de inicio hasta una determinada
longitud.
UPPER
LOWER
ROWIDTOCHAR
SELECT ROWIDTOCHAR(ROWID)
FROM DUAL;
RPAD
Añade N veces una determinada cadena de caracteres a la derecha una expresión. Muy util
para generar ficheros de texto de ancho fijo.
El siguiente ejemplo añade puntos a la expresion 'Hola mundo' hasta alcanzar una longitud
de 50 caracteres.
LPAD
Añade N veces una determinada cadena de caracteres a la izquierda de una expresión. Muy
util para generar ficheros de texto de ancho fijo.
RTRIM
LTRIM
TRIM
MOD
MOD(<dividendo>, <divisor> )
Secuencias
ORACLE proporciona los objetos de secuencia para la generación de códigos numericos
automáticos.
Las secuencias son una solución fácil y elegante al problema de los codigos autogenerados.
Se puede simplificar la orden, tomando los valores por defecto. El ejemplo anterior quedaría
del siguiente modo:
SELECT SQ_PRODUCTOS.NEXTVAL
FROM DUAL;
Podemos obtener el último valor generado por la secuencia con la función CURRVAL. Para
poder ejecutar la función CURRVAL debemos haber ejecutado previamente la
función NEXTVAL.
SELECT SQ_PRODUCTOS.CURRVAL
FROM DUAL;
PL/SQL y Java
Otra de la virtudes de PL/SQL es que permite trabajar conjuntamente con Java.
PL/SQL es un excelente lenguaje para la gestion de información pero en ocasiones, podemos
necesitar de un lenguaje de programación más potente. Por ejemplo podríamos necesitar
consumir un servicio Web, conectar a otro servidor, trabajar con Sockets .... Para estos casos
podemos trabajar conjuntamente con PL/SQL y Java.
Para poder trabajar con Java y PL/SQL debemos realizar los siguientes pasos:
ORACLE incorpora su propia versión de la máquina virtual Java y del JRE. Esta versión de
Java se instala conjuntamente conORACLE.
Para crear objetos Java en la base de datos podemos utilizar la uitlidad LoadJava
de ORACLE desde linea de comandos o bien crear objetos JAVA SOURCE en la propia base
de datos.
<java code>
...
};
El siguiente ejemplo crea y compila una clase Java OracleJavaClass en el interior de JAVA
SOURCE FuentesJava. Un aspecto muy a tener en cuenta es que los métodos de la
clase java que queramos invocar desde PL/SQL deben ser estaticos.
La otra opción sería guardar nuestro codigo java en el archivo [Link],
compilarlo y cargarlo en ORACLE con LoadJava.
loadJava -help
Una vez que tenemos listo el programa de Java debemos integrarlo con PL/SQL. Esto se
realiza a través de subprogramas de recubrimiento llamados Wrappers.
Una vez creado el wrapper, podremos ejecutarlo como cualquier otra funcion o procedure de
PL/SQL. Debemos crear un wrapper por cada función java que queramos ejecutar desde
PL/SQL.
SELECT SALUDA_WRAP('DEVJOKER')
FROM DUAL;
Una recomendación de diseño sería agrupar todos los Wrapper en un mismo paquete.
En el caso de que nuestro programa Java necesitase de packages Java adicionales,
deberiamos cargarlos en la base de datos con la utilidad LoadJava.
[Link]