Está en la página 1de 22

PL/SQL*

*Tomado del curso de Francisco Moreno

08/12/21 Laboratorio de Bases de Datos 1


DESARROLLO DE PROCEDIMIENTOS
Y FUNCIONES ALMACENADOS

 Hasta el momento todos los bloques PL/SQL han


sido:
 Sin nombre (anónimos)
 Temporales
 Los bloques se pueden almacenar en forma
permanente mediante subprogramas: funciones y
procedimientos para usarlos repetidamente
 Los subprogramas pueden llevar argumentos
(parámetros)

08/12/21 Laboratorio de Bases de Datos 2


Sea la tabla:

CREATE TABLE registro(


id_usuario VARCHAR2(10),
fecha DATE,
estacion VARCHAR2(15)
);

CREATE OR REPLACE PROCEDURE registrarse IS


BEGIN
INSERT INTO registro
VALUES (USER, SYSDATE, USERENV('TERMINAL'));
END;
/

Para ejecutarlo en SQL*Plus:


EXECUTE registrarse;
08/12/21 Laboratorio de Bases de Datos 3
PROCEDIMIENTOS

Sintaxis:
CREATE [OR REPLACE] PROCEDURE
nombre_procedimiento
[( arg1 [modo] tipo [ , arg2 [modo]
tipo...])]
IS | AS
Bloque PL/SQL
08/12/21 Laboratorio de Bases de Datos 4
 Se debe especificar la opción REPLACE cuando
ya exista el procedimiento y se desee remplazar
 Se puede usar AS o IS (son equivalentes)
 El bloque PL/SQL empieza, ya sea con la
palabra BEGIN, o con la declaración de las
variables locales (sin usar la palabra DECLARE).
 Para ver los errores de compilación se puede
usar el comando SHOW ERRORS en SQL*Plus

08/12/21 Laboratorio de Bases de Datos 5


 El "modo" especifica el tipo de argumento, el cual puede ser :

- IN (por defecto): Parámetro de entrada al subprograma


- OUT: Parámetro de salida. El subprograma devuelve un valor en el
parámetro
- IN OUT: Parámetro de entrada y salida. El subprograma
devolverá un valor posiblemente diferente al enviado originalmente

 Se deben declarar, en lo posible, los parámetros empleando %TYPE y


%ROWTYPE
 No se puede especificar tamaño para los parámetros en lo que
respecta al tipo de datos

08/12/21 Laboratorio de Bases de Datos 6


CREATE OR REPLACE PROCEDURE consulta_emp
(v_nro IN emp.cod%TYPE)
IS
v_nom emp.nom%TYPE;
BEGIN
SELECT nom INTO v_nom
FROM emp
WHERE cod = v_nro;
DBMS_OUTPUT.PUT_LINE(v_nom);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Empleado no existe');
END;
/

EXECUTE consulta_emp(15);

08/12/21 Laboratorio de Bases de Datos 7


 Si el procedimiento retorna un parámetro de salida para
verlo en SQL*Plus se debe declarar una variable en
SQL*Plus así:

- VAR nom_var TIPO;


- Invocar el subprograma con los parámetros
(las vbles de SQL*PLUS se preceden con ‘:’ )
- Imprimir: PRINT nom_var;
(sin precederla de ‘:’ )

 Los parámetros de salida normalmente son recibidos


por otros subprogramas que los invocan
08/12/21 Laboratorio de Bases de Datos 8
CREATE OR REPLACE PROCEDURE consulta_emp
(v_nro IN emp.cod%TYPE, v_nom OUT emp.nom%TYPE)
IS
BEGIN
SELECT nom INTO v_nom -- Se llena el parámetro de salida
FROM emp
WHERE cod = v_nro;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Empleado no existe');
END;
/
Invocación desde SQL*Plus:
SQL> VAR a VARCHAR2(10);
SQL> EXECUTE consulta_emp(15,:a);
SQL> PRINT a;
08/12/21 Laboratorio de Bases de Datos 9
Invocación desde otro subprograma:

CREATE OR REPLACE PROCEDURE


invoca_consulta(v_nro IN emp.cod%TYPE)
IS
nombre emp.nom%TYPE; Retornará con el nombre
BEGIN
consulta_emp(v_nro, nombre);
DBMS_OUTPUT.PUT_LINE('El nombre del empleado es: ' || nombre);
END;
/

Para ejecutar: EXECUTE invoca_consulta(15);

08/12/21 Laboratorio de Bases de Datos 10


Ejemplo Sea el modelo:

pagado
por
GASTO CLIENTE
el respon-
sable de
desempeñando

asignado a

EMPLEO

08/12/21 Laboratorio de Bases de Datos 11


Sean las tablas:

CREATE TABLE cliente( ced NUMBER(8) PRIMARY KEY,


nom VARCHAR2(10) NOT NULL
);

CREATE TABLE empleo( ced NUMBER(8) REFERENCES cliente,


nit_empresa INTEGER,
valor_mensual NUMBER(6) NOT NULL,
PRIMARY KEY (ced, nit_empresa)
);

CREATE TABLE gasto( cod_gasto NUMBER(8) PRIMARY KEY,


ced NUMBER(8) REFERENCES cliente,
valor_mensual NUMBER(6),
desc_gasto VARCHAR2(10)
);

08/12/21 Laboratorio de Bases de Datos 12


Ingreso de datos:

INSERT INTO cliente VALUES(10, 'Ana');


INSERT INTO cliente VALUES(20,'Pedro');
INSERT INTO cliente VALUES(30,'Luis');

INSERT INTO empleo VALUES(10, 71, 1000);


INSERT INTO empleo VALUES(10, 72, 800);
INSERT INTO empleo VALUES(10, 83, 700);
INSERT INTO empleo VALUES(20, 72, 2000);
INSERT INTO empleo VALUES(20, 55, 600);

INSERT INTO gasto VALUES(1, 10, 1000, 'Alquiler');


INSERT INTO gasto VALUES(2, 10, 400, 'Servicios');
INSERT INTO gasto VALUES(3, 10, 200, 'Celular');
INSERT INTO gasto VALUES(4, 10, 800, 'Hijos');
INSERT INTO gasto VALUES(5, 10, 500, 'Auto');
INSERT INTO gasto VALUES(6, 20, 1000, 'Alquiler');
INSERT INTO gasto VALUES(7, 20, 1000, 'Comida');
INSERT INTO gasto VALUES(8, 30, 1000, 'Paseo 1');
INSERT INTO gasto VALUES(9, 30, 1000, 'Paseo 2');
08/12/21 Laboratorio de Bases de Datos 13
Ejemplo.

Realizar el o los subprogramas


necesarios para resolver lo siguiente :
Imprimir la cédula de cada cliente y la
diferencia entre todo lo que devenga y todo
lo que se gasta.

08/12/21 Laboratorio de Bases de Datos 14


Soluciones
Versión 1: Total Ingresos

Total Gastos
CREATE OR REPLACE PROCEDURE totale(
CREATE OR REPLACE PROCEDURE codigo cliente.ced%TYPE,
totalg( total OUT NUMBER) IS
codigo cliente.ced%TYPE, BEGIN
total OUT NUMBER) IS SELECT NVL(SUM(valor_mensual),0)
BEGIN INTO total
SELECT NVL(SUM(valor_mensual),0) FROM empleo WHERE ced = codigo;
INTO total END;
FROM gasto WHERE ced = codigo; /
END;
/
Para ejecutar: Para ejecutar:
VAR g NUMBER; VAR e NUMBER;
EXECUTE totalg(10,:g); EXECUTE totale(10,:e);
PRINT g; PRINT e;
08/12/21 Laboratorio de Bases de Datos 15
Versión 2: Combinando los 2 procedimientos en 1

CREATE OR REPLACE PROCEDURE total


(codigo cliente.ced%TYPE,
sw INTEGER, -- si es 1 se consulta empleo, si no gasto
total OUT NUMBER) IS
BEGIN
IF sw=1 THEN
SELECT NVL(SUM(valor_mensual),0) INTO total
FROM empleo WHERE ced = codigo;
ELSE
SELECT NVL(SUM(valor_mensual),0) INTO total
FROM gasto WHERE ced = codigo;
END IF;
END;
/
Para ejecutar: EXECUTE total(10,2,:a);
También: EXECUTE total(10,1,:a);

08/12/21 Laboratorio de Bases de Datos 16


Versión 3: Eliminando el IF mediante el uso de PL/SQL dinámico

CREATE OR REPLACE PROCEDURE total(


codigo cliente.ced%TYPE,
tabla VARCHAR,
total OUT NUMBER) IS
BEGIN
EXECUTE IMMEDIATE 'SELECT NVL(SUM(valor_mensual),0) FROM '||
tabla || ' WHERE ced ='|| codigo
INTO total;
END;
/
Para ejecutar: EXECUTE total(10,'gasto',:a);
O también: EXECUTE total(10, 'empleo',:a);

08/12/21 Laboratorio de Bases de Datos 17


Por lo tanto la solución completa usando el procedimiento
total anterior es:

CREATE OR REPLACE PROCEDURE neto IS


totalg NUMBER(8);
totale NUMBER(8);
BEGIN
FOR mis_emp IN (SELECT * FROM cliente) LOOP
total(mis_emp.ced,'empleo',totale);
total(mis_emp.ced,'gasto',totalg);
DBMS_OUTPUT.PUT_LINE(mis_emp.ced ||' Total neto: '|| (totale
- totalg));
END LOOP;
END;
/
Para ejecutar: EXECUTE neto;
08/12/21 Laboratorio de Bases de Datos 18
Sin embargo Total puede hacerse mejor como una función:

DROP PROCEDURE total;

CREATE OR REPLACE FUNCTION total(


codigo cliente.ced%TYPE,
tabla VARCHAR
) RETURN NUMBER IS
mitotal NUMBER(6);
BEGIN
EXECUTE IMMEDIATE 'SELECT NVL(SUM(valor_mensual),0) FROM '|| tabla || '
WHERE ced ='|| codigo
INTO mitotal;
RETURN mitotal;
END;
/

08/12/21 Laboratorio de Bases de Datos 19


Y el procedimiento neto queda así:

CREATE OR REPLACE PROCEDURE neto IS


BEGIN
FOR c IN (SELECT * FROM cliente) LOOP
DBMS_OUTPUT.PUT_LINE( c.ced ||' Neto: '||
(total(c.ced,'empleo') -
total(c.ced,'gasto')));
END LOOP;
END;
/

08/12/21 Laboratorio de Bases de Datos 20


¿Cómo hubiera sido la respuesta del problema usando sólo
SQL puro?

1. Mediante una subconsulta escalar en este caso:

SELECT ced,((SELECT NVL(SUM(valor_mensual),0)


FROM empleo
WHERE ced = c.ced) -
( SELECT NVL(SUM(valor_mensual),0)
FROM gasto
WHERE ced = c.ced)) AS total
FROM cliente c;

Pero las subconsultas correlacionadas (ya sea hacia


"arriba" o hacia "abajo") son en general costosas...

08/12/21 Laboratorio de Bases de Datos 21


2. Otra posibilidad es mediante un oscuro OUTER JOIN:

SELECT ced, (NVL(sumae,0) - NVL(sumag,0)) AS total


FROM
( cliente
NATURAL LEFT OUTER JOIN
(SELECT ced, SUM(valor_mensual) AS sumae
FROM empleo
GROUP BY ced)
)
NATURAL LEFT OUTER JOIN
(SELECT ced, SUM(valor_mensual) AS sumag
FROM gasto
GROUP BY ced
);

Nota: Aquí se está utilizando la sintaxis de Oracle para


los Outer Joins válida a partir de la versión 9i.
08/12/21 Laboratorio de Bases de Datos 22

También podría gustarte