sábado, 29 de octubre de 2016

Manejando Archivos de Texto con PL/SQL (UTL_FILE)

Objetivos:
• Generar/Leer archivos de texto con PL/SQL.
• Conocer los métodos y funciones del paquete UTL_FILE.
• Crear reportes directamente de programas de PL/SQL.
• Ver ejemplos de uso práctico.
NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
____________________________________________________________________________________
Paquete UTL_FILE.
El paquete UTL_FILE permite leer y generar archivos de texto desde programas de PL/SQL. Hay que tener en cuenta que para ello, dichos archivos y las rutas donde se encuentran deben ser accesibles desde el servidor en el cual se ejecuta la instancia de Base de Datos. La lectura/escritura de archivos (File I/O) es una funcionalidad que se introdujo con PL/SQL Release 2.3 (Oracle 7.3 u Oracle 8.0) y es compatible con versiones posteriores. Luego de esta versión, es posible cargar datos de archivos directamente en Tablas de la Base de Datos mientras se aplica toda la potencia y flexibilidad de programación PL/SQL. Puede generar informes directamente desde PL/SQL sin tener que preocuparse acerca de las restricciones de buffer máximo de DBMS_OUTPUT.[1]
Métodos del Paquete: UTL_FILE.
Nombre
Descripción
FCLOSE
Cierra el archivo especificado.
FCLOSE_ALL
Cierra todos los archivos abiertos.
FFLUSH
Vacía todos los datos de la memoria buffer de UTL_FILE.
FOPEN
Abre el archivo especificado.
GET_LINE
Extrae la próxima línea del archivo.
IS_OPEN
Retorna TRUE si el archivo está abierto.
NEW_LINE
Inserta una nueva marca de línea al final de la línea actual de archivo.
PUT
Agrega texto al buffer.
PUT_LINE
Agrega una línea de texto al archivo.
PUTF
Agrega texto formateado en el buffer.
____________________________________________________________________________________
Ejemplos:

CREATE OR REPLACE DIRECTORY dir_archivos
  AS  'F:\DIR';
/*Debe luego crear la carpeta de manera manual en la dirección que especifique, en mi caso la cree en el disco F:/.*/
---
CREATE SEQUENCE seq_archivo
  START WITH 1
  INCREMENT BY 1;
/*En las sentencias anteriores creamos un directorio y una secuencia para usarlos en el próximo ejemplo.*/
---
CREATE OR REPLACE PROCEDURE p_crear_archivo
  IS
    v_archivo     UTL_FILE.FILE_TYPE;
    v_seq         NUMBER;
    v_datos       VARCHAR2(500);
    v_ult_dept    departments.department_id%TYPE;
    v_total_sal   NUMBER:= 0;
    v_total_emp   NUMBER:=  0;
--
    CURSOR c_emp_dept IS
        SELECT
            e.first_name||' '||e.last_name AS nombre,
            e.salary AS salario,
            d.department_id AS cod_dept,
            d.department_name AS departamento,
            l.city AS ciudad
        FROM employees e, departments d, locations l
        WHERE e.department_id = d.department_id
        AND d.location_id = l.location_id
        ORDER BY d.department_id;
BEGIN
    v_seq       :=  seq_archivo.NEXTVAL;
    v_archivo   := UTL_FILE.FOPEN
                                  (
                                    'DIR_ARCHIVOS',
                                    'DETALLE_EMPLEADOS'||v_seq||'.CSV',
                                    'W'
                                  );

    UTL_FILE.PUT_LINE(v_archivo,'Documento: Empleados por Departamento.'||CHR(10));
    UTL_FILE.PUT_LINE(v_archivo,'Fecha: ,Lugar: ');
    UTL_FILE.PUT_LINE(v_archivo,TO_CHAR(SYSDATE
                      'fmDD "de" MONTH "DEL" YYYY','nls_date_language=SPANISH')||', Santo Domingo. RD.'||CHR(10));
    UTL_FILE.PUT_LINE(v_archivo,'Empleado, Salario, Departamento, Ciudad');
--
    FOR rec IN c_emp_dept LOOP
/*La siguiente validación agrega una linea con algunos detalles del último departamento, esto ocurre cada vez que cambia el departamento.*/
        IF NVL(v_ult_dept, rec.cod_dept) != rec.cod_dept THEN
            UTL_FILE.PUT_LINE(v_archivo,'Cantidad Empleados: '||v_total_emp||',Total Salario: '||v_total_sal||CHR(10));
            v_total_emp := 0;
            v_total_sal := 0;
        END IF;
---A continuación la variable: v_datos se llena con los datos extraídos del Cursor: c_emp_dept 
        v_datos   :=
                    '"'||rec.nombre||
                    '","'||rec.salario||
                    '","'||rec.departamento||
                    '","'||rec.ciudad||
                    '"';
/*Las comillas dobles(") impiden que cualquier coma en la data dañe el reporte. Cada coma(,) representa una nueva columna en el archivo, por esto usamos las comillas dobles para especificar que lo encerrado en ellas son literales.*/
        v_total_emp     := v_total_emp+1;
        v_ult_dept      := rec.cod_dept;
        v_total_sal     := v_total_sal+rec.salario;
--
        UTL_FILE.PUT_LINE(v_archivo,v_datos);
--
    END LOOP;
--
    UTL_FILE.PUT_LINE(v_archivo,'Cantidad Empleados: '||v_total_emp||',Total Salario: '||v_total_sal);
--
    UTL_FILE.FCLOSE(v_archivo);
END;
/
SHOW ERROR

/*Como las funciones/procedimientos usadas en este ejemplo fueron ya explicadas anteriormente, me limitare a dar detalles, si tienen dudas puede comentar.*/
---
BEGIN
    p_crear_archivo;
END;
/*Creo este bloque anónimo para invocar el procedimiento: p_crear_archivo y así crear el Archivo.*/
---OUTPUT:

Nota: en la publicación: Leyendo archivos con UTL_FILE en ORACLE presentamos como leer un archivo previamente creado en disco usando PL/SQL.

jueves, 27 de octubre de 2016

Paquetes de PL/SQL

Objetivos:
• Conocer que es un Paquete.
• Ver las ventajas de su uso.
• Crear la especificación y el cuerpo de un Paquete.
• Las Variables y Subprogramas Privados de un Paquete.
• Ver ejemplos de algunos Paquetes.

NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
____________________________________________________________________________________
Los Paquetes de PL/SQL .
Como su nombre infiere, es una envoltura que agrupa un conjunto de objectos relacionados hasta cierto punto. Enfocado en PL/SQL un paquete puede ser definido como:

Un objeto de esquema que agrupa objectos relacionados(PL/SQL types, variables, y subprogramas) de forma lógica. Por lo general los Paquetes contienen dos partes, una especificación y un cuerpo, aunque a veces el cuerpo es innecesario. La especificación es donde se define la interfaz de tus aplicaciones; en la cual se declaran los tipos(types), variables, constantes, excepciones, cursores y subprogramas. En el cuerpo es donde se le da uso a los objectos antes declarados, ademas de ser donde se desarrolla/implementa la lógica completa del paquete.[1]

Sintaxis:
--Especificación
CREATE [ OR REPLACE ] PACKAGE [ schema. ]package
   [ invoker_rights_clause ]
   { IS | AS } pl/sql_package_spec;

--Cuerpo
CREATE OR REPLACE ] PACKAGE BODY [ schema. ]package
   { IS AS } pl/sql_package_body;
____________________________________________________________________________________
Ventajas de los Paquetes.

Modularidad: Los Paquetes permiten encapsular tipos(types), objectos y subprogramas relacionados lógicamente en un módulo de PL/SQL. Esto los hace fácil de entender, con interfaces simples, claras y bien definidas, para así facilitar el desarrollo de aplicaciones.

Fácil diseño de aplicaciones: Al diseñar una aplicación, todo lo que necesita inicialmente es la información de interfaz en la especificación del paquete. Puede codificar y compilar una especificación sin su cuerpo. Luego, puede compilar los subprogramas independientes(standalone) que hacen referencia el paquete. No es necesario definir el cuerpo del paquete completo hasta que esté listo para completar la aplicación.

Ocultación de información: Con los paquetes, puede especificar cuales objectos son públicos (visibles y accesibles) y cuales son privados (ocultos e inaccesible). Por ejemplo, si un paquete contiene cuatro subprogramas, tres podrían ser públicos y uno privado. El paquete oculta la implementación del subprograma privado para que sólo el paquete (no su aplicación) se vea afectada si la implementación cambia. Esto simplifica el mantenimiento y los cambios para mejora. Además, al ocultar los detalles de implementación de los usuarios, se protege la integridad del paquete.

Funcionalidad Añadida: Los cursores y variables públicas del paquete ​​persisten durante la duración de una sesión. Por lo tanto, pueden ser compartidas por todos los subprogramas que se ejecutan en el ambiente. Además, le permiten mantener datos a través de transacciones sin tener que almacenarla en la Base de Datos.

Mejor desempeño: Cuando ​​por primera vez se llama un subprograma de un paquete, todo el paquete se carga en memoria. Por lo tanto, las llamadas posteriores a subprogramas relacionados en el paquete no requieren I/O de disco.

Además, los paquetes impiden las dependencias en cascada y la recompilación innecesaria. Por ejemplo, si cambia el cuerpo de una función en un paquete, Oracle no recompila otros subprogramas que invocan la función, Esto porque dichos subprogramas sólo dependen de los parámetros y valor de retorno declarados en la especificación.
____________________________________________________________________________________
Especificación y Cuerpo del Paquete.
Especificación: Como ya especificamos, en la Especificación se declaran los objectos públicos. El alcance de un objecto público es el esquema del paquete. Un objecto público es visible en todas partes del esquema. Para hacer referencia a un objecto público que está en su alcance pero no es visible, debe preceder el nombre del objecto con el nombre del paquete. Eje: package_name.object_name.

La Especificación enumera los recursos del paquetes disponibles para la aplicación. Toda la información o recursos que su aplicación necesita utilizar se encuentran en la Especificación.

Cuerpo: es donde se implementa la Especificación del Paquete. Es decir, el Cuerpo del Paquete contiene la implementación de cada cursor y subprograma declarado en la Especificación del Paquete. Tenga en cuenta que los subprogramas definidos en el Cuerpo del Paquete son accesibles fuera del mismo sólo si sus características/especificación también aparecen en la Especificación del Paquete.

El Cuerpo del Paquete también puede contener declaraciones privadas, que definen tipos y objetos necesarios para el funcionamiento interno del paquete. El alcance de estas declaraciones es local en el cuerpo del paquete. Por lo tanto, los objectos declarados son inaccesibles, excepto desde el interior del Cuerpo del Paquete. A diferencia de la Especificación, el Cuerpo del Paquete puede contener la implementecion completa de los subprogramas.

Después de la parte declarativa del Cuerpo del Paquete está la parte de inicialización opcional, que típicamente contiene declaraciones que inicializan algunas de las variables previamente declaradas en el paquete.

La parte de inicialización de un paquete juega un papel menor, ya que, a diferencia de los subprogramas, un paquete no se puede llamar o pasar parámetros. Como resultado, la parte de inicialización de un paquete se ejecuta sólo una vez, la primera vez que se hace referencia al paquete.

Recuerde, si la Especificación de un paquete sólo declara tipos(types), constantes, variables y excepciones, el Cuerpo del Paquete es innecesario. Sin embargo, el Cuerpo todavía se puede utilizar para inicializar objectos declarados en la Especificación del Paquete.
____________________________________________________________________________________
Ejemplos Prácticos.

CREATE OR REPLACE PACKAGE hr.pkg_empleado IS

    --Sección de Variables y Tipos Records

    TYPE  typ_emp_details IS RECORD
      (
          nombre              VARCHAR2(60),
          tiempo_vig          NUMBER(2),  --Tiempo Vigente en la empresa
          departamento_act    hr.departments.department_name%TYPE,
          empleo              hr.jobs.job_title%TYPE,
          direccion           VARCHAR2(150)
      );

    TYPE  typ_empleo_hist IS TABLE OF

        hr.jobs.job_title%TYPE
            INDEX BY pls_integer;

    TYPE  typ_dept_hist IS TABLE OF

        hr.departments.department_name%TYPE
            INDEX BY pls_integer;

    PROCEDURE proc_emp_details

                          (
                              p_cod_emp       IN    hr.employees.employee_id%TYPE,
                              p_emp_details   OUT   typ_emp_details
                          );

    FUNCTION func_emp_hist

                      (
                          p_cod_emp       IN    hr.employees.employee_id%TYPE
                      ) RETURN typ_empleo_hist;
END pkg_empleado;
/*Este SCRIPT muestra como crear la Especificación de un Paquete de PL/SQL.*/
---
• La Especificación del paquete pkg_empleado define 1 tipo Record: typ_emp_details, dos tipos Table: typ_empleo_hist typ_dept_hist, un Procedimiento: proc_emp_details y una Función: func_emp_hist.

• Notar que el Procedimiento: proc_emp_details recibe como parámetro de entrada: p_cod_emp, que seria un numero de empleado: employee_id y retorna un parámetro de salida: p_emp_details, el cual es tipo: typ_emp_details definido en el mismo paquete.

• La función: func_emp_hist por igual recibe el numero de empleado y retorna un tipo: typ_empleo_hist definido también en el paquete.

CREATE OR REPLACE PACKAGE BODY hr.pkg_empleado IS

        ----Objetos Privados del Paquete
          v_valid_emp     NUMBER    :=  0;

          CURSOR  cur_valid_emp --Cursor para validar si el empleado existe.
                            (
                                p_emp hr.employees.employee_id%TYPE
                            ) IS
              SELECT 1
              FROM hr.employees
              WHERE employee_id = p_emp;

    ---Implementación de los procedimientos y funciones definidas en la Especificación del Paquete.
    PROCEDURE proc_emp_details
                          (
                              p_cod_emp       IN    hr.employees.employee_id%TYPE,
                              p_emp_details   OUT   typ_emp_details
                          ) IS

          CURSOR  cur_emp_details IS
              SELECT
                    e.first_name||' '||e.last_name AS nombre,
                    TRUNC(MONTHS_BETWEEN(SYSDATE, e.hire_date)/12AS tiempo,
                    d.department_name AS departamento,
                    j.job_title AS empleo,
                    NVL(l.street_address, 'No Especificado')||', '||l.city||', '||
                        NVL(l.state_province, ' ')||', '||c.country_name AS direccion
              FROM hr.employees e, hr.departments d, hr.jobs j, hr.locations l, hr.countries c
              WHERE e.employee_id = p_cod_emp
              AND e.department_id = d.department_id
              AND e.job_id = j.job_id
              AND d.location_id = l.location_id
              AND l.country_id = c.country_id;

      BEGIN

          OPEN    cur_valid_emp(p_cod_emp);
          FETCH   cur_valid_emp INTO v_valid_emp;
          CLOSE   cur_valid_emp;

          IF v_valid_emp < 1 THEN
              RAISE_APPLICATION_ERROR(-20001, 'El Código de Empleado '||p_cod_emp||' no es valido. Favor Validar!!!');
          END IF;

          OPEN    cur_emp_details;
          FETCH   cur_emp_details INTO p_emp_details;
          CLOSE   cur_emp_details;
      END proc_emp_details;

    FUNCTION func_emp_hist
                      (
                          p_cod_emp       IN    hr.employees.employee_id%TYPE
                      ) RETURN typ_empleo_hist  IS

          rec_empleo_hist         typ_empleo_hist;
          v_count                 NUMBER(2)   := 1;

          CURSOR  cur_emp_hist
                            (
                                p_emp   hr.employees.employee_id%TYPE
                            ) IS
              SELECT
                    j.job_title AS empleo
              FROM hr.job_history jh, hr.jobs j
              WHERE employee_id = p_emp
              AND j.job_id = jh.job_id
              ORDER BY start_date ASC;

      BEGIN

          OPEN    cur_valid_emp(p_cod_emp);
          FETCH   cur_valid_emp INTO v_valid_emp;
          CLOSE   cur_valid_emp;

          IF v_valid_emp < 1 THEN
              RAISE_APPLICATION_ERROR(-20002, 'El Código de Empleado '||p_cod_emp||' no es valido. Favor Validar!!!');
          END IF;

          FOR i IN  cur_emp_hist(p_cod_emp) LOOP
              rec_empleo_hist(v_count) := i.empleo;

              v_count :=  v_count+1;
          END LOOP;

          RETURN  rec_empleo_hist;

      END func_emp_hist;
END pkg_empleado;
/
SHOW ERROR
/*Este SCRIPT muestra como crear El Cuerpo de un Paquete de PL/SQL.*/
---
• En el Cuerpo de Paquetepkg_empleado notamos el desarrollo de los objetos definidos en la Especificación del mismo, ademas de algunos objectos definidos solo en el Cuerpo (Variable: v_valid_emp y Cursor: cur_valid_emp), los cuales son privados ya que solo es posible usarlos dentro del paquete.

SET SERVEROUTPUT ON
DECLARE
    rec_emp_details   hr.pkg_empleado.typ_emp_details;
BEGIN
    hr.pkg_empleado.proc_emp_details
                              (
                                100,
                                rec_emp_details
                              );
    DBMS_OUTPUT.PUT_LINE
                      (
                          'Empleado: '||rec_emp_details.nombre||CHR(10)||
                          'Años en la Empresa: '||rec_emp_details.tiempo_vig||CHR(10)||
                          'Departamento Actual: '||rec_emp_details.departamento_act||CHR(10)||
                          'Empleo Actual: '||rec_emp_details.empleo||CHR(10)||
                          'Dirección Actual: '||rec_emp_details.direccion
                      );
END;
/*Creamos este bloque anónimo para dar uso al paquete: pkg_empleado, notar como debemos crear una variable tipo Record: typ_emp_details el cual pertenece al mismo paquete y es del tipo de retorno del procedimiento: proc_emp_details; Mas adelante con PUT_LINE mostramos los datos extraídos.*/
---OUTPUT:
SET SERVEROUTPUT ON
DECLARE
    rec_empleo_hist   hr.pkg_empleado.typ_empleo_hist;
BEGIN
    rec_empleo_hist :=  hr.pkg_empleado.func_emp_hist(101);

    IF rec_empleo_hist.COUNT > 0 THEN
        FOR i IN  rec_empleo_hist.FIRST..rec_empleo_hist.LAST LOOP
            DBMS_OUTPUT.PUT_LINE
                              (
                                  'Empleo '||i||' : '||rec_empleo_hist(i)
                              );
        END LOOP;
    ELSE
        DBMS_OUTPUT.PUT_LINE('El Empleado no ha cambiado de Empleo.');
    END IF;
END;
/*Por igual, en este bloque anónimo creamos una variable tipo tabla: typ_empleo_hist, definido en el paquete, esto porque la Función: func_emp_hist retorna ese tipo de dato.*/
---OUTPUT:
_____________________________________________________________________________________
Fuentes: Oracle Database: PL/SQL Fundamentals,
https://docs.oracle.com/database/121/LNPLS/packages.htm#LNPLS00905

martes, 18 de octubre de 2016

Función: Cual es Nulo?

Objetivos:
• Desarrollar una función que reciba X cantidad de parámetros y determine cual o cuales de ellos son nulos.
• Ver lo flexible que resulta PL/SQL.
____________________________________________________________________________________
Introducción.
Este requerimiento es básico en cualquier lenguaje de programación, de hecho suena bastante trivial, pero en realidad lo que queremos es plantear un escenario algo distinto al que a diario vemos. Comúnmente nos valemos de estructuras de control condicionales(CASE, IF ELSE) para así lograr este cometido, es por ello que les presento un enfoque algo distinto con cierto grado de eficiencia para llevar a cabo el fin.

Por supuesto, una vez determinemos cuales son nulos es inevitable usar las mencionadas estructuras de control para así ejecutar la acción deseada, pero esta tarea puede ser desarrollada en la aplicación final (Eje: Oracle Forms, JAVA) que hace uso de la función.
____________________________________________________________________________________
Bloque Anónimo con la Lógica de la Función.

SET SERVEROUTPUT ON
SET VERIFY OFF
DECLARE
    v_1 VARCHAR2(15);
v_2 VARCHAR2(15);
v_3 VARCHAR2(15);
--
CURSOR c_nulls
(
p_1 VARCHAR2,
p_2 VARCHAR2,
p_3 VARCHAR2
) IS
SELECT
 DECODE (p_1,NULL,0,1)+  --Si es nulo retorna 0 de lo contrario 1
  DECODE (p_2,NULL,0,2)+  --Si es nulo retorna 0 de lo contrario 2
  DECODE (p_3,NULL,0,4)  --Si es nulo retorna 0 de lo contrario 4
FROM dual;  --En si, este SELECT suma los valores antes generados.
--
v_resultado NUMBER(1);
BEGIN
DBMS_OUTPUT.PUT_LINE(REPLACE( --Uso el REPLACE para que no se muestren los espacios tabulares
                        '¿Cual o cuales son Nulos?
                        Ninguno: 7,
                        Solo el Primero: 6,
                        Solo el Segundo: 5,
                        Solo el Tercero: 3,
                        El Primero y el Segundo: 4,
                        El Primero y el Tercero: 2,
                        El Segundo y el Tercero: 1,
                        Todos son Nulos: 0'
                      ,'                        ',''));
--
v_1 := '&PARAM1';
v_2 := '&PARAM2';
v_3 := '&PARAM3';
--
OPEN c_nulls(v_1, v_2, v_3);
FETCH c_nulls INTO v_resultado;
CLOSE c_nulls;
--
DBMS_OUTPUT.PUT_LINE(CHR(10)||'Resultado: '||v_resultado);
END;
/*Primero que nada, creamos este bloque anónimo que tiene el mismo enfoque de la función a continuación. Notar como el bloque imprime en pantalla el criterio de evaluación sin la necesidad de usar estructuras se control condicionales.*/
---OUTPUT:
____________________________________________________________________________________
Función Cual_es_NULL.

CREATE OR REPLACE FUNCTION cual_es_null
(
p_1 VARCHAR2,
p_2 VARCHAR2,
p_3 VARCHAR2
) RETURN NUMBER IS
CURSOR c_nulls
(
cp_1 VARCHAR2,
cp_2 VARCHAR2,
cp_3 VARCHAR2
) IS
SELECT
 DECODE (cp_1,NULL,0,1)+
  DECODE (cp_2,NULL,0,2)+
  DECODE (cp_3,NULL,0,4)
FROM dual;
--
v_resultado NUMBER(1);
BEGIN
OPEN c_nulls(p_1, p_2, p_3);
FETCH c_nulls INTO v_resultado;
CLOSE c_nulls;
--
RETURN v_resultado;
END;
/
SHOW ERROR
/*He Aquí la Función. Para simplificar el código y facilitar el análisis decidimos hacerla que solo acepte 3 parámetros.*/
---
SELECT
        cual_es_null(NULL,'HOLA','K'),
        cual_es_null(8,NULL,'N*'),
        cual_es_null(NULL,NULL,NULL),
        cual_es_null(5,8,9),
        cual_es_null('HOLA',78,NULL)
FROM dual;
/*Probamos la función.*/
---OUTPUT:

domingo, 16 de octubre de 2016

Disparadores/Triggers

Objetivos:
• Conocer los procedimientos almacenados llamados Triggers/Disparadores.
• Conocer sus usos mas comunes.
• Identificar diferentes Tipos de Triggers.
• Crear/modificar Trigger de acuerdo a nuestra necesidad.

NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
____________________________________________________________________________________
Los Triggers.
Un Trigger es un tipo especial de procedimiento almacenado que se ejecuta automáticamente cuando se produce un evento en el servidor de Base de Datos. Los Triggers DML se ejecutan cuando un usuario intenta modificar los datos a través de una sentencia DML. Los eventos DML son INSERT, UPDATE DELETE en una tabla o vista.[1]

Los Triggers son programas almacenados que se ejecutan o disparan automáticamente cuando se producen algunos eventos. Los Triggers son escritos para ser ejecutado en respuesta a cualquiera de los siguientes eventos: Una sentencia de manipulación de Bases de Datos (DML) (DELETEINSERT UPDATE).[2]

Oracle le permite definir procedimientos llamados Triggers que se ejecutan de manera implícita cuando un DELETEINSERT UPDATE se emite en una tabla asociada o, en algunos casos, en una vista, o hasta cuando se producen acciones en el sistema de Base de Datos. Estos procedimientos pueden ser escritos en PL/SQL o Java y se almacenan en la Base de Datos.

Los Triggers son similares a los procedimientos almacenados. Un Trigger almacenado en la Base de Datos puede incluir SQLPL/SQL o Java para funcionar como una unidad y puede invocar procedimientos almacenados. Sin embargo, los procedimientos y Triggers difieren en la forma en que se invocan. Un procedimiento se ejecuta de forma explícita por un usuario, aplicación o por un Trigger. Los Triggers se ejecutan de forma implícita por parte de Oracle cuando se produce un evento de activación, sin importar qué usuario está conectado o la aplicación que se está utilizando.[3]

Nota: El nombre mas común es Triggers pero también son conocidos como Desencadenadores o Disparadores.

Sintaxis:
CREATE OR REPLACE TRIGGER [ schema. ]trigger
   { BEFORE AFTER INSTEAD OF }
   { dml_event_clause
   | { ddl_event [ OR ddl_event ]...
     | database_event [ OR database_event ]...
     }
     ON { [ schema. ]SCHEMA
        | DATABASE
        }
   }
   [ WHEN (condition) ]
   { pl/sql_block | call_procedure_statement } ;
____________________________________________________________________________________
Sus Usos:
Los Triggers complementan las capacidades básicas de Oracle para proporcionar un sistema de gestión de Base de Datos altamente personalizado. Por ejemplo, un Trigger puede restringir las operaciones DML en una tabla para que solo se ejecuten en horario de oficina. También pueden ser usados para:

• Generar automáticamente valores derivados en columnas.
• Evitar transacciones inválidas. 
• Imponer complejas autorizaciones de seguridad.
• Exigir integridad referencial entre los nodos en una Base de Datos distribuida.
• Hacer cumplir reglas de negocio complejas.
• Proporcionar un registro de eventos transparente.
• Proporcionar auditoría.
• Mantener réplicas síncronas de tablas.
• Recopilar estadísticas de acceso en tablas.
• Modificar datos de una tabla cuando se emiten sentencias DML en vistas.
• Publicar información sobre eventos de Base de Datos, eventos de usuario, y sentencias SQL a aplicaciones suscritas.

Evento Disparador:
Un evento disparador es una sentencia SQL, evento de Base de Datos, o evento de usuario que  aprieta el gatillo o provoca la activación del Trigger. Los cuales pueden ser:

• Un INSERT, UPDATE o DELETE en una tabla específica (o una vista, en algunos casos).
• Un CREATE, ALTER o DROP en cualquier objeto de esquema.
• Un arranque(startup) de Base de Datos o un cierre(shutdown) de instancia.
• Un mensaje de error en particular o cualquier mensaje de error.
• Un inicio o cierre de sesión de un usuario de Base de Datos.
____________________________________________________________________________________
Tipos de Triggers:
• Triggers de fila y Triggers de sentencia (Row Triggers and Statement Triggers).
• Antes y después (BEFORE and AFTER Triggers).
• Triggers EN LUGAR DE (INSTEAD OF Triggers).
• Triggers de eventos del sistema y eventos de usuario (Triggers on System Events and User Events).

Triggers de fila y Triggers de sentencia (Row Triggers and Statement Triggers).
Al definir un Trigger, puede especificar el número de veces que la acción de disparo se va a ejecutar:

• Triggers de fila/Row Triggers: Una vez por cada fila afectada por la sentencia de activación, ejemplo: se dispara por una sentencia UPDATE que actualiza varias filas. Si una sentencia de activación no afecta alguna fila, el Trigger de fila no se ejecuta.

• Triggers de sentencia/Statement Triggers: Una vez por la sentencia de activación sin importar el número de filas que afecta.

Antes y después (BEFORE and AFTER Triggers).
También puede especificar el tiempo(cuando) de disparo; esto es, si la acción se va a ejecutar antes o después de la sentencia de activación. Los Triggers Antes y Después pueden aplicarse/combinarse con los Triggers de fila y Triggers de sentencia.

Los Triggers Antes y Después son activados/lanzados por sentencias DML y puede definirse sólo en tablas, no en vistas. Sin embargo, los Triggers en las tablas base de una vista se disparan si un INSERT, UPDATE o DELETE se emite en la vista. Los Triggers Antes y Después activados/lanzados por sentencias DDL sólo pueden ser definidos en la Base de Datos o un esquema, no en tablas particulares.

BEFORE Triggers:
Los BEFORE Triggers  ejecutan la acción antes que la sentencia de activación se haya ejecutado. Este tipo de Triggers se utiliza comúnmente en las siguientes situaciones:

• Cuando la acción del Trigger determina si la sentencia de activación se debe permitir que complete. 
• Para derivar valores específicos de columnas antes de completar una sentencia INSERT UPDATE.

AFTER Triggers:
Los Triggers AFTER ejecutan la acción después de ejecutar la sentencia de activación. Comúnmente usados para hacer copias en tablas que sirven de espejo.

Acceso a los valores de columna en Triggers de fila:
Dentro del cuerpo de un Triggers de fila, las sentencias de SQL y PL/SQL tienen acceso a los nuevos  y antiguos valores de columna en la actual fila procesada. Existen dos nombres que hacen referencia a cada columna de la tabla que se está modificado: una para el valor antiguo, y otra para el nuevo. Dependiendo del tipo de sentencia de activación, ciertos nombres podrían no tener significado.
• :OLD = Hace referencia al valor actual, el cual será modificado o eliminado.
• :NEW = Hace referencia al nuevo valor a ser establecido.
Un Trigger disparado por una sentencia INSERT solamente tiene acceso a los valores de columna  nuevos (:NEW). Debido a que la fila está siendo creada por el INSERT, los viejos valores son nulos.

Un Trigger disparado por una sentencia UPDATE tiene acceso tanto a los valores antiguos(:OLD) como nuevos (:NEW) de columna, esto aplica para los Triggers de fila BEFORE y AFTER.

Un Trigger disparado por una sentencia DELETE tiene un acceso solamente a los valores de columna :OLD. Debido a que una vez suprimida, la fila ya no existe, los valores :NEW son NULL. Nota: no es posible modificar los nuevos valores, de tratar se hacerlo una EXCEPCIÓN se levantaría.

Los antiguos (:OLD)  y nuevos (:NEW) valores están disponibles tanto antes (BEFORE) como después (AFTER) en los Triggers de fila. Un valor :NEW puede ser modificado en un Trigger de fila BEFORE, pero no en un Trigger de fila AFTER (esto porque la sentencia de activación es disparada antes que el Trigger de fila AFTER sea lanzado). Si un Trigger de fila BEFORE cambia el valor de :new.column, entonces, un Trigger de fila AFTER disparado por la misma instrucción ve el último valor asignado en el Trigger de fila BEFORE.

Los identificadores NEW OLD pueden ser usados en expresiones booleanas de una cláusula WHEN. Notar que dos puntos (:) deben preceder a ambos identificadores cuando se utilizan dentro del cuerpo del Trigger, pero esto no aplica cuando los mismo son usados la cláusula WHEN o en la opción REFERENCING.

INSTEAD OF Triggers:
Los INSTEAD OF Triggers proporcionan una manera transparente de modificar vistas que no deben ser modificadas directamente a través de sentencias DML (INSERTUPDATE y DELETE). Estos Triggers son llamados INSTEAD OF Triggers, ya que, a diferencia de otros tipos, Oracle dispara el Trigger en lugar de ejecutar la sentencia de activación.

Puede escribir sentencias INSERTUPDATE DELETE en vistas y el INSTEAD OF Trigger se dispara para actualizar las tablas subyacentes de manera apropiada. Los INSTEAD OF Triggers se activan por cada fila de la vista modificada.

Triggers de eventos del sistema y eventos de usuario (Triggers on System Events and User Events).
Puede utilizar Triggers para publicar información sobre eventos de Base de Datos a los suscriptores. Las aplicaciones pueden suscribirse a eventos de Base de Datos tal y como se suscriben a mensajes de otras aplicaciones. Estos eventos de Base de Datos pueden incluir:

• Eventos del sistema.
--Puesta en marcha(startup) y cierre(shutdown) de la Base de Datos.
--Eventos de error del servidor.
Eventos de usuario.
--Cuando el usuario inicia o cierra sesión.
--Sentencias DDL (CREATE, ALTER y DROP).
--Sentencias DML (INSERT, DELETE y UPDATE).
Los Triggers de eventos del sistema se pueden definir a nivel de Base de Datos o a nivel de esquema. Por ejemplo, un Trigger de cierre de Base de Batos se define a nivel de Base de Batos:

CREATE OR REPLACE TRIGGER trg_inicio_seccion 
  ON DATABASE STARTUP 
    BEGIN 
...
DBMS_AQ.ENQUEUE(...); 
... 
    END;
/**/
---
Los Triggers de sentencias DDL o eventos de inicio/cierre de sesión también se puede definir a nivel de Base de Datos o a nivel de esquema. Los Triggers en sentencias DML se pueden definir en una tabla o vista. Un Trigger definido a nivel de Base de Datos es disparado para todos los usuarios, mientras que un Trigger definido a nivel de esquema o tabla, ejecuta sólo cuando el evento de activación implica el esquema o tabla en cuestión.
____________________________________________________________________________________
Eliminar o Deshabilitar un Trigger.
Puede eliminar un Trigger con la sentencia DROP TRIGGER seguido del nombre del Trigger. Tome en cuenta en el Trigger debe estar en su propio esquema, de no estarlo, debe tener el privilegio de sistema DROP ANY TRIGGER. Del mismo modo si desea eliminar un Trigger de Base de Datos debe tener el privilegio ADMINISTER DATABASE TRIGGER.

Sintaxis:
DROP TRIGGER trigger_name;


Adicionalmente puede deshabilitar, habilitar o compilar un Trigger de Base de Datos con la sentencia ALTER TRIGGER. De igual manera si el Trigger pertenece a otro esquema debe tener el privilegio ALTER ANY TRIGGER, y si el Trigger es de Base de Datos debe tener el privilegio ADMINISTER DATABASE TRIGGER.

Sintaxis:
ALTER TRIGGER [ schema. ] trigger
  { { ENABLE | DISABLE }
  | RENAME TO new_name
  | COMPILE [ DEBUG ] [ compiler_parameters_clause ... ] [ REUSE SETTINGS ]
  };
____________________________________________________________________________________
Ejemplos:

CREATE OR REPLACE TRIGGER trg_secure_history
BEFORE INSERT OR UPDATE OR DELETE ON job_history
BEGIN
IF USER NOT IN ('SYSTEM','SYS') THEN
RAISE_APPLICATION_ERROR(-20001,'Esta tabla no debe ser modificada bajo ninguna circunstancia');
END IF;
END trg_secure_history;
/
SHOW ERROR
/*Creamos el Trigger: trg_secure_history, el cual restringe a todos usuarios  (excepto system y sys) el privilegio de modificar los datos de la tabla job_history. Este Trigger cabe en la clasificación de Trigger de sentencia y BEFORE Trigger.*/
---
INSERT INTO job_history
                      (
                        employee_id,
                        start_date,
                        end_date,
                        job_id,
                        department_id
                      )
VALUES
      (
        101,
        ADD_MONTHS(SYSDATE,-18),  --resta 18 meses a fecha actual.
        SYSDATE,
        'IT_PROG',
        100
      );
/*Conectados con hr, probamos el Trigger: trg_secure_history tratando de insertar un nuevo registro a la tabla: job_history.*/
---OUTPUT:
ALTER TRIGGER update_job_history
  DISABLE;
/*El ejemplo deshabilita el Trigger: update_job_history el cual viene por defecto en la tabla employees del esquema hr.*/
---
DROP TRIGGER trg_secure_history;
/*El ejemplo elimina el Trigger: trg_secure_history creado anteriormente.*/
---
CREATE OR REPLACE TRIGGER update_history
AFTER UPDATE OF job_id, department_id OR DELETE ON employees
FOR EACH ROW
BEGIN
INSERT INTO hr.job_history
  (
 employee_id,
 start_date,
 end_date,
 job_id,
 department_id
  )
VALUES
  (
 :OLD.employee_id,
 :OLD.hire_date,
 SYSDATE,
 :OLD.job_id,
 :OLD.department_id
  );
END actualiza_history;
/
SHOW ERROR
/*Creamos el trigger: update_history, el cual se activa cada vez que se modifica el campo job_id o department_id de la tabla employees, así como también si se elimina un registro de la misma. El Trigger inserta en la tabla job_history un registro con los datos previos a la modificación. Este es un Trigger de fila AFTER.*/
---
UPDATE employees
SET job_id = 'PU_CLERK'
WHERE employee_id = 206;
/*Para probar el Trigger, cambiamos el tipo de empleo al empleado 206.*/
---
SELECT *
FROM job_history
WHERE employee_id = 206;
/*Consultamos la tabla job_history.*/
---OUTPUT:
CREATE OR REPLACE VIEW dept_vw
(
cod_dept,
nomb_dept,
en_uso
)
AS
SELECT
department_id,
department_name,
CASE
WHEN 0 < (
 SELECT NVL(COUNT(*), 0)
 FROM employees e
 WHERE e.department_id = d.department_id
) THEN 'S'
ELSE 'N'
END
FROM departments d;
/*Creamos la vista: dept_vw que nos muestra el nombre y código de cada departamento de la empresa, dicha vista posee el campo virtual: en_uso que nos dice si dicho departamento tiene empleados trabajando en él. Nota: Esta vista permite la modificación de los datos de la tabla base (departments) a través de ella.*/
---
SELECT *
FROM dept_vw;
/*Consultamos nuestra vista.*/
---OUTPUT:
CREATE OR REPLACE TRIGGER trg_dept_secure
INSTEAD OF DELETE ON dept_vw
BEGIN
RAISE_APPLICATION_ERROR(-20023, 'Este operación no es permitida, contacte el Administrador!.');
END;
/
SHOW ERROR
/*Creamos un Trigger que impida eliminar datos de la tabla departments a través de la vista: dept_vw.*/
---
DELETE FROM dept_vw
WHERE cod_dept = 240;
/*Tratamos de eliminar el departamento 240 a través de la vista: dept_vw.*/
---OUTPUT:
_____________________________________________________________________________________

Fuentes: Oracle Database: PL/SQL Fundamentals,
https://docs.oracle.com/cd/B10500_01/server.920/a96524/c18trigs.htm,
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_7004.htm