Você está na página 1de 22

PL/SQL

Francisco Moreno
Universidad Nacional
Medellín, 2005
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)
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;
PROCEDIMIENTOS
Sintaxis:
CREATE [OR REPLACE] PROCEDURE
nombre_procedimiento
[( arg1 [modo] tipo [ , arg2 [modo]
tipo...])]
IS | AS
Bloque PL/SQL
• 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
• 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 ¿Y usando
SUBTYPE?
• 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
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);
• 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
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;
Invocación desde otro subprograma:

CREATE OR REPLACE PROCEDURE


invoca_consulta(v_nro IN emp.cod%TYPE)
IS
nombre emp.nom%TYPE;
BEGIN Retornará con el nombre

consulta_emp(v_nro, nombre);
DBMS_OUTPUT.PUT_LINE('El nombre del empleado es: '
|| nombre);
END;
/

Para ejecutar: EXECUTE invoca_consulta(15);


Ejemplo
• Sea el modelo:
pagado
por
GASTO CLIENTE
el respon-
sable de
desempeñando

asignado a

EMPLEO
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)
);
Ingreso de datos:

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


INSERT INTO cliente VALUES(20,'Paco');
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, 'Amante 1');
INSERT INTO gasto VALUES(9, 30, 1000, 'Amante 2');
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.
Soluciones
Versión 1:
Total Gastos Total Ingresos

CREATE OR REPLACE PROCEDURE CREATE OR REPLACE PROCEDURE


totalg( totale(
codigo cliente.ced%TYPE, codigo cliente.ced%TYPE,
total OUT NUMBER) IS total OUT NUMBER) IS
BEGIN BEGIN
SELECT NVL(SUM(valor_mensual),0) SELECT NVL(SUM(valor_mensual),0)
INTO total INTO total
FROM gasto WHERE ced = codigo; FROM empleo WHERE ced = codigo;
END; END;
/ /

Para ejecutar: Para ejecutar:


VAR g NUMBER; VAR e NUMBER;
EXECUTE totalg(10,:g); EXECUTE totale(10,:e);
PRINT g; PRINT e;
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);
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);
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;
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;
/
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;
/
¿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...
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.

Você também pode gostar