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.
Podemos acceder a un completo tutorial de SQL desde AQUI.
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.
¿Que vamos a necesitar?
Para poder seguir este tutorial correctamente necesitaremos tener los siguientes
elementos:
Una instancia de ORACLE 8i o superior funcionando correctamente.
Herramientas cliente de ORACLE, en particular SQL*Plus para poder ejecutar
los ejemplo.
Haber configurado correctamente una conexión a ORACLE.
Fundamentos de PL/SQL
Primeros pasos con PL/SQL
Para programar en PL/SQL es necesario conocer sus fundamentos.
Como introducción vamos a ver algunos elementos y conceptos básicos del lenguaje.
PL/SQL no es CASE-SENSITIVE, es decir, no diferencia mayúsculas de
minúsculas como otros lenguajes de programación como C o Java. Sin embargo
debemos recordar que ORACLE es CASE-SENSITIVE en la búsquedas de
texto.
Una linea en PL/SQL contiene grupos de caracteres conocidos como
UNIDADES LEXICAS, que pueden ser clasificadas como:
o DELIMITADORES
o IDENTIFICADORES
o LITERALES
o COMENTARIOS
o EXPRESIONES
o Operadores Logicos
o Operadores Relacionales
o Cursores
o Variables
o Subprogramas
o Excepciones
o Paquetes
-- Linea simple
/*
Conjunto de Lineas
*/
-- CHAR [(longitud_maxima)]
nombre CHAR(20);
/* Indica que puede almacenar valores alfanuméricos de 20
posiciones */
VARCHAR2 (Caracter de longitud variable): Almacena datos de tipo caracter
empleando sólo la cantidad necesaria aún cuando la longitud máxima sea mayor.
-- 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 */
BOOLEAN (lógico): Se emplea para almacenar valores TRUE o FALSE.
hay_error BOOLEAN;
DATE (Fecha): Almacena datos de tipo fecha. Las fechas se almacenan
internamente como datos numéricos, por lo que es posible realizar operaciones
aritmeticas con ellas.
Atributos de tipo. Un atributo de tipo PL/SQL es un modificador que puede ser
usado para obtener información de un objeto de la base de datos. El atributo
%TYPE permite conocer el tipo de una variable, constante o campo de la base
de datos. El atributo %ROWTYPE permite obtener los tipos de todos los
campos de una tabla de la base de datos, de una vista o de un cursor.
PL/SQL también permite la creación de tipos personalizados (registros) y
colecciones(tablas de PL/SQL), que veremos en sus apartados correspondientes.
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.
Por ejemplo:
La precisión máxima es La precisión máxima es dec(3,1) es un
dec(p, e)
de 38 dígitos. de 38 dígitos. número que tiene 2
dígitos antes del
decimal y un dígito
después del
decimal.
Donde p es la
precisión y e la
escala.
Por ejemplo:
decimal(p, La precisión máxima es La precisión máxima es decimal(3,1) es un
e) de 38 dígitos. de 38 dígitos. número que tiene 2
dígitos antes del
decimal y un dígito
después del
decimal.
double
precision
float
int
integer
Donde p es la
precisión y e la
escala.
Por ejemplo:
numeric(p, La precisión máxima es La precisión máxima es numeric(7,2) es un
e) de 38 dígitos. de 38 dígitos. número que tiene 5
dígitos antes del
decimal y 2 dígitos
después del
decimal.
real
smallint
Donde tamaño es el
Hasta 32767 bytes en Hasta 32767 bytes en número de
PLSQL. PLSQL. caracteres a
char
almacenar. Son
(tamaño) Hasta 2000 bytes en Hasta 2000 bytes en
cadenas de ancho
Oracle 8i. Oracle 9i.
fijo. Se rellena con
espacios.
Donde tamaño es el
Hasta 32767 bytes en Hasta 32767 bytes en número de
varchar2 PLSQL. PLSQL. caracteres a
(tamaño) Hasta 4000 bytes en Hasta 4000 bytes en almacenar. Son
Oracle 8i. Oracle 9i. cadenas de ancho
variable.
Son cadenas de
long Hasta 2 gigabytes. Hasta 2 gigabytes.
ancho variable.
Hasta 32767 bytes en Hasta 32767 bytes en
PLSQL. PLSQL. Son cadenas
raw binarias de ancho
Hasta 2000 bytes en Hasta 2000 bytes en
variable.
Oracle 8i. Oracle 9i.
Son cadenas
long raw Hasta 2 gigabytes. Hasta 2 gigabytes. binarias de ancho
variable.
Una fecha entre el 1 de Una fecha entre el 1 de
Enero de 4712 A.C. y el Enero de 4712 A.C. y el
date
31 de Diciembre de 9999 31 de Diciembre de 9999
D.C. D.C.
Incluye año, mes
timestamp fractional seconds día, hora, minutos y
(fractional No soportado por Oracle precision debe ser un segundos.
seconds 8i. número entre 0 y 9. (El
Por ejemplo:
precision) valor por defecto es 6)
timestamp(6)
Período de tiempo
interval almacenado en años
year precision debe ser y meses.
year (year No soportado por Oracle
un número entre 0 y 9. (El
precision) 8i. Por ejemplo:
valor por defecto es 2)
to month interval year(4) to
month
Rowid universal.
urowid
Hasta 2000 bytes. Hasta 2000 bytes. Donde tamaño es
[tamaño]
opcional.
Válido en PLSQL, este Válido en PLSQL, este
boolean tipo de datos no existe en tipo de datos no existe en
Oracle 8i. Oracle 9i.
Donde tamaño es el
Hasta 32767 bytes en Hasta 32767 bytes en número de
nchar
PLSQL. Hasta 2000 bytes PLSQL. Hasta 2000 bytes caracteres a
(tamaño)
en Oracle 8i. en Oracle 9i. almacenar. Cadena
NLS de ancho fijo.
Donde tamaño es el
número de
Hasta 32767 bytes en Hasta 32767 bytes en
nvarchar2 caracteres a
PLSQL. Hasta 4000 bytes PLSQL. Hasta 4000 bytes
(tamaño) almacenar. Cadena
en Oracle 8i. en Oracle 9i.
NLS de ancho
variable.
Localizadores de
archivo apuntan a
bfile Hasta 4 gigabytes. Hasta 4 gigabytes. un objeto binario de
sólo lectura fuera de
la base de datos.
Localizadores LOB
apuntan a un gran
blob Hasta 4 gigabytes. Hasta 4 gigabytes. objeto binario
dentro de la base de
datos.
Localizadores LOB
apuntan a un gran
clob Hasta 4 gigabytes. Hasta 4 gigabytes. objeto de caracteres
dentro de la base de
datos.
Localizadores LOB
apuntan a un gran
nclob Hasta 4 gigabytes. Hasta 4 gigabytes. objeto NLS de
caracteres dentro de
la base de datos
Operadores en PL/SQL
La siguiente tabla ilustra los operadores de PL/SQL.
Tipo de operador Operadores
Operador de asignación := (dos puntos + igual)
Operadores aritméticos + (suma)
- (resta)
* (multiplicación)
/ (división)
** (exponente)
Operadores relacionales = (igual a)
o de comparación <> (distinto de)
< (menor que)
> (mayor que)
>= (mayor o igual a)
<= (menor o igual a)
Operadores lógicos AND (y lógico)
NOT (negacion)
OR (o lógico)
Operador de
||
concatenación
IF (expresion) THEN
-- Instrucciones
ELSIF (expresion) THEN
-- Instrucciones
ELSE
-- Instrucciones
END IF;
Un aspecto a tener en cuenta es que la instrucción condicional anidada es ELSIF y no
"ELSEIF".
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 >>
El siguiente ejemplo ilustra el uso de GOTO.
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
En PL/SQL tenemos a nuestra disposición los siguientes iteradores o bucles:
LOOP
WHILE
FOR
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;
El bucle WHILE, se repite mientras que se cumpla expresion.
El bucle FOR, se repite tanta veces como le indiquemos en los identificadores inicio y
final.
-- Instrucciones
END LOOP;
En el caso de especificar REVERSE el bucle se recorre en sentido inverso.
Bloques PL/SQL
Un programa de PL/SQL está compuesto por bloques. Un programa está compuesto
como mínimo de un bloque.
Los bloques de PL/SQL pueden ser de los siguientes tipos:
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;
De las anteriores partes, únicamente la sección de ejecución es obligatoria, que
quedaría delimitada entre las cláusulas BEGIN y END. Veamos un ejemplo de bloque
PL/SQL muy genérico. Se trata de un bloque anónimos, es decir no lo identifica ningún
nombre. Los bloques anónimos identifican su parte declarativa con la palabra reservada
DECLARE.
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;
A continuación vamos a ver cada una de estas secciones
Sección de Declaración de Variables
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.
La sintaxis generica para la declaracion de constantes y variables es:
DECLARE
/* Se declara la variable de tipo VARCHAR2(15) identificada por
v_location y se le asigna
el valor "Granada"*/
v_location VARCHAR2(15) := ’Granada’;
Cursores en PL/SQL
Introducción a cursores PL/SQL
PL/SQL utiliza cursores para gestionar las instrucciones SELECT. Un cursor es un
conjunto de registros devuelto por una instrucción SQL. Técnicamente los cursores son
fragmentos de memoria que reservados para procesar los resultados de una consulta
SELECT.
Podemos distinguir dos tipos de cursores:
Cursores implicitos. Este tipo de cursores se utiliza para operaciones SELECT
INTO. Se usan cuando la consulta devuelve un único registro.
Cursores explicitos. Son los cursores que son declarados y controlados por el
programador. Se utilizan cuando la consulta devuelve un conjunto de registros.
Ocasionalmente también se utilizan en consultas que devuelven un único
registro por razones de eficiencia. Son más rápidos.
Un cursor se define como cualquier otra variable de PL/SQL y debe nombrarse de
acuerdo a los mismos convenios que cualquier otra variable. Los cursores implicitos no
necesitan declaración.
El siguiente ejemplo declara un cursor explicito:
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.
El siguiente ejemplo muestra la declaracion de un cursor con un parámetro, identificado por
p_continente.
declare
cursor c_paises (p_continente IN VARCHAR2) is
SELECT CO_PAIS, DESCRIPCION
FROM PAISES
Cursores Implicitos
Declaración de cursores implicitos.
Los cursores implicitos se utilizan para realizar consultas SELECT que devuelven un único
registro.
Deben tenerse en cuenta los siguientes puntos cuando se utilizan cursores implicitos:
Con cada cursor implicito debe existir la palabra clave INTO.
Las variables que reciben los datos devueltos por el cursor tienen que contener el mismo
tipo de dato que las columnas de la tabla.
Los cursores implicitos solo pueden devolver una única fila. En caso de que se devuelva
más de una fila (o ninguna fila) se producirá una excepcion. No se preocupe si aún no sabe
que es una excepcion, le valdrá conocer que es el medio por el que PL/SQL gestiona los
errores.
El siguiente ejemplo muestra un cursor implicito:
declare
vdescripcion VARCHAR2(50);
begin
SELECT DESCRIPCION
INTO vdescripcion
from PAISES
WHERE CO_PAIS = 'ESP';
end;
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.
CURSOR nombre_cursor IS
instrucción_SELECT
También debemos declarar los posibles parametros que requiera el cursor:
OPEN nombre_cursor;
o bien (en el caso de un cursor con parámetros)
OPEN nombre_cursor(valor1, valor2, ..., valorN);
Para recuperar los datos en variables PL/SQL.
CLOSE nombre_cursor;
El siguiente ejemplo ilustra el trabajo con un cursor explicito. Hay que tener en cuenta que al leer los datos del cursor debemos hacerlo sobre
variables del mismo tipo de datos de la tabla (o tablas) que trata el cursor.
DECLARE
CURSOR cpaises
IS
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
FETCH cpaises INTO co_pais,descripcion,continente;
CLOSE cpaises;
END;
Podemos simplificar el ejemplo utilizando el atributo de tipo %ROWTYPE sobre el cursor.
DECLARE
CURSOR cpaises
IS
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
registro cpaises%ROWTYPE;
BEGIN
OPEN cpaises;
FETCH cpaises INTO registro;
CLOSE cpaises;
END;
El mismo ejemplo, pero utilizando parámetros:
DECLARE
CURSOR cpaises (p_continente VARCHAR2)
IS
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES
WHERE CONTINENTE = p_continente;
registro cpaises%ROWTYPE;
BEGIN
OPEN cpaises('EUROPA');
FETCH cpaises INTO registro;
CLOSE cpaises;
END;
Cuando trabajamos con cursores debemos considerar:
Cuando un cursor está cerrado, no se puede leer.
Cuando leemos un cursor debemos comprobar el resultado de la lectura utilizando los atributos de los cursores.
Cuando se cierra el cursor, es ilegal tratar de usarlo.
Es ilegal tratar de cerrar un cursor que ya está cerrado o no ha sido abierto
Atributos de cursores
Atributo Antes de abrir Al abrir Durante la recuperación Al finalizar la recuperación Después de cerrar
%NOTFOUND ORA-1001 NULL FALSE TRUE ORA-1001
%FOUND ORA-1001 NULL TRUE FALSE ORA-1001
%ISOPEN FALSE TRUE TRUE TRUE FALSE
%ROWCOUNT ORA-1001 0 * ** ORA-1001
OPEN nombre_cursor;
LOOP
FETCH nombre_cursor INTO lista_variables;
EXIT WHEN nombre_cursor%NOTFOUND;
/* Procesamiento de los registros recuperados */
END LOOP;
CLOSE nombre_cursor;
Aplicada a nuestro ejemplo anterior:
DECLARE
CURSOR cpaises
IS
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
LOOP
FETCH cpaises INTO co_pais,descripcion,continente;
EXIT WHEN cpaises%NOTFOUND;
dbms_output.put_line(descripcion);
END LOOP;
CLOSE cpaises;
END;
Otra forma es por medio de un bucle WHILE LOOP. La instrucción FECTH aparece dos veces.
OPEN nombre_cursor;
FETCH nombre_cursor INTO lista_variables;
WHILE nombre_cursor%FOUND
LOOP
/* Procesamiento de los registros recuperados */
FETCH nombre_cursor INTO lista_variables;
END LOOP;
CLOSE nombre_cursor;
DECLARE
CURSOR cpaises
IS
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
FETCH cpaises INTO co_pais,descripcion,continente;
WHILE cpaises%found
LOOP
dbms_output.put_line(descripcion);
FETCH cpaises INTO co_pais,descripcion,continente;
END LOOP;
CLOSE cpaises;
END;
Por último podemos usar un bucle FOR LOOP. Es la forma más corta ya que el cursor es implicitamente se ejecutan las instrucciones OPEN,
FECTH y CLOSE.
BEGIN
FOR REG IN (SELECT * FROM PAISES)
LOOP
dbms_output.put_line(reg.descripcion);
END LOOP;
END;
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;
FETCH cpaises INTO co_pais,descripcion,continente;
END LOOP;
CLOSE cpaises;
COMMIT;
END;
Cuando trabajamos con cursores de actualización debemos tener en cuenta las
siguientes consideraciones:
Los cursores de actualización generan bloqueos en la base de datos.
Excepciones en PL/SQL
Manejo de excepciones
En PL/SQL una advertencia o condición de error es llamada una excepción.
Las excepciones se controlan dentro de su propio bloque.La 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.
El siguiente ejemplo muestra un bloque de excepciones que captura las excepciones
NO_DATA_FOUND y ZERO_DIVIDE. Cualquier otra excepcion será capturada en
el bloque WHEN OTHERS THEN.
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;
Como ya hemos dicho cuando ocurre un error, se ejecuta el bloque EXCEPTION,
transfiriéndose el control a las sentencias del bloque. Una vez finalizada la ejecución del
bloque de EXCEPTION no se continua ejecutando el bloque anterior.
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:
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
La sentencia RAISE permite lanzar una excepción en forma explícita. Es posible utilizar esta
sentencia en cualquier lugar que se encuentre dentro del alcance de la excepción.
DECLARE
-- Declaramos una excepcion identificada por VALOR_NEGATIVO
VALOR_NEGATIVO EXCEPTION;
valor NUMBER;
BEGIN
-- Ejecucion
valor := -1;
RAISE VALOR_NEGATIVO;
END IF;
EXCEPTION
-- Excepcion
Con la sentencia RAISE podemos lanzar una excepción definida por el usuario o
predefinida, siendo el comportamiento habitual lanzar excepciones definidas por el
usuario.
Recordar la existencia de la excepción OTHERS, que simboliza cualquier condición
de excepción que no ha sido declarada. Se utiliza comúnmente para controlar cualquier
tipo de error que no ha sido previsto. En ese caso, es común observar la sentencia
ROLLBACK en el grupo de sentencias de la excepción o alguna de las funciones
SQLCODE – SQLERRM, que se detallan en el próximo punto.
Uso de SQLCODE y SQLERRM
Al manejar una excepción es posible usar las funciones predefinidas SQLCode y
SQLERRM para aclarar al usuario la situación de error acontecida.
SQLcode devuelve el número del error de Oracle y un 0 (cero) en caso de exito al
ejecutarse una sentencia SQL.
Por otra parte, SQLERRM devuelve el correspondiente mensaje de error.
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;
También es posible entregarle a la función SQLERRM un número negativo que
represente un error de Oracle y ésta devolverá el mensaje asociado.
DECLARE
msg VARCHAR2(255);
BEGIN
msg := SQLERRM(-1403);
DBMS_OUTPUT.put_line(MSG);
END;
DECLARE
v_div NUMBER;
BEGIN
SELECT 1/0 INTO v_div FROM DUAL;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-21000,'No se puede dividir por cero');
END;
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;
La excepcion NO_DATA_FOUND se produce durante la ejecución de la funcion
fn_fecha, pero como no existe ningún manejador de la excepción en dicha funcion, la
excepción se propaga hasta el bloque que ha realizado la llamada. En ese momento se
captura la excepcion.
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.
Los subprogramas pueden recibir parámetros.
Los subprogramas pueden ser de varios tipos:
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.
La sintaxis de un procedimiento almacenado es la siguiente:
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.
Existen dos formas de pasar argumentos a un procedimiento almacenado a la hora de
ejecutarlo (en realidad es válido para cualquier subprograma). Estas son:
Notación posicional: Se pasan los valores de los parámetros en el mismo orden en que el
procedure los define.
BEGIN
Actualiza_Saldo(200501,2500);
COMMIT;
END;
Notación nominal:Se pasan los valores en cualquier orden nombrando explicitamente el
parámetro.
BEGIN
Actualiza_Saldo(cuenta => 200501,new_saldo => 2500);
COMMIT;
END;
Funciones en PL/SQL
Una función es un subprograma que devuelve un valor.
La sintaxis para construir funciones es la siguiente:
return(result);
[EXCEPTION]
-- Sentencias control de excepcion
END [<fn_name>];
El uso de OR REPLACE permite sobreescribir una función existente. Si se omite, y la
función existe, se producirá, un error.
La sintaxis de los parámetros es la misma que en los procedimientos almacenado,
exceptuando que solo pueden ser de entrada.
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.
Una vez creada y compilada la función podemos ejecutarla de la siguiente forma:
DECLARE
Valor NUMBER;
BEGIN
Valor := fn_Obtener_Precio('000100');
END;
Las funciones pueden utilizarse en sentencias SQL de manipulación de datos (SELECT, UPDATE,
INSERT y DELETE):
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.
La sintaxis para crear un trigger es la siguiente:
Valor Descripción
Define qué tipo de orden DML provoca la activación del
INSERT, DELETE, UPDATE
disparador.
Define si el disparador se activa antes o después de que se
BEFORE , AFTER
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 una vez,
FOR EACH ROW
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.
El siguiente ejemplo muestra un trigger que inserta un registro en la tabla PRECIOS_PRODUCTOS
cada vez que insertamos un nuevo registro en la tabla PRODUTOS:
ACCION
OLD NEW
SQL
No definido; todos los campos toman Valores que serán insertados cuando se
INSERT
valor NULL. complete la orden.
Valores originales de la fila, antes deNuevos valores que serán escritos cuando
UPDATE
la actualización. se complete la orden.
No definidos; todos los campos toman el
DELETE Valores, antes del borrado de la fila.
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.
Utilización de predicados de los triggers: INSERTING, UPDATING y DELETING
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.
Su comportamiento es el siguiente:
Predicado Comportamiento
TRUE si la orden de disparo es INSERT; FALSE en otro
INSERTING
caso.
TRUE si la orden de disparo es UPDATE; FALSE en otro
UPDATING
caso.
TRUE si la orden de disparo es DELETE; FALSE en otro
DELETING
caso.
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.
Para crear la especificación de un paquete la sintaxis general es la siguiente:
fila cDatos%ROWTYPE;
BEGIN
OPEN cDatos(mes);
LOOP FETCH cDatos INTO fila;
EXIT WHEN cDatos%NOTFOUND;
/* Procesamiento de los registros recuperados */
END LOOP;
CLOSE cDatos;
EXCEPTION
WHEN OTHERS THEN
RAISE ERROR_CONTABILIZAR;
END Contabilizar;
END PKG_CONTABILIDAD;
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.
La sintaxis general es la siguiente:
);
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;
miPAIS.DESCRIPCION := 'ITALIA';
miPAIS.CONTINENTE := '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.
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
INTO miPAIS
FROM PAISES
WHERE CO_PAIS = 27;
Puede asignarse un registro a otro cuando sean del mismo tipo:
DECLARE
miPAIS.CO_PAIS := 27;
miPAIS.DESCRIPCION := 'ITALIA';
miPAIS.CONTINENTE := 'EUROPA';
otroPAIS := miPAIS;
END;
DECLARE
miPAIS PAISES%ROWTYPE;
BEGIN
/* Sentencias ... */
END;
Lo cual significa que el registro miPAIS tendrá la siguiente estructura: CO_PAIS
NUMBER, DESCRIPCION VARCHAR2(50), CONTINENTE VARCHAR2(20).
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.
Una tabla PL/SQL :
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.
La sintaxis general para declarar una tabla de PL es la siguiente:
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;
DECLARE
miPAIS(1).CO_PAIS := 27;
miPAIS(1).DESCRIPCION := 'ITALIA';
miPAIS(1).CONTINENTE := 'EUROPA';
END;
Funciones para el manejo de tablas PL/SQL
Cuando trabajamos con tablas de PL podemos utilizar las siguientes funciones:
FIRST. Devuelve el menor índice de la tabla. NULL si está vacía.
LAST. Devuelve el mayor índice de la tabla. NULL si está vacía.
El siguiente ejemplo muestra el uso de FIRST y LAST :
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 misCiudades.FIRST..misCiudades.LAST
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 misCiudades.FIRST..misCiudades.LAST
LOOP
IF misCiudades.EXISTS(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;
COUNT. Devuelve el número de elementos de la tabla PL/SQL.
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:'||misCiudades.COUNT);
END;
PRIOR (n). Devuelve el número del índice anterior a n en la tabla.
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:' || misCiudades.PRIOR(3));
END;
NEXT (n). Devuelve el número del índice posterior a n en la tabla.
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 previo a 3 es:' || misCiudades.NEXT(1));
END;
TRIM. Borra un elemento del final de la tabla PL/SQL.
TRIM(n) borra n elementos del final de la tabla PL/SQL.
DELETE. Borra todos los elementos de la tabla PL/SQL.
DELETE(n) borra el correspondiente al índice n.
DELETE(m,n) borra los elementos entre m y n.
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.
La sintaxis general es la siguiente:
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 se establece mediante el número de parámetros
utilizados en el constructor, si declaramos un VARRAY de cinco elementos pero al
inicializarlo pasamos sólo tres parámetros al constructor, el tamaño del VARRAY será
tres. Si se hacen asignaciones a elementos que queden fuera del rango se producirá un
error.
El tamaño de un VARRAY podrá aumentarse utilizando la función EXTEND, pero
nunca con mayor dimensión que la definida en la declaración del tipo. Por ejemplo, la
variable v_lista 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:
LIMIT . Devuelve el número maximo de elementos que admite el VARRAY.
EXTEND .Añade un elemento al VARRAY.
EXTEND(n) .Añade (n) elementos al VARRAY.
Varrays en la base de datos
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.
La sintaxis general es:
Para modificar un varray almacenado, primero hay que seleccionarlo en una variable
PL/SQL. Luego se modifica la variable y se vuelve a almacenar en la tabla.
La utilización de VARRAYS en la base de datos está completamente
desaconsejada.
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 PAISES.DESCRIPCION%TYPE;
TYPE t_continente IS TABLE OF PAISES.CONTINENTE%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;
/
Podemos utilizar BULK COLLECT con registros de PL.
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;
DECLARE
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
Para confirmar una transacción se utiliza la sentencia COMMIT. Cuando realizamos
COMMIT los cambios se escriben en la base de datos.
Para deshacer una transacción se utiliza la sentencia ROLLBACK. Cuando
realizamos ROLLBACK se deshacen todas las modificaciones realizadas por la
transacción en la base de datos, quedando la base de datos en el mismo estado que antes
de iniciarse la transacción.
Un ejemplo clásico de transacción son las transferencias bancarias. Para realizar una
transferencia de dinero entre dos cuentas bancarias debemos descontar el dinero de una
cuenta, realizar el ingreso en la otra cuenta y grabar las operaciones y movimientos
necesarios, actuaizar los saldos ... .Si en alguno de estos puntos se produce un fallo en el
sistema podríamos hacer descontado el dinero de una de las cuentas y no haberlo
ingresado en la otra. Por lo tanto, todas estas operaciones deben ser correctas o fallar
todas. En estos casos, al confirmar la transaccion (COMMIT) o al deshacerla
(ROLLBACK) garantizamos que todos los datos quedan en un estado consistente.
En una transacción los datos modificados no son visibles por el resto de usuarios
hasta que se confirme la transacción.
El siguiente ejemplo muestra una supuesta transacción bancaria:
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.
ORACLE es completamente transaccional. Siempre debemos especificar si que
queremos deshacer o confirmar la transacion.
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.
PL/SQL permite marcar un bloque con PRAGMA
AUTONOMOUS_TRANSACTION. Con esta directiva marcamos el subprograma
para que se comporte como transacción diferente a la del proceso principal, llevando el
control de COMMIT o ROLLBACK independiente.
Observese el siguiente ejemplo. Primero creamos un procedimiento y lo marcamos
con PRAGMA AUTONOMOUS_TRANSACTION.
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
PL/SQL ofrece la posibilidad de ejecutar sentencias SQL a partir de cadenas de
caracteres. Para ello debemos emplear la instrucción EXECUTE IMMEDIATE.
Podemos obtener información acerca de número de filas afectadas por la instrucción
ejecutada por EXEXUTE IMMEDIATE utilizando SQL%ROWCOUNT.
El siguiente ejemplo muestra la ejecución de un comando SQL dinamico.
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
clave USING . 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;
Cursores con SQL dinámico
Con SQL dinámico también podemos utilizar cursores.
Para utilizar un cursor implicito solo debemos construir nuestra sentencia SELECT en una variable de
tipo caracter y ejecutarla con EXECUTE IMMEDIATE utilizando la palabra clave INTO.
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';
DECLARE
TYPE cur_typ IS REF CURSOR;
c_cursor CUR_TYP;
fila PAISES%ROWTYPE;
v_query VARCHAR2(255);
codigo_pais VARCHAR2(3) := 'ESP';
BEGIN
NVL(<expresion>, <valor>)
El siguiente ejemplo devuelve 0 si el precio es nulo, y el precio cuando está informado:
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;
Este otro ejemplo muestra la conversión con formato de día y hora.
TO_CHAR(<expresion>, [<formato>])
TO_NUMBER(<expresion>, [<formato>])
LOWER
Convierte una expresion alfanumerica a minúsculas.
ROWIDTOCHAR
Convierte un ROWID a tipo caracter.
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.
MOD(<dividendo>, <divisor> )
SELECT MOD(20,15) -- Devuelve el modulo de dividir 20/15
FROM DUAL
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.
LA sintaxis general es la siguiente:
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;
Para eliminar una secuencia definitivamente de la base de datos debemos utilizar la
sentencia DROP.
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:
Crear el programa Java y cargarlo en la base de datos.
Crear un program de recubrimiento (Wrapper) de PL/SQL.
Creacion de Objetos Java en la base de datos ORACLE.
ORACLE incorpora su propia versión de la máquina virtual Java y del JRE. Esta
versión de Java se instala conjuntamente con ORACLE.
Para crear objetos Java en la base de datos podemos utilizar la uitlidad LoadJava de
ORACLE desde linea de comandos o bien crear objetos JAVA SOURCE en la propia
base de datos.
La sintaxis para la creación de JAVA SOURCE en ORACLE es la siguiente.
<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.
loadJava -help
loadJava -u usario/password -v -f -r OracleJavaClass.class
loadJava -u usario/password -v -f -r OracleJavaClass.java
CREATE OR REPLACE
FUNCTION Saluda_wrap (nombre VARCHAR2)
RETURN VARCHAR2
AS
LANGUAGE JAVA NAME
'OracleJavaClass.Saluda(java.lang.String) return java.lang.String';
Una vez creado el wrapper, podremos ejecutarlo como cualquier otra funcion o
procedure de PL/SQL. Debemos crear un wrapper por cada función java que queramos
ejecutar desde PL/SQL.
Cuando ejecutemos el wrapper, es decir, la función "Saluda_wrap", internamente
se ejecutará la clase java y se invocará el método estático "OracleJavaClass.Saluda".
SELECT SALUDA_WRAP('DEVJOKER')
FROM DUAL;
La ejecución de este ejemplo en SQL*Plus genera la siguiente salida: