Introducción a la programación PL/SQL en Oracle
Enviado por Chuletator online y clasificado en Informática y Telecomunicaciones
Escrito el en español con un tamaño de 102,2 KB
TEMA XII. PROGRAMACIÓN DE GUIONES
INTRODUCCIÓN AL PL/SQL
SQL es un lenguaje de cuarta Generación (4GL), es decir, el lenguaje describe lo que debe hacerse, pero no La forma de realizarlo. Los lenguajes de tercera generación, en cambio, son Procedimentales. En muchas ocasiones, las estructuras procedimentales que Implementan los lenguajes 3GL son muy útiles para la realización de programas. El PL/SQL combina la potencia del SQL como 4GL con las estructuras Procedimentales de un 3GL.
PL/SQL (Lenguaje Procedimental/SQL)
Es un lenguaje de programación (basado en ADA), que es, en realidad, un apoyo y
Una extensión al lenguaje SQL de la
Base de Datos
Oracle
Al ser procedimental, nos permite
Utilizar determinadas estructuras en los programas que no podríamos utilizar de
Otra manera en SQL. De esta forma podemos implementar programas más estables y
Funcionales.
Se trata de un lenguaje Orientado a bloque, es decir, su unidad mínima de trabajo es el bloque. Éste es Un conjunto de declaraciones, órdenes y controles. Podemos compararlos con las Funciones o subprogramas de otros lenguajes.
Su potencia se basa en distintos Aspectos:
·Su compatibilidad con SQL. Combina la potencia Del SQL para la manipulación de datos con la capacidad de procesamiento de los Lenguajes procedimentales.
·Permite mejorar la productividad y el Rendimiento, puesto que, a diferencia del SQL, un bloque de sentencias PL/SQL Se traduce en una sola llamada a la base de datos, lo que aumenta la Eficiencia.
·Es portable entre distintas máquinas y sistemas Operativos que trabajen con el sistema de bases de datos de Oracle.
Los programas creados con PL/SQL Se pueden almacenar en la base de datos como cualquier otro objeto de ésta. De Este modo se facilita a todos los usuarios autorizados el acceso a estos Programas.
Los programas se ejecutan en el Servidor, con el consiguiente ahorro de recursos en los clientes y disminución Del tráfico de red.
ESTRUCTURA DEL BLOQUE PL/SQL
Formato de un bloque PL/SQL:
DECLARE
declaraciones
BEGIN
órdenes
EXCEPTION
gestión De errores y excepciones
END
Consideraciones:
·Sólo la parte ejecutable (BEGIN, END) es Obligatoria.
·Los bloques pueden anidarse.
·Las órdenes, declaraciones, etc., deben terminar Con un “;”. Sólo los IF y los LOOP son excepciones a esta regla. En SQL, el “;” Supone el final de una orden y, en consecuencia, su ejecución. En los bloques PL/SQL, el “/” (run) es el finalizador de la orden, indicando el final del Bloque.
·Para escribir comentarios podemos utilizar dos Guiones “—“ cuando el comentario ocupa una sola línea o, los símbolos “/*” (para comenzar) y “*/” (para finalizar) cuando ocupa más de una línea.
·Podemos ponerle una etiqueta al bloque usando Los signos “<< etiqueta >>”. Esa etiqueta sirve para referenciar al Bloque con sentencias como el GOTO.
Las etiquetas también se pueden Usar dentro de los bloques para delimitar o marcar los distintos elementos que Hay en su interior.
DECLARACIÓN DE VARIABLES
Las declaraciones de variables Tienen la siguiente pinta:
DECLARE
cumpleanos date;
contador number(2);
Al declarar las variables se les Puede asignar un valor:
contador number(2) := 5;
v_contador number DEFAULT 10; (equivale a :=)
nombre char(11) := ‘Pedro Gómez’;
ciudad char(10) NOT NULL := ‘Toledo’;
sino boolean; (admite tres posibles valores: sí, no, Null)
porcen CONSTANT number(3,2) := 0.15;
max_val number(8,2) := 1000 * porcen;
Para dar valores a las variables Se pueden usar cláusulas SELECT con la cláusula INTO:
SELECT Nombre_columna INTO variable FROM tabla;
Disponemos de dos tipos de
Atributos que permiten declarar las variables con los mismos tipos que otras
Variables. Esos atributos son %TYPE y %ROWTYPE.
%TYPE le asigna a una variable el mismo tipo de Datos que otra variable o columna de la tabla:
compras number(8) := 20000;
cantidad compras%TYPE;
De esta forma indicamos que la Variable cantidad es del mismo tipo que compras.
También podemos asignar el mismo Tipo que el de una columna de la tabla:
mi_variable tabla.Columna%TYPE;
%ROWTYPE crea una variable de Tipo registro, que será una estructura que contenga las mismas columnas que la Tabla a la que se hace referencia. La variable tendrá los mismos nombres y Tipos de datos que los de la fila de la tabla:
mi_fila tcliente%ROWTYPE;
Podemos asignar valores de Distintas formas:
mi_fila.Dni := ‘40404048’;
mi_fila := fila2 (fila2 será una Variable del mismo tipo)
SELECT * INTO mi_fila FROM tcliente;
Este tipo de variables no se
Pueden usar en sentencias INSERT o UPDATE. Por lo tanto, no sería
Correcto:
INSERT INTO tcliente Valúes (mi_fila);
Cuando en un bloque se declara una variable, ésta es Local en ese bloque y global en todos los bloques incluidos en el primero. Esto Quiere decir que desde un bloque interior se puede hacer referencia a una Variable declarada en el bloque exterior.
Si en un bloque interior se Declara una variable con el mismo nombre que otra declarada en el bloque Exterior, se puede hacer referencia a la variable global anteponiendo el nombre Del bloque exterior (si tiene nombre): etiqueta_bloque_exterior.Variable
ESTRUCTURAS DE CONTROL
CONTROL CONDICIONAL
La Ejecución de ciertas órdenes depende del cumplimiento de una o más condiciones:
IF condición THEN
sentencias;
END IF;
o bien
IF condición THEN
sentencias;
ELSE
sentencias;
END IF;
Los IF se Pueden anidar de la siguiente forma:
IF condición THEN
sentencias;
ELSE
IF condición2 THEN
sentencias;
ELSE
sentencias;
END IF;
ESTRUCTURAS REPETITIVAS
Permiten repetir una serie de órdenes un determinado número de veces. Hay tres tipos:
LOOP
Con salida En la parte central del bucle:
LOOP
sentencias;
EXIT WHEN condición de salida;
sentencias;
END LOOP;
Lo anterior Equivale a:
LOOP
sentencias;
IF Condición de salida THEN EXIT;
END IF;
sentencias;
END LOOP;
WHILE
Bucle en el Que las sentencias se ejecutan mientras se cumpla la condición:
WHILE condición LOOP
sentencias;
END LOOP;
FOR
FOR variable IN [REVERSE] mínimo..Máximo LOOP
sentencias;
END LOOP;
Con la Opción reverse la variable comienza tomando el valor máximo y va disminuyendo Hasta el valor mínimo.
La variable Utilizada en la instrucción FOR no es necesario declararla (el propio FOR lo Hace).
Hay otras Dos sentencias que conviene comentar:
GOTO:
Transfiere el control, de manera incondicional, a las sentencias de una
Etiqueta:
GOTO insertar;
<< insertar >>
INSERT INTO .......
NULL
Implica “no hacer nada”. Ejemplo:
IF Salario < 80000 THEN
sentencias;
ELSE
null;
END IF;
EJEMPLOS
Antes de
Comenzar con los ejemplos, vamos a hacer dos cosas para evitar que muestre en
Pantalla todas las sentencias antes de ejecutarlas (set echo off)
Y para
Evitar que muestre los valores anteriores y nuevos (set ver off o set
Verify off)
.
Para no Tener que escribirlas al comienzo de cada sesíón, podríamos escribirlas en el Archivo LOGIN.SQL.
Comencemos
Haciendo un programa que acepte un número por el teclado y nos diga si es par o
Impar (ed sql001)
:
rem sql001
clear buff;
set serveroutput on;
DECLARE
numro Number(4):=№
BEGIN
if (numro mod 2)=0 then
dbms_output.Put_line('El Número '||numro||' es par');
else
dbms_output.Put_line('El Número '||numro||' es impar');
end if;
END;
/
La orden set serveroutput on Nos permite usar la sentencia dbms_output.Put_line que saca por pantalla Los resultados (PL/SQL no nos proporciona otra forma de mostrar datos en Pantalla.
Cuando ejecutemos el fichero (@
Sql001)
Aparecerá el mensaje Procedimiento PL/SQL terminado con éxito.
Para que no aparezca, podemos hacer set feedback off.
El siguiente bloque PL/SQL
Escribe los números del 10 al 1 (ed sql002)
:
--sql002
set serveroutput on;
BEGIN
for i in reverse 1..10 loop
dbms_output.Put_line(i);
end loop;
END;
/
Programa que nos dice el número
De veces que un número puede ser dividido por 2 (ed sql010)
:
--sql010
clear buff;
set serveroutput on;
DECLARE
numro Number(4):=№
divdo number(4):=0;
veces number(2):=0;
BEGIN
divdo := numro;
while divdo mod 2 = 0 loop
veces := veces + 1;
divdo := divdo / 2;
end Loop;
dbms_output.Put_line('El Número '||numro||' es '||veces||' veces divisible por dos');
END;
/
Cambiamos el programa anterior
Usando la orden ACCEPT para pedir por teclado un número mediante un mensaje (ed
Sql010)
:
--sql010
clear buff;
set serveroutput on;
accept numero Prompt 'dame un múmero ...: ';
DECLARE
divdo number(4):=0;
veces number(2):=0;
BEGIN
divdo :=№
while divdo mod 2 = 0 loop
veces := veces + 1;
divdo := divdo / 2;
end Loop;
dbms_output.Put_line(&numero||' Es '||veces||' veces divisible por dos');
END;
/
undefine Numero
Al usar la orden ACCEPT la Variable numero queda definida (de ahí el undefine numero que Escribimos al final).
Programa que calcula el total de
Clientes, la suma de las compras y la media de éstas. Vamos a usar %TYPE y la
Asignación de valor a una variable con una cláusula SELECT) (ed sql020)
:
-- sql020
set serveroutput on;
DECLARE
total_clientes number(4);
total_compras Tcliente.Compras%type;
media_compras Tcliente.Compras%type;
BEGIN
select count(*), Sum(compras), avg(compras)
into Total_clientes, total_compras, media_compras
from Tcliente;
dbms_output.Put_line('nº De clientes : '||total_clientes);
dbms_output.Put_line('total De compras: '||total_compras);
dbms_output.Put_line('media De compras: '||media_compras);
END;
/
Antes de hacer el ejemplo
Siguiente hagamos select * from tvendedor y select avg(porcen) from
Tvendedor para poder observar los cambios cuando lo ejecutemos. Este
Ejemplo actualiza los porcentajes de los vendedores poniéndoles a aquellos que
No llegan, el valor de la media (ed sql030)
:
--sql030
BEGIN
update tvendedor set Porcen = (select avg(porcen) from Tvendedor)
where porcen <
(select avg(porcen) from tvendedor);
commit;
END;
/
Recordemos que commit confirma Las modificaciones, que ya no podrán deshacerse con rollback.
Programa que aumenta en dos Décimas la comisión de los vendedores con titulación elemental si la media de Sus porcentajes es inferior a la media de los porcentajes de los que tienen Titulación media (ed sql040):
-- sql040
DECLARE
me number;
mm number;
BEGIN
select avg(porcen) into me From tvendedor where titulación = 'ELEMENTAL';
select avg(porcen) into mm From tvendedor where titulación = 'MEDIA';
if me < mm then
update tvendedor set porcen=porcen+0.2
where Titulación = 'ELEMENTAL';
end if;
commit;
END;
/
Programa que escribe la serie 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, … hasta un número que escribiremos (ed sql045):
rem sql045
clear buff;
set serveroutput on;
accept numero prompt 'dame un número ...: ';
DECLARE
penúltimo number(4):=-1;
ultimo number(4):=1;
actual number(5);
tope number;
BEGIN
tope := №
actual := penúltimo + ultimo;
while actual < tope loop
dbms_output.Put_line(actual);
penúltimo := ultimo;
ultimo := actual;
actual := penúltimo + ultimo;
end loop;
END;
/
undefine numero;
TABLAS
En PL/SQL hay dos tipos de datos Compuestos: las tablas o arrays y los registros.
Las tablas tienen una columna y Una clave por la que se indexan. La columna puede contener cualquier tipo de Dato, pero el índice debe ser siempre BINARY_INTEGER.
Para declarar las tablas, Primero hay que definir un tipo tabla y, después, se declaran las tablas de ese Tipo:
TYPE nombre IS TABLE OF
tipo De columna
Índex BY BINARY_INTEGER;
Ejemplos:
DECLARE
TYPE Nombres IS TABLE OF
alumnos.Nombre%TYPE
Índex BY BINARY_INTEGER;
TYPE Tipotabla IS TABLE OF
VARCHAR2(10)
Índex BY BINARY_INTEGER;
TYPE Otro_tipo IS TABLE OF
tcliente%ROWTYPE
Índex BY BINARY_INTEGER:
Podemos declarar variables de Los tipos definidos:
nombre_alumnos nombres;
mi_tabla tipotabla;
Para hacer referencia a un elemento de una tabla Haremos cosas del tipo:
nombre_alumnos(i) := ‘PEPE PÉREZ’;
Programa que almacena los 10 Primeros números pares en un vector “v” (ed sql048):
--sql048
set serveroutput on;
DECLARE
type vector is table of Number índex by binary_integer;
v vector;
BEGIN
for i in 1..10 loop
v(i):= 2*i;
dbms_output.Put_line('i= '||i ||' v = '|| v(i));
end loop;
END;
/
Consideraciones con respecto a Las tablas:
·Una tabla consta de dos columnas, una para el índice Y la otra para almacenar los valores.
·Son válidas expresiones del tipo: v(-7) :=9
·Los elementos de la tabla no se almacenan Necesariamente en memoria de forma contigua.
·Si tenemos dos tablas del mismo tipo se le puede Asignar a una de ellas el contenido de la otra.
Atributos de tabla:
ATRIBUTO | DESCRIPCIÓN |
EXISTS(n) | Devuelve TRUE si existe el elemento n IF Tabla.EXISTS(i) THEN ... |
COUNT | Devuelve el número de elementos de una tabla IF Tabla.COUNT = 100 THEN ... |
FIRST | Devuelve El índice del primer elemento de la tabla. i Binary_integer := tabla.FIRST |
LAST | Devuelve el índice del último elemento de la tabla |
PRIOR(n) | Devuelve el índice del elemento anterior de la tabla. i:=tabla.PRIOR(i) |
NEXT(n) | Devuelve el índice del elemento siguiente |
DELETE(m,n) | Borra los elementos desde m hasta n. Si no se ponen Parámetros, borra toda la tabla. Si se pone sólo uno, borra el elemento Correspondiente. |
Programa que lee la tabla de Clientes y coloca el contenido de las columnas nombre y compras en dos arrays. Luego mostrará el contenido de estos (ed sql050):
--sql050
set serveroutput on
DECLARE
type nombres is table of Tcliente.Nombre%type
índex by binary_integer;
type importes is table of Tcliente.Compras%type
índex by binary_integer;
vnombres nombres;
vimportes importes;
i binary_integer:=0;
fila tcliente%rowtype;
BEGIN
for fila in (select nombre, Compras from tcliente) loop
i:=i+1;
vnombres(i):=fila.Nombre;
vimportes(i):=fila.Compras;
end loop;
for i in Vnombres.First..Vnombres.Last loop
dbms_output.Put_line('i= '||i ||' nombre = '||
vnombres(i)||' Compras = '||vimportes(i));
end loop;
END;
/
REGISTROS
De la misma forma que con los Arrays, primero definimos los tipos y luego declaramos las variables de esos Tipos.
TYPE Tipo_registro IS Récord (
campo1 tipo1 [not null][:= exprs],
campo2 tipo2 [not null][:= exprs],
campon tipon [not null][:= exprs]);
Ahora podemos definir variables de ese tipo:
mi_registro tipo_registro;
Para hacer referencia a alguno De los campos se utiliza la notación punto (mi_registro.Campo3).
Si queremos asignar a la vez Valores a todos los campos de un registro, podemos asignar a un registro el Contenido de otro (si son del mismo tipo) o podemos utilizar las sentencias SELECT y FETCH.
No se puede asignar valores Utilizando listas ni se pueden utilizar en operaciones de inserción. No son Correctas:
INSERT INTO tabla Valúes (mi_registro);
mi_registro := (valor1, valor2,...);
Un registro se puede utilizar en La definición de otro.
Veamos un ejemplo sencillo de Utilización de registros (ed sql02):
--sql052
set serveroutput on
DECLARE
type t_registro is Récord(
tabla char(10),
fecha date,
contador number(4));
mi_registro T_registro;
BEGIN
mi_registro.Tabla := 'tcliente';
mi_registro.Fecha := sysdate;
select Count(dni) into mi_registro.Contador from Tcliente;
dbms_output.Put_line(mi_registro.Tabla||
mi_registro.Fecha||' Nº = '||mi_registro.Contador);
END;
/
CURSORES
DEFINICIONES, SENTENCIAS DE MANEJO, ATRIBUTOS
Los cursores sirven para trabajar con consultas que Devuelven más de una fila. Esto permite realizar algunos tratamientos antes de Usar la información extraída y controlar qué fila se está procesando en cada Instante.
Un cursor se maneja como un Fichero secuencial en el que se almacenan las filas de una consulta. Para manipularlo Es necesario abrirlo, leerlo y cerrarlo.
Los cursores se definen en la Zona de declaraciones:
CURSOR Nombre IS SELECT sentencia_select;
Por ejemplo, creamos un cursor Para los clientes de tipo ‘AA’:
CURSOR Mi_cursor IS select * from tcliente where tipo = ‘AA’;
Consideraciones sobre cursores:
·No puede definirse con una consulta que contenga La cláusula INTO.
·Sus contenidos se obtienen en el momento de Abrirlos. No son “refrescados” hasta la siguiente apertura.
·Se identifica por su nombre. No es posible Asignar valores a un nombre de cursor o usarlo en expresiones.
Para abrir un cursor à OPEN nombre_cursor. Cuando se abre, se ejecuta la consulta asociada.
Para recorrer el cursor se Utiliza FETCH, que devuelve una fila cada vez que se ejecuta. Los valores que Lee o devuelve tienen que ser depositados en variables predefinidas
FETCH Nombre_cursor INTO variables;
También se puede volcar la Información sobre una sola variable definida con %ROWTYPE:
DECLARE
fila tcliente%ROWTYPE;
BEGIN
fetch Mi_cursor into fila;
Para cerrar el cursor à CLOSE nombre_cursor;
Se pueden declarar variables del Mismo tipo que un cursor ya definido. Por ejemplo definimos la variable mi_copia Con la estructura del cursor mi_cursor.
mi_copia mi_cursor%ROWTYPE;
Los cursores tienen cuatro Atributos predefinidos que nos sirven para conocer el estado del cursor en un Determinado momento:
·%NOTFOUND. Se activa si el último FETCH no ha recuperado ninguna fila del cursor (se Han agotado las filas de la consulta). Ejemplo: EXIT WHEN mi_cursor%NOTFOUND;
·%FOUND. Es el caso contrario, se activa si el último FETCH ha devuelto una fila. Antes de realizar ningún FETCHA su valor es Null.
·%ROWCOUNT. Devuelve el número de filas Recuperadas hasta ese momento con un FETCH. Ejemplo: if mi_cursor%ROWCOUNT = 5 then .........
·%ISOPEN. Verdadero si el cursor está abierto.
Si intentamos usar %FOUND, % NOTFOUND, %ROWCOUNT antes de la apertura o después del cierre se producirá un Error.
Programa que utiliza un cursor Para obtener el número de vendedores que tienen cada una de las titulaciones, y Cuántos tienen una comisión superior a 10. Además, nos dará el número de Registros leídos (ed sql058):
-- sql058
set serveroutput on
DECLARE
cursor cvendedor is
select titulación, porcen from Tvendedor;
cs number:=0;
cm number:=0;
ce number:=0;
po number:=0;
x cvendedor%rowtype;
BEGIN
open cvendedor;
loop
fetch cvendedor Into x;
exit when Cvendedor%notfound;
if X.Porcen > 10 then
po:=po+1;
end if;
if x.Titulación = 'SUPERIOR' then
cs:=cs+1;
else
if x.Titulación = 'MEDIA' then
cm:=cm+1;
else
ce:=ce+1;
end if;
end if;
end loop;
dbms_output.Put_line ('total de registros leídos '||cvendedor%rowcount);
close cvendedor;
dbms_output.Put_line('total Con titulación superior : '||cs);
dbms_output.Put_line('total Con titulación media : '||cm);
dbms_output.Put_line('total Con titulación elemental : '||ce);
dbms_output.Put_line('total Con comisión superior a 10 : '||po);
END;
/
Si definimos un cursor Utilizando expresiones, tenemos que poner un alias a las columnas Correspondientes para poder referenciar los campos de posibles valores de tipo Cursor.
Si definimos un cursor haciendo:
cursor Cvendedor is
select titulación, Porcen + 0.5 nuevopor from tvendedor;
Si definimos una variable x
Cvendedor%rowtype, podemos poner en el programa expresiones del tipo if
X.Nuevopor > 10 then...
Se puede trabajar con cursores Usando los bucles for para cursores, que provocan la apertura, los fetch Y el close de una manera implícita.
El siguiente programa hace lo mismo que el anterior Y usa un bucle for:
-- sql060
set serveroutput on
DECLARE
cursor cvendedor is
select Titulación, porcen from tvendedor;
cs number:=0;
cm number:=0;
ce number:=0;
po number:=0;
rl number:=0;
BEGIN
for x in cvendedor loop
if x.Porcen > 10 then
po:=po+1;
end if;
if x.Titulación = 'SUPERIOR' then
cs:=cs+1;
else
if X.Titulación = 'MEDIA' then
cm:=cm+1;
else
ce:=ce+1;
end if;
end if;
rl:=cvendedor%rowcount;
end loop;
dbms_output.Put_line('registros Leídos : '||rl);
dbms_output.Put_line('total Con titulación superior : '||cs);
dbms_output.Put_line('total Con titulación media : '||cm);
dbms_output.Put_line('total Con titulación elemental : '||ce);
dbms_output.Put_line('total Con comisión superior a 10 : '||po);
END;
/
No se podría colocar la
Sentencia rl:=cvendedor%rowcount;
fuera del bucle. La razón es que fuera
Del bucle for ya no existen ni el cursor ni sus atributos.
CURSORES IMPLÍCITOS
Cuando se procesa una sentencia SQL no asociada a un cursor declarado explícitamente, el sistema abre un cursor Implícito.
PL/SQL permite que referenciemos El más reciente cursor implícito como el cursor SQL. Con estos cursores no Podemos usar OPEN, FETCH ni CLOSE, pero sí podemos usar (con condiciones) los Atributos del cursor para obtener información del último comando ejecutado.
Para referenciar un atributo del Cursor implícito à SQL%NOTFOUND. Esta sentencia tiene que ser inmediata a la sentencia SELECT, INSERT, etc. Y devuelve TRUE al insertar una fila, al actualizar filas, o al Borrar filas. Devolverá FALSE si falla La inserción, modificación o borrado de una fila. Ejemplo:
--sql065
set serveroutput on
BEGIN
update tcliente set tipo = 'BB'
where dni = '12345';
if sql%found then -- USAMOS Aquí EL CURSOR Implícito
dbms_output.Put_line('cambiado ');
else
dbms_output.Put_line('este Cliente no existe');
end if;
END;
/
El programa sql060 podría Haberse hecho sin definir un cursor de forma explícita usando la sentencia:
for X in (select titulación, porcen, nom_ven from tvendedor) loop
Hay que tener en cuenta que no Se puede manejar el atributo sql%rowcount.
CURSORES CON PARÁMETROS
Definir cursores con parámetros Nos permite cualificar la consulta.
CURSOR Nombre (parámetro1 tipo, parámetro2 tipo ...)
IS SELECT columnas... WHERE columna = parámetro1 ..........;
Vamos a hacer un programa que Pida por teclado un código de jefe y un porcentaje. Se creará un cursor con los Vendedores que tengan ese jefe y un porcentaje de comisión superior y Obtendremos cuántos vendedores tienen cada una de las titulaciones:
-- sql091
set serveroutput on
accept p prompt 'Dame un Porcentaje...........: '
accept j prompt 'Dame un código de Superior...: '
DECLARE
cursor cvendedor(p_por number:=&p, P_sup char:='&j') is
select titulación, porcen from tvendedor
where Porcen > p_por and superior = p_sup;
cs number:=0;
cm number:=0;
ce number:=0;
x cvendedor%rowtype;
BEGIN
open cvendedor;
loop
fetch cvendedor Into x;
exit when Cvendedor%notfound;
if x.Titulación = 'SUPERIOR' then
cs:=cs+1;
else
if X.Titulación = 'MEDIA' then
cm:=cm+1;
else
ce:=ce+1;
end if;
end if;
end loop;
dbms_output.Put_line('total De registros '||cvendedor%rowcount);
close cvendedor;
dbms_output.Put_line('total Con titulación superior '||cs);
dbms_output.Put_line('total Con titulación media '||cm);
dbms_output.Put_line('total Con titulación elemental '||ce);
END;
/
undefine p
undefine j
Dos cosas a tener en cuenta:
A) se ha Utilizado el atributo %rowcount para obtener el total de registros del cursor y
B) en la Declaración de los parámetros no se incluye su tamaño
El paso de parámetros se puede Hacer en la sentencia OPEN nombre_cursor(par1, par2,....)
DECLARE
cursor Cvendedor(p_por number, p_sup char) is
select Titulación, porcen from tvendedor
where Porcen > p_por and superior = p_sup;
BEGIN
open Cvendedor(&p, ‘&j’);
MODIFICACIÓN DE DATOS CON CURSORES
Si un cursor está posicionado Sobre una fila, podemos modificarla o borrarla si en la definición del cursor Lo hemos indicado.
Si el cursor se define sobre una Tabla podemos poner:
CURSOR Nombre IS SELECT sentencia_select
FOR UPDATE [NOWAIT];
Si se define sobre más tablas, Las tablas que pueden ser actualizadas se pueden especificar nombrando alguna De sus columnas (no es necesario que estas columnas intervengan en la cláusula SELECT.
CURSOR Nombre IS SELECT sentencia_select
FOR UPDATE OF col1, col2, ... [NOWAIT];
Al definir un cursor con FOR UPDATE, se produce un bloqueo sobre la tabla o tablas susceptibles de Modificación. Si la tabla ya está bloqueada por otro usuario cuando se crea el Cursor, el sistema queda a la espera de que quede liberada. Si, además de la Sentencia FOR UPDATE, añadimos NOWAIT, el sistema no crea el cursor y devuelve Un error.
Update
Nos permite modificar la tabla o Vista sobre la que se ha formado el cursor:
UPDATE Nombre_tabla SET col1 = exp1, col2 = exp2 ...
WHERE CURRENT OF nombre_cursor;
Con esta sentencia se actualiza La fila a la que apunta el cursor “nombre_cursor”.
Delete
Se utiliza para borrar la fila Sobre la que apunta el cursor:
DELETE FROM nombre_tabla
WHERE CURRENT OF nombre_cursor;
En el ejemplo se utiliza un Cursor para actualizar los porcentajes de comisión de los vendedores, haciendo Que aquellos que tengan una comisión inferior a 10 se les ponga este valor (antes de ejecutar este programa conviene comprobar cuál es el contenido de TVENDEDOR: select * from tvendedor):
-- sql110
set serveroutput on
DECLARE
cursor cvendedor is
select nom_ven, Porcen from tvendedor
where Porcen < 10
for Update;
x cvendedor%rowtype;
BEGIN
open cvendedor;
loop
fetch Cvendedor into x;
exit When cvendedor%notfound;
update tvendedor set porcen = 10
where Current of cvendedor;
dbms_output.Put_line('actualizando '||cvendedor%rowcount
||' '||x.Nom_ven||x.Porcen);
end loop;
close cvendedor;
commit;
END;
/
MANEJO DE EXCEPCIONES
Utilizando las excepciones se Pueden gestionar los errores y los mensajes (tanto las propias de SQL*PLUS como Los de los usuarios).
Al detectar una excepción, PL/SQL pasa el control a la parte EXCEPTION. En ella se ejecutan las sentencias Que se hayan previsto para cada caso.
Cuando se produce una excepción, El bloque en el que se ha producido acaba.
EXCEPTION
WHEN Excepción1 THEN
órdenes;
WHEN Excepción2 THEN
órdenes;
WHEN OTHERS THEN
órdenes;
Si tenemos bloques anidados y se Produce una excepción en el bloque interno, el control del programa pasa a su Sección de excepciones. Si no tiene esta sección o no trata este error, éste Pasa al bloque externo para ser tratado allí.
Tipos de excepciones:
·
Excepciones que tienen relación con la
Herramienta.
qInternas Ya definidas.
qNo Definidas.
·Excepciones definidas por el usuario.
Excepciones internas ya Definidas
Estas excepciones activan unos Indicadores cuya descripción es la siguiente:
Excepción | Descripción |
CURSOR_ALREADY_OPEN | Se activa al abrir un cursor que Ya está abierto. |
DUP_VAL_ON_INDEX | Intento de almacenar un valor que ya existe en índice único. |
INVALID_CURSOR | Operación incorrecta sobre un cursor. |
INVALID_NUMBER | Fallo de conversión de un carácter a un número. |
LOGON_DENIED | Nombre de usuario/contraseña no válido. |
NO_DATA_FOUND | Una orden SELECT devuelve cero filas. |
PROGRAM_ERROR | Se activa si PL/SQL tiene un problema de ejecución. |
NOT_LOGGED_ON | Se activa si se intenta algún tipo de llamada a la base de Datos sin haber iniciado la sesíón. |
STORAGE_ERROR | Fallo de memoria. |
TIMEOUT_ON_RESOURCE | Se acaba el tiempo intentando coger un recurso. |
TOO_MANY_ROWS | Cuando se espera que una sentencia SELECT devuelva una Fila y devuelve más. |
TRANSACTION_BACKED_OUT | Cuando se anula la parte remota de una transacción. |
VALUE_ERROR | Cuando hay un error de aritmética, conversión, Truncamiento o restricción. |
ZERO_DIVIDE | Error al intentar dividir por cero. |
En el siguiente ejemplo vamos a Intentar encontrar al cliente con dni 101010104, previendo una excepción para El caso de que no lo encuentre:
--sql120
DECLARE
fila tcliente%rowtype;
BEGIN
select * into fila from Tcliente
where Dni = '101010104';
dbms_output.Put_line(fila.Nombre);
EXCEPTION
when no_data_found then
dbms_output.Put_line('no Lo he encontrado');
END;
/
Excepciones internas no Definidas
Las excepciones estándar del Sistema tienen asociado un código (NO_DATA_FOUND tiene asociado el error ORA-01403, TOO_MANY_ROWS el ORA-01427, etc.). Sólo se les ha dado nombre a Algunos errores del sistema (a los más comunes). El resto pueden ser tratados Definiendo una variable en la que se deposite el error enviado por el sistema.
Por ejemplo, si el error
Devuelto por el sistema es el –1547 cuando no tiene sitio para crear una nueva
Extensión, para asociar este error a una variable (ésta será de tipo EXCEPTION)
Usamos la orden PRAGMA EXCEPTION_INIT.
DECLARE
error_sin_sitio EXCEPTION;
pragma Exception_init (error_sin_sitio, -1547);
BEGIN
sentencias
EXCEPTION
when Error_sin_sito then
sentencia ...
END;
Excepciones definidas por El usuario
Puede interesarnos generar
Nuestras propias situaciones de excepción y darles un tratamiento específico.
Para hacer esto, definimos una variable de tipo EXCEPTION, generamos el
Código en el bloque de excepciones y, dentro del bloque BEGIN, arrancamos la
Excepción con la sentencia RAISE nombre_excepción.
DECLARE
mi_error EXCEPTION;
BEGIN
sentencias
if condición then
RAISE Mi_error;
end If;
EXCEPTION
when Mi_error then
sentencias ...
END;
En el siguiente ejemplo vamos a Considerar una excepción el hecho de que algún vendedor tenga en la columna SUPERIOR un valor null. Trataremos esa excepción dando un mensaje:
-- sql130
set serveroutput on
DECLARE
cursor Cvendedor is
select Nom_ven, superior from tvendedor;
x Cvendedor%rowtype;
error_sup Exception;
BEGIN
open Cvendedor;
loop
fetch cvendedor into x;
exit when cvendedor%notfound;
if x.Superior is null then
raise error_sup;
else
dbms_output.Put_line(x.Nom_ven);
end if;
end loop;
close cvendedor;
EXCEPTION
when error_sup then
dbms_output.Put_line('no Tiene superior '||x.Nom_ven);
END;
/
En la primera fila se encuentra Que el vendedor tiene valor null en SUPERIOR, se arranca la excepción y el Programa se acaba después de ejecutar las sentencia previstas.
Otras consideraciones sobre las excepciones
·Cuando se dispara una excepción dentro de un Bloque FOR LOOP, el cursor se cierra implícitamente antes de que el manejador De la excepción sea invocado. Por lo tanto, los valores de sus atributos no Están disponibles en el manejador de la excepción.
·Al ejecutar las rutinas de manejo de Excepciones, la ejecución del bloque acaba y, no es posible volver a un punto Determinado dentro de él. Para solucionar esto, podríamos crear un bloque Interno que contuviera el manejo de excepciones. Vamos a ver este tipo de Solución modificando el último programa de la manera siguiente:
-- sql133
set serveroutput on
DECLARE
cursor Cvendedor is
select nom_ven, superior from Tvendedor;
x cvendedor%rowtype;
error_sup exception;
BEGIN
open cvendedor;
loop
fetch Cvendedor into x;
exit when cvendedor%notfound;
begin
if X.Superior is null then
raise error_sup;
else
dbms_output.Put_line(x.Nom_ven);
end If;
exception
when Error_sup then
dbms_output.Put_line (x.Nom_ven||' ..*no tiene Superior*.. ');
end;
end loop;
close cvendedor;
END;
/
Cuando se produce una excepción, El bloque (interno) que la contiene finaliza, pero no el bloque exterior. Por Lo tanto, el programa continúa.
·Si deseamos redisparar una excepción hacia el Bloque contenedor para que éste la trate de nuevo, basta con incluir la Sentencia RAISE en el manejador local de la excepción.
Podemos usar dos funciones para Gestionar errores:
Función | Descripción |
SQLCODE | Devuelve el código asociado con la excepción activada en último lugar. Devuelve siempre un número negativo excepto con NO_DATA_FOUND, Que devuelve 100. Para excepciones de usuario devuelve 1, a no ser que estén Redefinidas por PRAGMA EXCEPTION_INIT. |
SQLERRM | Devuelve el mensaje asociado con el actual SQLCODE. Dado Que se trata de una cadena de 512 caracteres, el programador normalmente Corta el texto a mostrar. Si se proporciona un número entero, devuelve el Mensaje asociado a ese valor entero (a:=sqlerrm(1403)) |
Estas funciones tienen que ser Asignadas a variables. Ejemplo:
--sql140
set serveroutput on
DECLARE
e_num number;
e_men Varchar2(100);
fila tcliente%rowtype;
BEGIN
select * into fila from Tcliente
where dni = '101010104';
dbms_output.Put_line(fila.Nombre);
EXCEPTION
when others then
e_num := sqlcode;
e_men := substr(sqlerrm,1,100);
dbms_output.Put_line ('error nº '||e_num||' mensaje: '||e_men);
END;
/
El procedimiento RAISE_APPLICATION_ERROR permite introducir mensajes de error definidos por el Usuario desde procedimiento almacenados. Para llamar a este procedimiento:
raise_application_error (nº de error, mensaje [,{TRUE | FALSE]);
El número de error tiene que Estar entre –20000 y –20999, ambos incluidos. Mensaje es el texto que acompaña Al error. TRUE o FALSE determina si el error se sitúa sobre la pila de errores O si sustituye a todos los demás errores existentes (la opción por defecto es FALSE). Ejemplo:
-- sql145
set serveroutput on
DECLARE
cursor cvendedor is
select nom_ven, Superior from tvendedor;
x cvendedor%rowtype;
BEGIN
open cvendedor;
loop
fetch cvendedor Into x;
exit when Cvendedor%notfound;
if x.Superior Is null then
raise_application_error(-20000,'falta Superior');
else
dbms_output.Put_line(x.Nom_ven);
end if;
end loop;
close cvendedor;
END;
/
En el siguiente programa Forzamos un mensaje para cualquier situación:
-- sql150
set serveroutput on
DECLARE
cursor cvendedor is
select nom_ven, Superior from tvendedor;
x cvendedor%rowtype;
err_num number;
err_msg Varchar2(80);
BEGIN
open cvendedor;
loop
fetch cvendedor into x;
exit when cvendedor%notfound;
begin
if x.Superior is null then
raise_application_error(-20000,'no tiene Superior');
else
raise_application_error(-20001,'SÍ Tiene superior');
end if;
exception
when OTHERS then
err_num := SQLCODE;
err_msg := SUBSTR(SQLERRM,1,80);
dbms_output.Put_line(x.Nom_ven||err_num||' '||err_msg);
end;
end loop;
close cvendedor;
END;
/
EJERCICIOS:
1. Mostrar los nombres de los empleados con los Trienios de antigüedad en la empresa
2. Mostrar qué empleados tienen un sueldo superior al De su jefe
3.
Pedir por teclado la denominación de un puesto (por ejemplo, ANALISTA) e
Incrementar las comisiones de los empleados que estén a su cargo (todos los
Empleados que dependan de cada ANALISTA) en una cantidad igual a la comisión
Que recibe el superior, dividida por el número de sus empleados.
SUBPROGRAMAS
Pueden Llevar parámetros y ser llamados desde otros programas.
Hay dos Tipos: procedimientos y funciones. Ambos tienen secciones declarativa, Ejecutable y de excepciones. Se diferencian en que una llamada a un Procedimiento es una orden PL/SQL en si misma, en tanto que una llamada a una Función se realiza como parte de una expresión.
PROCEDIMIENTOS
Tienen una Estructura similar a un bloque PL/SQL. Se pueden utilizar con cualquier Herramienta que use PL/SQL y en cualquier bloque, procedimiento o función. Deben ser declarados en la sección DECLARE después del resto de variables.
PROCEDURE Nombre(parámetros) IS
declaraciones;
BEGIN
sentencias;
EXCEPTION
excepciones;
END nombre;
No se puede Especificar el tamaño de los parámetros, en caso de hacerlo se generará un Error. Si el procedimiento no tiene parámetros, no tenemos que usar los Paréntesis.
El Siguiente bloque pide un número y llama a un procedimiento que escribe la Palabra “hola” tantas veces como indique el número:
-- sql200
set Serveroutput on
DECLARE
procedure Veces (i number) is
begin
for X in 1..I loop
dbms_output.Put_line('hola');
end Loop;
end Veces;
BEGIN
veces(&b);
END;
/
Procedimiento que calcula el Factorial de un número:
--sql210
set Serveroutput on
DECLARE
procedure Factorial (i number) is
fact Int:=1;
begin
for X in 1..I loop
fact:=fact*x;
end Loop;
dbms_output.Put_line(fact);
end Factorial;
BEGIN
factorial(&b);
END;
/
El siguiente procedimiento Modificará el tipo del cliente que especifiquemos haciendo que sea AA. En el Caso de que ya lo sea, nos dará un aviso:
--sql220
set Serveroutput on
DECLARE
procedure Cambiar(cod_cli char) is
tipo_cli Tcliente.Tipo%type;
begin
select Tipo into tipo_cli
from tcliente where dni=cod_cli;
if tipo_cli='AA' then
Dbms_output.Put_line('ya es de tipo AA');
else
update tcliente Set tipo='AA' where dni=cod_cli;
end If;
end Cambiar;
BEGIN
cambiar('&a');
END;
/
Cuando no se encuentra el Cliente, se produce un error y se abandona el programa. Vamos a mejorar el Programa tratando la excepción cuando el cliente no está en la tabla. Además, Vamos a hacer que nos pida por teclado el tipo que le queremos poner al Cliente:
--sql225
set Serveroutput on
DECLARE
procedure Cambiar(cod_cli char, nue_tip char) is
tipo_cli tcliente.Tipo%type;
begin
select Tipo into tipo_cli
from tcliente where dni=cod_cli;
if Tipo_cli=nue_tip then
dbms_output.Put_line('ya es de tipo '||nue_tip);
else
update tcliente set tipo=nue_tip
where dni=cod_cli;
end If;
exception
when no_data_found then
dbms_output.Put_line('este Cliente no existe ');
end cambiar;
BEGIN
cambiar('&cliente','&nuevo_tipo');
END;
/
El siguiente programa hace lo Mismo que el anterior usando el cursor implícito que se genera al ejecutar la Sentencia UPDATE:
--sql230
set Serveroutput on
DECLARE
procedure Cambiar(cod_cli char, nue_tip char) is
begin
update Tcliente set tipo=nue_tip
where dni=cod_cli and tipo!=nue_tip;
if Sql%found then -- USAMOS EL CURSOR IMPLÍCITO
dbms_output.Put_line('cambiado');
else
dbms_output.Put_line('este Cliente no existe o ya es de ese tipo');
end If;
end Cambiar;
BEGIN
cambiar('&cliente','&nuevo_tipo');
END;
/
PROCEDIMIENTOS Y PARÁMETROS
Ya hemos visto la utilización De parámetros en procedimiento. Por defecto, los parámetros son de entrada (pasan de la llamada al procedimiento). Es posible tener parámetros de salida y También parámetros de entrada y salida.
PROCEDURE
nombre(par1 IN tipo, Par2 OUT tipo2, par3 IN OUT tipo3, ...) IS
declaraciones;
BEGIN
sentencias;
EXCEPTION
excepciones;
END nombre;
·Un parámetro de tipo IN permite pasar valores a Un subprograma cuando se produce la llamada. Hay que tener en cuenta que, Dentro del subprograma, no podemos modificarlo.
·Un parámetro de tipo OUT permite devolver Valores al bloque o al subprograma que le ha llamado. La única operación que se Puede hacer sobre él es asignarle una variable (no se le puede asignar una Constante ni tampoco una expresión).
·Un parámetro de tipo IN OUT puede ser Inicializado. También puede ser asignado a cualquier otra variable y ser Empleado en cualquier expresión.
Vamos a ver unos ejemplos Sencillos sobre la utilización de los parámetros:
--sql235
set Serveroutput on
DECLARE
x Number:=33;
procedure P1(i in number, x out number) is
a Int:=10;
begin
dbms_output.Put_line('entro en procedimiento '||'i= '||i||' a= '||a);
x:=a;
end P1;
BEGIN
dbms_output.Put_line('antes De llamar x= '||x);
p1(2,x);
dbms_output.Put_line('salgo del Procedimiento x= '||x);
END;
/
--sql240
set Serveroutput on
DECLARE
x Number:=33;
procedure P2(x in out number) is
a Int:=10;
begin
dbms_output.Put_line('entro En procedimiento '||'x= '||x||' a= '||a);
x:=a+x;
end P2;
BEGIN
dbms_output.Put_line('antes De llamar x= '||x);
p2(x);
dbms_output.Put_line('salgo del Procedimiento x= '||x);
END;
/
El siguiente programa calcula El valor de las combinaciones de m elementos tomados de n en n. Este valor se Obtiene haciendo = m!/(n!*(m-n)!)
--sql242
set serveroutput on
DECLARE
m Number:=&m;
n Number:=&n;
m_fac Number;
n_fac Number;
m_n_fac Number;
procedure Factorial(entrada in number, salida out number) is
f Int:=1;
begin
for X in 1..Entrada loop
f:=f*x;
end Loop;
salida:=f;
end Factorial;
BEGIN
factorial(m,m_fac);
factorial(n,n_fac);
factorial(m-n,m_n_fac);
dbms_output.Put_line('resultado= '||m_fac/(n_fac*m_n_fac));
END;
/
FUNCIONES
Son subprogramas que Devuelven un valor. Para ello deben utilizar la orden RETURN, que finaliza la Función y retorna al programa principal un valor.
FUNCTION Nombre(parámetros) RETURN tipo IS
declaraciones;
BEGIN
sentencias;
return Variable;
EXCEPTION
excepciones;
END nombre;
El tipo de Dato que tiene que devolver es “tipo”. No se debe especificar su tamaño. Veamos un ejemplo sencillo:
--sql250
set Serveroutput on
DECLARE
x Number:=33;
resultado Number;
function Suma_30(x number) return number is
a Number:=30;
begin
dbms_output.Put_line('entro En la función '||'x='||x||' a= '||a);
a:=a+x;
return A;
end Suma_30;
BEGIN
dbms_output.Put_line('antes De llamar x= '||x);
resultado:=suma_30(x);
dbms_output.Put_line('salgo de la Función resultado= '||resultado);
END;
/
Cuando declaramos los Subprogramas, podemos asignar valores por defecto a algunos de sus parámetros. Conviene que éstos se sitúen al final de la lista.
Vamos a Hacer un programa que pida dos números y nos indique si el resto de dividirlos Es cero. Si no se introduce ningún valor para el segundo se asumirá que vale 10:
--sql253
set Serveroutput on
DECLARE
dividendo Number(3):=&numero1;
divisor Number(2):=to_number('&numero2');
function Esdiv(dndo dividendo%type, dsor divisor%type default 10) return boolean is
begin
if (dndo mod dsor)=0 then
return True;
else
return False;
end If;
end esdiv;
BEGIN
if Divisor is null then
if Esdiv(dividendo) then
dbms_output.Put_line(dividendo||' Es divisible por 10');
else
dbms_output.Put_line(dividendo||' NO es divisible por 10');
end If;
else
if Esdiv(dividendo,divisor) then
dbms_output.Put_line(dividendo||' Y '||divisor||' son divisibles');
else
dbms_output.Put_line(dividendo||' Y '||divisor||' NO son divisibles');
end If;
end If;
END;
/
FUNCIONES RECURSIVAS
La Recursión consiste en un subprograma que se invoca a sí mismo. En una función Recursiva deberá haber dos “return”, uno para acabar la recursión finalizando La ejecución del subprograma y otro que corresponderá a la llamada recursiva al Mismo.
Puede haber Una solución iterativa para un problema resuelto de forma recursiva. Hay que Decir que un proceso recursivo es más fácil de optimizar y resulta más elegante Y corto. Sin embargo, consume más recursos que un proceso iterativo.
Vamos a Hacer un programa que calcule el factorial de un número de forma recursiva:
--sql255
set Serveroutput on
DECLARE
f Number:=&f;
resultado Number;
function Factorial(x positive) return number is
begin
if X=0 then
return 1;
else
return X*factorial(x-1);
end If;
end Factorial;
BEGIN
resultado:=factorial(f);
dbms_output.Put_line('Factorial De '||f||' = '||resultado);
END;
/
Programa Que escribe el elemento enésimo de la sucesión de Fibonacci:
--sql260
set Serveroutput on
DECLARE
n Number:=&n;
resultado Number;
function Fibo(x positive) return number is
begin
if x=1 or x=2 then
return 1;
else
return fibo(x-1) + fibo(x-2);
end If;
end fibo;
BEGIN
if N>0 then
resultado:=fibo(n);
end if;
dbms_output.Put_line('Fibonacci De '||n||' = '||resultado);
END;
/
PROCEDIMIENTOS Y FUNCIONES ALMACENADOS
Se trata de Procedimientos que se almacenan dentro del servidor. Se pueden invocar desde Muchos entornos y aplicaciones. Cuando están almacenados en la base de datos, Podemos invocarlos desde el prompt de sql usando “execute”:
SQL>
EXECUTE procedimiento(parámetros);
Para Llamarlos desde otro bloque o procedimiento basta con nombrarlos.
Cuando se Trata de funciones, lo único que hay que tener en cuenta es que el valor que Devuelven hay que asignárselo a una variable:
resultado:=función(var);
Para Ejecutar una función desde el prompt haremos algo similar a lo siguiente:
SQL> accept valor;
SQL> var k number;
SQL> Execute :k:=función(&valor);
SQL> print k;
Para crear un procedimiento o función almacenados usamos la orde