Procedimientos, Cursores, Excepciones, Triggers y Paquetes en PL/SQL: Guía Práctica con Ejemplos

Clasificado en Inglés

Escrito el en español con un tamaño de 6,4 KB

Procedimientos y funciones en PL/SQL

En PL/SQL se pueden crear procedimientos almacenados con parámetros de entrada (IN), de salida (OUT) y de entrada/salida (IN OUT). A continuación se muestra un ejemplo básico:

CREATE PROCEDURE imp(ven NUMBER, com IN OUT NUMBER, imp_co OUT NUMBER) IS
BEGIN
  IF ven > 1 THEN
    com := 2;
  END IF;
  imp_co := (com / 1) * ven;
END imp;
/

DECLARE
  com NUMBER := 5;
  pag NUMBER;
BEGIN
  imp(1, com, pag);
END;

Procedimientos con cursores

Un cursor permite recorrer un conjunto de filas. En el siguiente ejemplo se actualizan los salarios de los empleados cuyo salario está por debajo de la media de su oficio:

CREATE PROCEDURE a_s IS
  CURSOR c IS
    SELECT salario, oficio, ROWID FROM emple ORDER BY oficio;
  ofi VARCHAR2(10) := 'SIN';
  medio NUMBER := 0;
BEGIN
  FOR var IN c LOOP
    IF ofi = 'SIN' THEN
      ofi := var.oficio;
      SELECT AVG(NVL(salario, 0)) INTO medio FROM emple WHERE oficio = ofi;
    END IF;
    IF ofi != var.oficio THEN
      ofi := var.oficio;
      SELECT AVG(NVL(salario, 0)) INTO medio FROM emple WHERE oficio = ofi;
    END IF;
    IF medio > var.salario THEN
      UPDATE emple SET salario = salario + 2 WHERE ROWID = var.ROWID;
    END IF;
  END LOOP;
END a_s;
/

Funciones

CREATE FUNCTION a(f1 DATE, f2 DATE) RETURN INTEGER IS
BEGIN
  RETURN MONTHS_BETWEEN(f1, f2) / 12;
END a;
/

Parámetros con valor por defecto

CREATE OR REPLACE PROCEDURE p(n1 NUMBER DEFAULT 0) ...

Cursores explícitos e implícitos

Cursor explícito con OPEN, FETCH y CLOSE

CREATE PROCEDURE emp(n NUMBER) IS
  CURSOR c IS SELECT apellido FROM emple WHERE dept_no = n;
  var emple.apellido%TYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO var;
    IF c%NOTFOUND THEN EXIT; END IF;
    DBMS_OUTPUT.PUT_LINE(var);
  END LOOP;
  CLOSE c;
END emp;
/

EXECUTE emp(30);

Cursor con bucle FOR

CREATE PROCEDURE emp(n NUMBER) IS
  CURSOR c IS SELECT apellido FROM emple WHERE dept_no = n;
BEGIN
  FOR var IN c LOOP
    DBMS_OUTPUT.PUT_LINE(var.apellido);
  END LOOP;
END emp;
/

Cursor implícito

DECLARE
  ape VARCHAR2(10);
BEGIN
  SELECT apellido INTO ape FROM emple WHERE emp_no = 5;
  DBMS_OUTPUT.PUT_LINE(ape);
END;

Gestión de excepciones

Excepciones predefinidas

CREATE PROCEDURE Div_0 IS
  total REAL;
BEGIN
  total := 5 / 0;
EXCEPTION
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('oo');
END Div_0;
/

Excepciones definidas por el usuario

CREATE PROCEDURE Depto(d NUMBER) IS
  CURSOR c IS SELECT apellido FROM emple WHERE dept_no = d;
  n_50 EXCEPTION;
BEGIN
  IF d = 50 THEN RAISE n_50; END IF;
  FOR var IN c LOOP
    DBMS_OUTPUT.PUT_LINE(var.apellido);
  END LOOP;
EXCEPTION
  WHEN n_50 THEN
    DBMS_OUTPUT.PUT_LINE('NO EN 50');
END Depto;
/

Excepciones asociadas a códigos de error de Oracle

DECLARE
  a EXCEPTION;
  PRAGMA EXCEPTION_INIT(a, cod_oracle);
BEGIN
  ...
EXCEPTION
  WHEN a THEN ...
