Tutorial PL SQL
Tutorial PL SQL
1.1. Introducció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.
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:
1
1.2. Programación PL/SQL.
• 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
2
2. Fundamentos de PL/SQL.
• DELIMITADORES
• IDENTIFICADORES
• LITERALES
• COMENTARIOS
• EXPRESIONES
• Operadores Aritméticos
• Operadores Lógicos
• Operadores Relacionales
• Constantes
• Cursores
• Variables
• Subprogramas
• Excepciones
• Paquetes
3
• LITERAL: Es un valor de tipo numérico, caracter, cadena o lógico no
representado por un identificador (es un valor explícito).
-- Linea simple
/*
Conjunto de Lineas
*/
4
2.2. Tipos de datos en PL/SQL.
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:
saldo NUMBER(16,2);
/* Indica que puede almacenar un valor numérico de 16
posiciones, 2 de ellas decimales. Es decir, 14 enteros
y dos decimales */
•CHAR (Caracter): Almacena datos de tipo caracter con una longitud máxima 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 */
5
•BOOLEAN (lógico): Se emplea para almacenar valores TRUE o FALSE.
hay_error BOOLEAN;
Existen por supuesto más tipos de datos, la siguiente tabla los muestra:
6
Tipo de dato / Oracle 8i Oracle 9i Descripción
Sintáxis
dec(p, e) La precisión La precisión máxima es Donde p es la
máxima es de 38 de 38 dígitos. precisión y e la
dígitos. escala.
Por ejemplo:
dec(3,1) es un
número que
tiene 2 dígitos
antes del
decimal y un
dígito después
del decimal.
Por ejemplo:
decimal(3,1) es
un número que
tiene 2 dígitos
antes del
decimal y un
dígito después
del decimal.
double
precision
float
int
integer
7
Tipo de dato / Oracle 8i Oracle 9i Descripción
Sintáxis
Por ejemplo:
number(7,2) es
un número que
tiene 5 dígitos
antes del
decimal y 2
dígitos después
del decimal.
real
smallint
char (tamaño) Hasta 32767 bytes Hasta 32767 bytes en Donde tamaño
en PLSQL. PLSQL. es el número de
caracteres a
Hasta 2000 bytes Hasta 2000 bytes en almacenar. Son
en Oracle 8i. Oracle 9i. cadenas de
ancho fijo. Se
rellena con
espacios.
varchar2 Hasta 32767 bytes Hasta 32767 bytes en Donde tamaño
(tamaño) en PLSQL. PLSQL. es el número de
caracteres a
8
Tipo de dato / Oracle 8i Oracle 9i Descripción
Sintáxis
Hasta 4000 bytes Hasta 4000 bytes en almacenar. Son
en Oracle 8i. Oracle 9i. cadenas de
ancho variable.
Por ejemplo:
timestamp(6)
Por ejemplo:
timestamp(5)
with time zone
9
Tipo de dato / Oracle 8i Oracle 9i Descripción
Sintáxis
Por ejemplo:
timestamp(4)
with local time
zone
Por ejemplo:
interval year(4)
to month
interval day No soportado por day precision debe ser Incluye año,
(day precision) Oracle 8i. un número entre 0 y 9. mes día, hora,
to second (El valor por defecto es minutos y
(fractional 2) segundos.
seconds
precision) fractional seconds Por ejemplo:
precision debe ser un interval day(2)
número entre 0 y 9. (El to second(6)
valor por defecto es 6)
10
Tipo de dato / Oracle 8i Oracle 9i Descripción
Sintáxis
11
Tipo de dato / Oracle 8i Oracle 9i Descripción
Sintáxis
4000 bytes en bytes en Oracle 9i. caracteres a
Oracle 8i. almacenar.
Cadena NLS de
ancho variable.
12
2.3. Operadores en PL/SQL.
13
3. Estructuras de control en PL/SQL.
IF (expresion) THEN
-- Instrucciones
ELSIF (expresion) THEN
-- Instrucciones
ELSE
-- Instrucciones
END IF;
Sentencia GOTO
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');
14
END;
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;
15
3.3. Bloques PL/SQL.
• Bloques anónimos
• Subprogramas
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;
16
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;
En esta parte se declaran las variables que va a necesitar nuestro programa. Una
variable se declara asignándole un nombre o "identificador" seguido del tipo de
valor que puede contener. También se declaran cursores, de gran utilidad para la
consulta de datos, y excepciones definidas por el usuario. También podemos
especificar si se trata de una constante, si puede contener valor nulo y asignar
un valor inicial.
17
nombre_variable [CONSTANT] <tipo_dato> [NOT NULL][:=valor_inicial]
donde:
• 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.
• 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.
Ejemplos:
18
3.3.3. Estructura de un bloque anónimo.
DECLARE
/* Se declara la variable de tipo VARCHAR2(15) identificada por v_location
y se le asigna
el valor "Granada"*/
v_location VARCHAR2(15) := ’Granada’;
/*Se declara la constante de tipo NUMBER identificada por PI
y se le asigna
el valor 3.1416*/
PI CONSTANT NUMBER := 3.1416;
/*Se declara la variable del mismo tipo que tenga el campo
nombre de la tabla tabla_empleados
identificada por v_nombre y no se le asigna ningún valor */
v_nombre tabla_empleados.nombre%TYPE;
/*Se declara la variable del tipo registro correspondiente a
un supuesto cursor, llamado
micursor, identificada por reg_datos*/
reg_datos micursor%ROWTYPE;
BEGIN
/*Parte de ejecucion*/
EXCEPTION
/*Parte de excepciones*/
END;
19
3.3.4. Estructura de un subprograma:
BEGIN
/*Parte de ejecucion*/
EXCEPTION
/*Parte de excepciones*/
END;
20
4. Cursores en 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 explícitos combinados con un estructura de bloque.
21
con el cursor.
El siguiente ejemplo muestra la declaración 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
WHERE CONTINENTE = p_continente;
begin
/* Sentencias del bloque ...*/
end;
22
4.2. Cursores Implícitos.
• 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 implícitos solo pueden devolver una única fila. En caso de
que se devuelva más de una fila (o ninguna fila) se producirá una
excepción. No se preocupe si aún no sabe que es una excepción, le valdrá
conocer que es el medio por el que PL/SQL gestiona los errores.
declare
vdescripcion VARCHAR2(50);
begin
SELECT DESCRIPCION
INTO vdescripcion
from PAISES
WHERE CO_PAIS = 'ESP';
23
4.2.1. Excepciones asociadas a los cursores implícitos.
Los cursores implícitos sólo pueden devolver una fila, por lo que pueden
producirse determinadas excepciones. Las más comunes que se pueden
encontrar son no_data_found y too_many_rows. La siguiente tabla explica
brevemente estas excepciones.
Excepción Explicación
24
4.3. Cursores Explícitos en PL/SQL
Los cursores explícitos se emplean para realizar consultas SELECT que pueden
devolver cero filas, o más de una fila.
Para trabajar con un cursor explicito necesitamos realizar las siguientes tareas:
• Declarar el cursor.
• Abrir el cursor con la instrucción OPEN.
• Leer los datos del cursor con la instrucción FETCH.
• Cerrar el cursor y liberar los recursos con la instrucción CLOSE.
CURSOR nombre_cursor IS
instrucción_SELECT
25
Para recuperar los datos en variables PL/SQL.
-- o bien ...
El siguiente ejemplo ilustra el trabajo con un cursor explícito. 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;
26
END;
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;
27
4.3.3. Manejo del cursor.
Por medio de ciclo LOOP podemos iterar a través del cursor. Debe tenerse
cuidado de agregar una condición para salir del bucle:
28
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;
DECLARE
CURSOR cpaises
IS
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
LOOP
dbms_output.put_line(descripcion);
END LOOP;
CLOSE cpaises;
29
END;
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;
FROM PAISES;
co_pais VARCHAR2(3);
descripcion VARCHAR2(50);
continente VARCHAR2(25);
BEGIN
OPEN cpaises;
WHILE cpaises%found
LOOP
dbms_output.put_line(descripcion);
END LOOP;
CLOSE cpaises;
END;
30
Por último podemos usar un bucle FOR LOOP. Es la forma más corta ya que el
cursor implícitamente ejecutan las instrucciones OPEN, FECTH y CLOSE.
BEGIN
FOR REG IN (SELECT * FROM PAISES)
LOOP
dbms_output.put_line(reg.descripcion);
END LOOP;
END;
31
4.3.4. Cursores de actualización.
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>.
UPDATE <nombre_tabla> SET
<campo_1> = <valor_1>
[,<campo_2> = <valor_2>]
WHERE CURRENT OF <cursor_name>
32
33
5. Excepciones en PL/SQL.
5.1. Manejo de excepciones.
34
DECLARE
-- Declaraciones
BEGIN
-- Ejecucion
EXCEPTION
-- Excepcion
END;
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
WHEN OTHERS THEN
-- Se ejecuta cuando ocurre una excepcion de un tipo no tratado
-- en los bloques anteriores
END;
35
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.
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.
36
Excepción Se ejecuta ... SQLCODE
37
Excepción Se ejecuta ... SQLCODE
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
38
-- Ejecucion
EXCEPTION
-- Excepcion
END;
Como las variables, una excepción declarada en un bloque es local a ese bloque
y global a todos los sub-bloques que comprende.
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
WHEN VALOR_NEGATIVO THEN
dbms_output.put_line('El valor no puede ser negativo');
END;
Con la sentencia RAISE podemos lanzar una excepción definida por el usuario o
predefinida, siendo el comportamiento habitual lanzar excepciones definidas por
el usuario.
39
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.
DECLARE
err_num NUMBER;
err_msg VARCHAR2(255);
result NUMBER;
BEGIN
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;
40
DECLARE
msg VARCHAR2(255);
BEGIN
msg := SQLERRM(-1403);
DBMS_OUTPUT.put_line(MSG);
END;
RAISE_APPLICATION_ERROR.
Siendo:
• error_num es un entero negativo comprendido entre -20001 y -20999
• mensaje la descripción del error
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;
41
42
5.8. Propagación de excepciones en PL/SQL.
43
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;
44
6. Subprogramas en PL/SQL.
Como hemos visto anteriormente los bloques de PL/SQL pueden ser bloques
anónimos (scripts) y subprogramas.
● Procedimientos almacenados
● Funciones
● Triggers
● Subprogramas.
45
CREATE [OR REPLACE]
PROCEDURE <procedure_name> [(<param1> [IN|OUT|IN OUT] <type>,
<param2> [IN|OUT|IN OUT] <type>,
...)]
IS
-- Declaracion de variables locales
BEGIN
-- Sentencias
[EXCEPTION]
-- Sentencias control de excepcion
END [<procedure_name>];
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;
46
También podemos asignar un valor por defecto a los parámetros, utilizando la
clausula DEFAULT o el operador de asignació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;
BEGIN
Actualiza_Saldo(200501,2500);
COMMIT;
END;
BEGIN
Actualiza_Saldo(cuenta => 200501,new_saldo => 2500);
COMMIT;
47
END;
48
7. Funciones en PL/SQL.
Una función es un subprograma que devuelve un valor. La sintaxis para construir
funciones es la siguiente:
Ejemplo:
49
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 ;
DECLARE
Valor NUMBER;
BEGIN
Valor := fn_Obtener_Precio('000100');
END;
SELECT CO_PRODUCTO,
DESCRIPCION,
fn_Obtener_Precio(CO_PRODUCTO)
FROM PRODUCTOS;
50
8. Triggers (Disparadores).
51
El uso de OR REPLACE permite sobreescribir un trigger existente. Si se omite, y
el trigger existe, se producirá, un error.
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 después 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.
Valor Descripción
INSERT,
Define qué tipo de orden DML provoca la
DELETE,
activación del disparador.
UPDATE
BEFORE , Define si el disparador se activa antes o
AFTER 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
FOR EACH
de orden se activan sólo una vez, antes o
ROW
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 ámbito 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 después(NEW) de la acción SQL
(INSERT, UPDATE, DELETE) que ha ejecutado el trigger. Utilizando esta
variable podemos acceder a los datos que se están insertando, actualizando o
borrando.
52
PRECIOS_PRODUCTOS cada vez que insertamos un nuevo registro en la tabla
PRODUTOS:
Una misma tabla puede tener varios triggers. En tal caso es necesario conocer el
orden en el que se van a ejecutar.
53
• Se ejecuta si existe, el disparador de tipo BEFORE con nivel de fila.
• Se ejecuta la propia orden.
• Se ejecuta si existe, el disparador de tipo AFTER (disparador
posterior) con nivel de fila.
• Se ejecuta, si existe, el disparador de tipo AFTER con nivel de orden.
Dentro del ámbito 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 después(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
54
borrando.
ACCION
OLD NEW
SQL
Los registros OLD y NEW son sólo válidos dentro de los disparadores con nivel
de fila.
Su comportamiento es el siguiente:
Predicado Comportamiento
55
56
9. Subprogramas en bloques anónimos.
DECLARE
idx NUMBER;
END;
57
10. Packages en PL/SQL.
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.
58
END <pkgName>;
59
-- Control de excepciones
END;
END <pkgName>;
END PKG_CONTABILIDAD;
Aquí sólo hemos declarado las variables y constantes, prototipado las funciones y
procedimientos públicos . Es en el cuerpo del paquete cuando escribimos el
código de los subprogramas Contabilizar y fn_Obtener_Saldo.
60
CREATE PACKAGE BODY PKG_CONTABILIDAD IS
RETURN NUMBER
IS
saldo NUMBER;
BEGIN
SELECT SALDO INTO saldo
FROM SALDOS
WHERE CO_CUENTA = codigo_cuenta;
return (saldo);
END;
PROCEDURE Contabilizar(mes VARCHAR2)
IS
CURSOR cDatos(vmes VARCHAR2)
IS
SELECT *
FROM FACTURACION
WHERE FX_FACTURACION = vmes
AND PENDIENTE_CONTABILIZAR = 'S';
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;
Los paquetes pueden llegar a ser programas muy complejos y suelen almacenar
gran parte de la lógica de negocio.
61
10. Registros PL/SQL.
Estos son:
● Registros
● Tablas de PL
● Varray
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.
62
);
Los registros son un tipo de datos, por lo que podremos declarar variables de
dicho tipo de datos.
DECLARE
DECLARE
TYPE PAIS IS RECORD
(CO_PAIS NUMBER ,
DESCRIPCION VARCHAR2(50),
CONTINENTE VARCHAR2(20)
);
63
miPAIS PAIS;
miMONEDA MONEDA;
BEGIN
/* Sentencias
*/
END;
En este caso hay que tener cuidado en especificar las columnas en el orden
conveniente según la declaración de los campos del registro. Para este tipo de
asignación es muy frecuente el uso del atributo %ROWTYPE que veremos más
adelante.
DECLARE
64
tabla, vista o cursor de la base de datos mediante el atributo %ROWTYPE.
DECLARE
miPAIS PAISES%ROWTYPE;
BEGIN
/* Sentencias ... */
END;
Las tablas de PL/SQL son tipos de datos que nos permiten almacenar varios
valores del mismo tipo de datos.
65
• 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;
66
tPAISES PAISES;
BEGIN
tPAISES(1).CO_PAIS := 27;
tPAISES(1).DESCRIPCION := 'ITALIA';
tPAISES(1).CONTINENTE := 'EUROPA';
END;
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;
67
• 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;
BEGIN
misCiudades(1) := 'MADRID';
misCiudades(3) := 'MALAGA';
68
/* Devuelve 2, ya que solo hay dos elementos con valor
*/
dbms_output.put_line(
'El número de elementos es:'||misCiudades.COUNT);
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:' || misCiudades.PRIOR(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:' || misCiudades.NEXT(1));
END;
69
10.3. VARRAYS
Los VARRAY deben estar inicializados antes de poder utilizarse. Para inicializar
un VARRAY se utiliza un constructor (podemos inicializar el VARRAY en la
sección DECLARE o bien dentro del cuerpo del bloque):
DECLARE
/* Declaramos el tipo VARRAY de cinco elementos
VARCHAR2*/
TYPE t_cadena IS VARRAY(5) OF VARCHAR2(50);
/* Asignamos los valores con un constructor */
v_lista t_cadena:= t_cadena('Aitor', 'Alicia',
'Pedro','','');
BEGIN
70
v_lista(4) := 'Tita';
v_lista(5) := 'Ainhoa';
END;
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 máximo de elementos que admite el VARRAY.
• EXTEND .Añade un elemento al VARRAY.
• EXTEND(n) .Añade (n) elementos al VARRAY.
Para poder crear tablas con campos de tipo VARRAY debemos crear el VARRAY
como un objeto de la base de datos.
71
Una vez que hayamos creado el tipo sobre la base de datos, podremos utilizarlo
como un tipo de datos más en la creación de tablas, declaración de variables ....
72
10.4. 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.
v_descripcion t_descripcion;
v_continente t_continente;
BEGIN
SELECT DESCRIPCION,
CONTINENTE
BULK COLLECT INTO v_descripcion, v_continente
FROM PAISES;
END;
/
v_paises t_paises;
BEGIN
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
BULK COLLECT INTO v_paises
FROM PAISES;
73
', ' ||
v_paises(i).CONTINENTE);
END LOOP;
END;
DECLARE
v_paises t_paises;
BEGIN
SELECT CO_PAIS, DESCRIPCION, CONTINENTE
BULK COLLECT INTO v_paises
FROM PAISES;
END;
/
74
11. Transacciones.
Es decir, una transacción es una o varias sentencias SQL que se ejecutan en una
base de datos como una única operación, confirmándose o deshaciéndose 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
75
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;
76
queremos deshacer o confirmar la transacción.
En ocasiones es necesario que los datos escritos por parte de una transacción
sean persistentes a pesar de que la transacción se deshaga con ROLLBACK.
PRAGMA AUTONOMOUS_TRANSACTION.
77
A continuación utilizamos el procedimiento desde un bloque de PL/SQL:
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
producido y hacer ROLLBACK de la transacción. Pero si hacemos ROLLBACK de
la transacción también lo hacemos de la inserción del log.
78
12. SQL Dinámico.
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;
79
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;
80
generado con SQL dinámico.
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
v_query := 'SELECT * FROM PAISES WHERE CO_PAIS = :cpais';
OPEN c_cursor FOR v_query USING codigo_pais;
LOOP
FETCH c_cursor INTO fila;
EXIT WHEN c_cursor%NOTFOUND;
dbms_output.put_line(fila.DESCRIPCION);
END LOOP;
CLOSE c_cursor;
END;
81
13. Funciones integradas de PL/SQL.
SYSDATE
NVL
NVL(<expresion>, <valor>)
DECODE
82
"<cond1>" devuelve el valor1 "<val1>", en caso contrario evalúa la siguiente
condición y así hasta que una de las condiciones se cumpla. Si no se cumple
ninguna condición se devuelve el valor por defecto.
TO_DATE
TO_DATE(<expresion>, [<formato>])
SELECT TO_DATE('01/12/2006',
'DD/MM/YYYY')
FROM DUAL;
83
TO_CHAR
TO_CHAR(<expresion>, [<formato>])
TO_NUMBER
TO_NUMBER(<expresion>, [<formato>])
TRUNC
LENGTH
84
Devuelve la longitud de un tipo CHAR.
INSTR
Su sintaxis es la siguiente:
REPLACE
85
SUBSTR
Obtiene una parte de una expresión, desde una posición de inicio hasta una
determinada longitud.
UPPER
LOWER
ROWIDTOCHAR
SELECT ROWIDTOCHAR(ROWID)
FROM DUAL;
RPAD
86
El siguiente ejemplo añade puntos a la expresión 'Hola mundo' hasta alcanzar
una longitud de 50 caracteres.
LPAD
RTRIM
LTRIM
87
TRIM
MOD
MOD(<dividendo>, <divisor> )
88
14. Secuencias.
Las secuencias son una solución fácil y elegante al problema de los códigos auto
generados.
89
Para obtener el siguiente valor de una secuencia debemos utilizar la función
NEXTVAL. NEXTVAL se puede utilizar el cualquier sentencia SQL DML
(SELECT, INSERT, UPDATE).
SELECT SQ_PRODUCTOS.NEXTVAL
FROM DUAL;
SELECT SQ_PRODUCTOS.CURRVAL
FROM DUAL;
90
15. 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 con ORACLE.
Para crear objetos Java en la base de datos podemos utilizar la utilidad LoadJava
de ORACLE desde linea de comandos o bien crear objetos JAVA SOURCE en la
propia base de datos.
91
deben ser estáticos.
92
OracleJavaClass.java, 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.
CREATE OR REPLACE
FUNCTION Saluda_wrap (nombre VARCHAR2)
RETURN VARCHAR2
AS
LANGUAGE JAVA NAME
'OracleJavaClass.Saluda(java.lang.String) return
java.lang.String';
Una vez creado el wrapper, podremos ejecutarlo como cualquier otra función o
procedure de PL/SQL. Debemos crear un wrapper por cada función java que
93
queramos ejecutar desde PL/SQL.
SELECT SALUDA_WRAP('DEVJOKER')
FROM DUAL;
SALUDA_WRAP('DEVJOKER')
----------------------------------------------
Hola desde JavaDEVJOKER
94