En este artículo habíamos visto como escribir un stored procedure:

Escribiendo un stored procedure

Ahora veremos como escribir un trigger.

En realidad son muy parecidos, hay pocas diferencias entre ellos pero es muy importante que sepas cuales son, puedes leer más en este artículo:

Similitudes y diferencias entre stored procedures y triggers

Cosas que debes saber sobre los triggers:

  1. Tú nunca pides que se ejecute un trigger, el trigger siempre se ejecuta automáticamente
  2. Los triggers no pueden recibir parámetros de entrada
  3. Los triggers no pueden devolver parámetros de salida
  4. Dentro de los triggers puedes usar las variables booleanas INSERTING, UPDATING, DELETING
  5. Dentro de los triggers existen las pseudo-variables NEW y OLD
  6. Dentro de los triggers no puedes usar el comando SUSPEND
  7. Los triggers se ejecutan siguiendo el orden establecido en POSITION. O sea que primero se ejecuta POSITION 0, luego POSITION 1, luego POSITION 2, etc.

Tipos de trigger

Hay dos tipos de trigger:

  1. BEFORE
  2. AFTER

Los triggers BEFORE se ejecutan antes de que la operación de inserción, actualización, borrado, sea realizada. ¿Para qué se los usa? Para validar que las columnas tengan solamente valores correctos.

Los triggers AFTER se ejecutan después de que la operación de inserción, actualización, borrado sea realizada. ¿Para qué se los usa? Típicamente para cambiar el contenido de otra tabla, o de otra fila dentro de la misma tabla.

Ejemplo 1:

Queremos asegurarnos de que al hacer una venta se registre el precio de venta del producto.

CREATE TRIGGER VENTASDET_BI FOR VENTASDET
   ACTIVE BEFORE INSERT OR UPDATE
   POSITION 0
AS
BEGIN

   IF(NEW.VEN_MONTOX IS NULL OR NEW.VEN_MONTOX <= 0) THEN
      EXCEPTION E_SIN_IMPORTE;

END;

La pseudo-variable NEW.VEN_MONTOX tiene el valor que se quiere guardar en la columna VEN_MONTOX. Si ese valor es NULL o es menor o igual que cero eso es un error, no se debe permitir. Por lo tanto se eleva una excepción llamada E_SIN_IMPORTE. Siempre que se eleva una excepción el procesamiento se detiene en ese punto. ¿La consecuencia? que los usuarios jamás podrán grabar una fila si el valor que quieren poner en la columna VEN_MONTOX es NULL o es menor o igual que cero.

En otras palabras, hemos validado que la columna VEN_MONTOX siempre tenga un valor mayor que cero. Hagan lo que hagan los usuarios jamás conseguirán guardar en esa columna un valor NULL, cero, o negativo.

Ejemplo 2:

Queremos asegurarnos que el porcentaje de ganancia siempre sea del 5% o más, nunca debería ser menor.


CREATE TRIGGER PRODUCTOS_BIU FOR PRODUCTOS
   ACTIVE BEFORE INSERT OR UPDATE
   POSITION 1
AS
BEGIN

   IF (NEW.PRD_PREVTA * 100 / NEW.PRD_PRECTO - 100 < 5) THEN
      EXCEPTION E_PORCENTAJE_MENOR_5
         'El porcentaje de ganancia es menor que el 5%. Identificador = ' || NEW.PRD_IDENTI ;

   WHEN EXCEPTION E_PORCENTAJE_MENOR_5 DO BEGIN
      IN AUTONOMOUS TRANSACTION DO
         INSERT INTO ERRORES
                   (ERR_MODULO, ERR_COMENT)
            VALUES ('PRODUCTOS_BIU' , 'El % de ganancia del producto ' || NEW.PRD_NOMBRE || ' es menor que 5%') ;
      EXCEPTION;
   END

END;

En este caso si el porcentaje de ganancia es menor que el 5% lanzamos la excepción E_PORCENTAJE_MENOR_5. Al lanzar una excepción el procesamiento se detiene en ese punto. Luego capturamos la excepción. ¿Por qué la capturamos? Porque queremos guardar en la tabla ERRORES el error que encontramos, así más tarde podremos revisar esa tabla y ver todos los errores que ocurrieron. Si no capturamos la excepción entonces el producto no será grabado en la tabla PRODUCTOS pero no sabremos el motivo. Recuerda que puede haber muchos motivos por los cuales no se lo pudo grabar (Primary Key duplicada, Unique Key duplicada, restricción Check no cumplida, etc.). Finalmente relanzamos la excepción. ¿Para qué? Para que el Servidor del Firebird no grabe la fila y le diga al Cliente (y el Cliente se lo dirá a nuestra aplicación) que ocurrió una excepción.

Entonces nuestro trigger hace lo siguiente: Valida que el porcentaje de ganancia sea igual o mayor que el 5%. Si lo es, continúa normalmente. Si no lo es, inserta una fila en la tabla ERRORES.

Ejemplo 3:

Queremos asegurarnos de que exista la Sucursal que se quiere usar.


CREATE TRIGGER BIU_PRODUCTOS FOR PRODUCTOS
   ACTIVE BEFORE INSERT OR UPDATE
   POSITION 1
AS
BEGIN

   IF(NEW.PRD_CODSUC NOT IN (SELECT SUC_CODIGO FROM SUCURSALES)) THEN
      EXCEPTION E_SIN_SUCURSAL;

END;

Lo que estamos diciendo aquí es: si la Sucursal que se quiere grabar no existe en la tabla SUCURSALES entonces lanzar una excepción para que no se grabe este producto.

O sea, nos aseguramos que en la columna PRD_CODSUC se tenga el código de una Sucursal que existe.

Ejemplo 4:

Tenemos una tabla donde guardamos los saldos acumulados de cada cuenta contable. Cuando el usuario quiere ver un Balance de Sumas y Saldos, un Balance General o un Estado de Resultados lo que hacemos es mostrarle el contenido de esa tabla de saldos acumulados. Pero si alguno de los asientos contables ha cambiado entonces se debe reprocesar la tabla de saldos acumulados. ¿Cómo sabemos antes de mostrar un informe si debemos reprocesar la tabla de saldos acumulados? Leyendo el valor de la columna CON_RECSAL (recalcular saldos) de la tabla CONFIGURACIÓN. Si la columna CON_RECSAL tiene una «V» (que significa «verdadero») entonces sí debemos recalcular los saldos acumulados.


CREATE TRIGGER AIUD_ASIENTOSDET FOR ASIENTOSDET
   ACTIVE AFTER INSERT OR UPDATE OR DELETE
   POSITION 2
AS
BEGIN

   UPDATE OR INSERT INTO CONFIGURACION
            (CON_IDENTI,CON_RECSAL)
     VALUES (1         , 'V');

END;

Fíjate que este es un trigger que se ejecutará después de que la inserción, actualización o borrado se haya realizado en la tabla ASIENTOSDET. ¿Por qué después? Porque dice que se activará AFTER. Y la palabra inglesa «after» significa «después».

En otras palabras, este trigger solamente se ejecutará si ningún trigger BEFORE lanzó una excepción. Si un trigger BEFORE lanzó una excepción o un trigger AFTER con un valor de POSITION menor que 2 lanzó una excepción entonces este trigger jamás se ejecutará.

Si este trigger llega a ejecutarse eso significa que a la tabla ASIENTOSDET exitosamente se le insertó, actualizó o borró una fila. Y en ese caso actualiza a las columnas CON_IDENTI y CON_RECSAL de la tabla CONFIGURACIÓN. ¿Para qué? Para que sepamos que las columnas de la tabla SALDOS_ACUMULADOS tienen valores incorrectos y volvamos a calcularlos.

Conclusión:

Los triggers son una de las herramientas poderosísimas que nos provee el Firebird y debemos utilizarlos al máximo para tener unas aplicaciones robustas y súper confiables.

Un buen uso de los triggers nos asegurará de que jamás tengamos basura en nuestra Base de Datos (se le llama «basura» a los datos que están pero que no deberían estar).

Artículos relacionados:

Entendiendo a los triggers

Uso de los dos puntos dentro de un stored procedure o trigger

Similitudes y diferencias entre stored procedures y triggers

El índice del blog Firebird21