END;

Excepciones definidas con RAISE_APPLICATION_ERROR

IF d = 50 THEN
  RAISE_APPLICATION_ERROR(-20001, 'NO');
END IF;

Control de transacciones

Los SAVEPOINT permiten deshacer parcialmente una transacción:

SAVEPOINT uno;
INSERT ...;
SAVEPOINT dos;
INSERT ...;
IF ... THEN
  ROLLBACK TO uno;
ELSIF ...
  ROLLBACK TO dos;
ELSE
  COMMIT;
END IF;

Cursores con actualización

CREATE PROCEDURE act(num NUMBER, i NUMBER) IS
  CURSOR c IS
    SELECT salario, ROWID FROM emple WHERE dept_no = num;
    -- CURSOR c IS SELECT salario FROM emple WHERE dept_no = num FOR UPDATE;
  n NUMBER := 0;
BEGIN
  SELECT COUNT(*) INTO n FROM emple WHERE dept_no = num;
  IF n = 0 THEN
    RAISE_APPLICATION_ERROR(-20001, 'no exist');
  END IF;
  FOR var IN c LOOP
    IF ... THEN
      UPDATE emple SET salario = salario + i WHERE ROWID = var.ROWID;
      -- UPDATE emple SET salario = salario + i WHERE CURRENT OF c;
    END IF;
  END LOOP;
END act;
/

Triggers

Trigger BEFORE UPDATE

CREATE TRIGGER e BEFORE UPDATE ON emple FOR EACH ROW
DECLARE
  no EXCEPTION;
BEGIN
  DBMS_OUTPUT.PUT_LINE('OK');
  IF :OLD.salario >= 200 THEN
    RAISE no;
  END IF;
END;
/

Trigger de auditoría en tabla de log

CREATE TABLE log (f DATE, m VARCHAR2(50));

CREATE TRIGGER log_e AFTER UPDATE OR INSERT OR DELETE ON emple FOR EACH ROW
DECLARE
  msj log.m%TYPE;
BEGIN
  IF INSERTING THEN
    msj := 'Insertado ' || :NEW.apellido;
  END IF;
  IF UPDATING THEN
    msj := 'Actualizado ' || :OLD.apellido || ' a ' || :NEW.apellido;
  END IF;
  IF DELETING THEN
    msj := 'Borrado ' || :OLD.apellido;
  END IF;
  INSERT INTO log_emple(f, m) VALUES (SYSDATE, msj);
END;
/

Trigger INSTEAD OF sobre una vista

CREATE TRIGGER v1t INSTEAD OF INSERT OR UPDATE ON vi1
DECLARE
BEGIN
  IF INSERTING THEN
    INSERT INTO clientes (cod, nom) VALUES (:NEW.cod, :NEW.nom);
    INSERT INTO pedidos (num, cli) VALUES (:NEW.num, :NEW.cod);
  END IF;
  IF UPDATING THEN
    INSERT INTO clientes (cod, nom) VALUES (:NEW.cod, :OLD.nom);
    UPDATE pedidos SET cli = :NEW.cod WHERE cli = :OLD.cod;
    DELETE FROM clientes WHERE cod = :OLD.cod;
  END IF;
END;
/

Paquetes

Especificación del paquete

CREATE PACKAGE g AS
  PROCEDURE s(i emple.salario%TYPE);
END;

Cuerpo del paquete

CREATE PACKAGE BODY g AS
  PROCEDURE s(i emple.salario%TYPE) IS
  BEGIN
    UPDATE emple SET salario = i;
  END;
END;
/

EXECUTE g.s(18);

Seguridad: roles, usuarios y perfiles

Roles y privilegios

CREATE ROLE e;
GRANT CREATE SESSION TO e;
GRANT INSERT ON emple TO e;
GRANT INSERT ON depart TO e;
GRANT SELECT ANY TABLE TO e;

Usuarios

CREATE USER fer IDENTIFIED BY perro;
GRANT e TO fer;

Perfiles

CREATE PROFILE dos LIMIT SESSIONS_PER_USER 2;
ALTER USER fer IDENTIFIED BY perro PROFILE dos;

Eliminación de objetos

DROP ROLE e;
DROP USER fer;
DROP PROFILE dos;
DROP TRIGGER t;

Entradas relacionadas: