Procedimientos, Funciones y Triggers
Hasta ahora cada consulta SQL vivía sola: si la misma lógica (un cálculo, una validación) se repite en diez consultas distintas, hay que mantenerla en diez lugares. Las funciones y procedimientos almacenados guardan esa lógica una sola vez, adentro de la propia base de datos. Los triggers van un paso más allá: ejecutan código automáticamente cuando algo pasa —un INSERT, un UPDATE— sin que nadie tenga que acordarse de llamarlos.
👁 — vistas · Ing. Roy Carrasco, Facultad de Ingeniería de Sistemas · UAB
Scrollea para empezar — a la izquierda vas a ver la consulta y su resultado armarse solos, mientras leés a la derecha.
-- todavía no escribimos nada
La misma fórmula, repetida en cada consulta
Cada vez que una consulta necesita el precio final con IVA (13%), hay que escribir precio * 1.13 de nuevo. Si el porcentaje de IVA cambia algún día, hay que salir a buscar cada consulta que lo usa, una por una.
Guardar la fórmula una sola vez
Una función guarda esa lógica adentro de la base de datos, con un nombre propio. RETURNS NUMERIC dice qué tipo de dato devuelve; el cuerpo entre $$ ... $$ está escrito en PL/pgSQL, el lenguaje procedural de PostgreSQL.
Se usa igual que ROUND() o LOWER()
Una vez creada, calcular_precio_final(precio) se llama exactamente como cualquier función de PostgreSQL —adentro de un SELECT, pasándole el nombre de una columna como argumento.
Una función también puede decidir
PL/pgSQL soporta las mismas estructuras que ya conocés de otros lenguajes. clasificar_stock(cantidad) devuelve un texto distinto según en qué rango cae el argumento que recibe.
Una columna calculada, con lógica adentro
Igual que en el paso 03, la función se llama desde el SELECT —solo que esta vez devuelve un texto distinto según el valor de stock de cada fila.
Cuando no hace falta devolver nada
Un procedimiento no tiene RETURNS: no devuelve un valor para usar en un SELECT, sino que ejecuta una acción —acá, un UPDATE. Se define con CREATE PROCEDURE, no CREATE FUNCTION.
Se ejecuta con CALL, no con SELECT
Un procedimiento se invoca con CALL, nunca adentro de un SELECT —justamente porque no devuelve nada que se pueda leer ahí. Acá actualiza el precio del producto 2 directamente en la tabla.
Practicá lo que viste
Antes de seguir a triggers, confirmá que la diferencia entre función y procedimiento quedó clara.
¿Cuál es la diferencia real entre una función y un procedimiento almacenado?
Sobre la sintaxis de CREATE FUNCTION y CREATE PROCEDURE.
1. El cuerpo de una función o procedimiento en PL/pgSQL se escribe entre .
2. Para invocar un procedimiento (no una función), se usa la instrucción .
Clasifica cada elemento según si pertenece a una función o a un procedimiento.
-- todavía no escribimos nada
Nadie se acuerda de auditar a mano
Cada vez que alguien actualiza el stock de un producto, en teoría también debería insertar una fila en una tabla de auditoría, a mano, en la misma transacción. Tarde o temprano, alguien se va a olvidar.
Dónde va a quedar el registro
Una tabla normal, sin nada especial todavía: auditoria_stock va a guardar qué producto cambió, cuánto stock tenía antes, cuánto tiene ahora, y cuándo.
OLD y NEW: la fila antes y después
Una función pensada para un trigger devuelve TRIGGER, no un tipo de dato normal. Adentro tiene acceso a dos filas especiales: OLD (la fila antes del cambio) y NEW (la fila después). Acá inserta ambos valores de stock en la auditoría.
Conectar la función a un evento
AFTER UPDATE ON productos dice cuándo se dispara: después de un UPDATE en esa tabla. FOR EACH ROW dice que corre una vez por cada fila afectada. WHEN (OLD.stock IS DISTINCT FROM NEW.stock) es un filtro extra: solo si el stock realmente cambió —no en cualquier UPDATE.
Nadie llamó a la función. Pasó sola.
Este UPDATE no menciona ni registrar_cambio_stock() ni auditoria_stock en ningún lado. Aun así, al consultar la auditoría después, ya hay una fila nueva —el trigger se disparó solo, apenas cambió el stock.
Un trigger también puede impedir algo
AFTER reacciona a un cambio que ya pasó. BEFORE INSERT corre antes de que la fila se guarde, y con RAISE EXCEPTION puede cancelar la operación por completo si algo no es válido —acá, un precio que no sea mayor a 0.
El INSERT nunca llega a guardarse
Este intento de insertar un producto con precio negativo no inserta nada: el trigger BEFORE INSERT corre primero, evalúa la condición, y frena la operación entera con el error que definiste en RAISE EXCEPTION.
Practicá lo que viste
Emparejá cada pieza de un trigger con lo que hace, y confirmá por qué el WHEN importa.
Une cada término con su descripción.
El trigger de auditoría tiene WHEN (OLD.stock IS DISTINCT FROM NEW.stock). Si alguien actualiza solo el precio de un producto (sin tocar el stock), ¿qué pasa?
Ejercicios de razonamiento
En estas preguntas no se te pide recordar sintaxis: se te da un escenario, y debés razonar cuál sería la respuesta correcta antes de comprobarla.
Necesitás una operación que calcule el promedio de las notas de un estudiante y lo use directamente dentro de un SELECT, junto con otras columnas de la tabla.
¿Función o procedimiento almacenado?
Cada vez que se inserta un nuevo pedido, hay que descontar automáticamente esa cantidad del stock del producto correspondiente —sin que la aplicación tenga que acordarse de hacerlo aparte.
¿Qué herramienta encaja mejor?
Un compañero de equipo agregó un trigger BEFORE INSERT que valida datos, pero se queja de que "no anda": los INSERT inválidos se siguen guardando igual.
Completá la explicación que le darías:
Para que un trigger de validación de verdad impida guardar la fila, la función tiene que cuando el dato es inválido — de lo contrario, la fila se guarda igual aunque el trigger haya notado el problema.
Actividades de repaso
Un repaso integrador de todo el módulo. Cada actividad se corrige al instante; tu progreso se guarda automáticamente en este navegador.
¿En qué lenguaje se escribe normalmente el cuerpo de una función o procedimiento en PostgreSQL?
Un trigger se puede llamar directamente con SELECT registrar_cambio_stock(), igual que una función normal.
¿Qué diferencia hay entre un trigger BEFORE y uno AFTER?
¿Qué le pasa a un INSERT si un trigger BEFORE INSERT ejecuta RAISE EXCEPTION dentro de su función?
¿Para qué sirve específicamente la cláusula WHEN de un CREATE TRIGGER?
Cheat Sheet
| Forma | Para qué sirve |
|---|---|
CREATE FUNCTION nombre(...) RETURNS tipo AS $$ ... $$ LANGUAGE plpgsql; | Guardar lógica que devuelve un valor, usable en un SELECT. |
CREATE PROCEDURE nombre(...) LANGUAGE plpgsql AS $$ ... $$; | Guardar una acción que no devuelve valor, invocada con CALL. |
CALL nombre(argumentos); | Ejecutar un procedimiento almacenado. |
IF ... THEN ... ELSIF ... ELSE ... END IF; | Decidir qué hacer, adentro de PL/pgSQL. |
RETURNS TRIGGER | Marca que una función está pensada para usarse en un trigger, no para llamarse directo. |
OLD / NEW | La fila antes y después del cambio, disponibles dentro de la función del trigger. |
CREATE TRIGGER nombre BEFORE|AFTER evento ON tabla FOR EACH ROW EXECUTE FUNCTION funcion(); | Conectar una función de trigger a un evento de una tabla. |
WHEN (condición) | Filtro extra: el trigger solo se dispara si la condición es verdadera. |
RAISE EXCEPTION 'mensaje'; | Detener la operación completa con un error, típico en un trigger BEFORE de validación. |
¿Sabías que...?
🐘 PL/pgSQL no es el único lenguaje posible
PostgreSQL permite escribir funciones en otros lenguajes instalados como extensión: PL/Python, PL/Perl e incluso PL/Java. PL/pgSQL es el que viene incluido por defecto y el más usado, pero no es la única opción.
🔁 Un trigger puede disparar a otro trigger
Si el INSERT que hace un trigger dentro de su función activa, a su vez, otro trigger sobre esa segunda tabla, PostgreSQL sigue la cadena. Encadenar demasiados triggers entre sí puede volverse difícil de rastrear —y en un caso extremo, hasta generar una recursión infinita que Postgres corta con un límite de profundidad.
👁️ También existen los triggers INSTEAD OF
Una vista normal no siempre acepta INSERT/UPDATE/DELETE directo. Un trigger INSTEAD OF permite definir manualmente qué hacer cuando alguien intenta modificar una vista, traduciendo esa operación a cambios reales sobre las tablas de base.