5 – Programación con funciones y procedimientos¶
Diferencias entre función y procedimiento¶
Elemento | Función | Procedimiento |
---|---|---|
Devuelve un valor | ✅ Sí, con RETURN | ❌ No |
Llamada | En una SELECT o expresión | Con el comando CALL |
Se puede usar en SQL | ✅ Sí | ❌ No directamente |
Tiene OUT | ❌ Generalmente no | ✅ Sí |
Ejemplo de llamada¶
Llamada a función:
SELECT mi_funcion(5);
Llamada a procedimiento:
CALL mi_procedimiento('dato');
Esqueleto de una función o procedimiento¶
Ambos comienzan con CREATE OR REPLACE y pueden tener:
- Sección DECLARE: para definir variables internas.
- Bloque BEGIN ... END: cuerpo principal.
- Sección EXCEPTION: captura de errores.
Función¶
CREATE OR REPLACE FUNCTION suma(a INT, b INT)
RETURNS INT AS $$
DECLARE
resultado INT;
BEGIN
resultado := a + b;
RETURN resultado;
END;
$$ LANGUAGE plpgsql;
Procedimiento¶
CREATE OR REPLACE PROCEDURE imprimir_suma(a INT, b INT)
AS $$
DECLARE
resultado INT;
BEGIN
resultado := a + b;
RAISE NOTICE 'La suma es: %', resultado;
END;
$$ LANGUAGE plpgsql;
Parámetros IN, OUT e INOUT¶
Tipo | Descripción |
---|---|
IN | Valor de entrada (por defecto) |
OUT | Parámetro que devuelve un valor |
INOUT | Sirve como entrada y salida |
CREATE OR REPLACE PROCEDURE dame_doble(IN entrada INT, OUT salida INT)
AS $$
BEGIN
salida := entrada * 2;
END;
$$ LANGUAGE plpgsql;
Llamada desde consola: CALL dame_doble(5, x); -- x = 10
Nota importante sobre excepciones¶
Cada procedimiento o función en PostgreSQL se ejecuta automáticamente dentro de una transacción. Por eso NO es necesario iniciar manualmente una transacción con BEGIN. Sin embargo, si se lanza una excepción (RAISE EXCEPTION) y no se captura con EXCEPTION, se produce un rollback implícito de toda la función o procedimiento.
Condicionales¶
IF / ELSIF / ELSE¶
IF total > 100 THEN
RAISE NOTICE 'Total alto';
ELSIF total = 100 THEN
RAISE NOTICE 'Total exacto';
ELSE
RAISE NOTICE 'Total bajo';
END IF;
CASE¶
CASE tipo_producto
WHEN 'A' THEN
precio := 10;
WHEN 'B' THEN
precio := 15;
ELSE
precio := 5;
END CASE;
Bucles¶
LOOP con EXIT¶
LOOP
total := total + 1;
EXIT WHEN total >= 5;
END LOOP;
WHILE¶
WHILE stock > 0 LOOP
stock := stock - 1;
END LOOP;
FOR IN SELECT¶
FOR fila IN SELECT * FROM productos LOOP
RAISE NOTICE 'Producto: %', fila.nombre;
END LOOP;
Variable mágica FOUND¶
FOUND indica si la última operación de tipo SELECT INTO, FETCH, UPDATE, DELETE, etc. encontró al menos una fila.
LOOP
FETCH mi_cursor INTO fila;
EXIT WHEN NOT FOUND;
-- procesar fila
END LOOP;
También útil para salir de bucles si ya no hay más resultados.
Cursores en PostgreSQL¶
Un cursor permite recorrer los resultados de una consulta fila por fila.
¿Cómo se declara un cursor?¶
DECLARE
cur_empleados CURSOR FOR
SELECT * FROM empleados ORDER BY id;
Ejemplo completo: recorrido ascendente¶
DO $$
DECLARE
cur_empleados CURSOR FOR
SELECT * FROM empleados ORDER BY id;
fila RECORD;
BEGIN
OPEN cur_empleados;
FETCH NEXT FROM cur_empleados INTO fila;
WHILE FOUND LOOP
RAISE NOTICE 'Empleado: % (% años)', fila.nombre, fila.edad;
FETCH NEXT FROM cur_empleados INTO fila;
END LOOP;
CLOSE cur_empleados;
END;
$$;
Ejemplo completo: recorrido descendente¶
DO $$
DECLARE
cur_empleados CURSOR FOR
SELECT * FROM empleados ORDER BY id;
fila RECORD;
BEGIN
OPEN cur_empleados;
FETCH LAST FROM cur_empleados INTO fila;
WHILE FOUND LOOP
RAISE NOTICE 'Empleado: % (% años)', fila.nombre, fila.edad;
FETCH PRIOR FROM cur_empleados INTO fila;
END LOOP;
CLOSE cur_empleados;
END;
$$;
Resumen: direcciones de FETCH¶
Dirección | Descripción |
---|---|
NEXT | Fila siguiente (por defecto) |
PRIOR | Fila anterior |
FIRST | Primera fila |
LAST | Última fila |
- Siempre cerrar el cursor con CLOSE al final.
- Usar ORDER BY si el orden es importante.
- Utilizar FOUND para saber si FETCH devolvió fila.
Variables tipo RECORD¶
Las variables RECORD permiten guardar varios campos sin declarar uno por uno.
DECLARE
empleado RECORD;
SELECT
* INTO empleado
FROM
empleados
WHERE id = 1;
RAISE NOTICE 'Nombre: %, Salario: %', empleado.nombre, empleado.salario;
Uso con FETCH:
FETCH cur_empleados INTO empleado;
RAISE NOTICE 'ID: %, Nombre: %', empleado.id, empleado.nombre;
RAISE NOTICE y RAISE EXCEPTION¶
RAISE NOTICE 'ID: %, Nombre: %', id, nombre;
IF salario < 0 THEN
RAISE EXCEPTION 'Salario negativo: %', salario;
END IF;
Los placeholders (%) se sustituyen por los valores en orden.
Bloques BEGIN … EXCEPTION¶
BEGIN
SELECT * INTO empleado FROM empleados WHERE id = 999;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE NOTICE 'No se encontró el empleado';
WHEN TOO_MANY_ROWS THEN
RAISE NOTICE 'Demasiados resultados';
WHEN OTHERS THEN
RAISE NOTICE 'Error inesperado';
END;
Captura localizada con bloques anidados:
BEGIN
BEGIN
SELECT * INTO emp FROM empleados WHERE activo = TRUE;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Error solo aquí';
END;
END;
SELECT INTO¶
Ejemplo básico (una sola columna)¶
DECLARE
salario NUMERIC;
BEGIN
SELECT sueldo INTO salario
FROM empleados
WHERE id = 5;
END;
Si no se encuentra ninguna fila, la variable toma el valor NULL.
Si se encuentran varias, se toma solo la primera.
SELECT INTO con múltiples columnas¶
DECLARE
nombre TEXT;
edad INT;
BEGIN
SELECT nombre, edad INTO nombre, edad
FROM empleados
WHERE id = 1;
END;
DECLARE
emp RECORD;
BEGIN
SELECT id, nombre, edad INTO emp
FROM empleados
WHERE id = 1;
RAISE NOTICE 'Nombre: %, Edad: %', emp.nombre, emp.edad;
END;
SELECT INTO STRICT¶
Situación | Excepción lanzada |
---|---|
Ninguna fila encontrada | NO_DATA_FOUND |
Más de una fila encontrada | TOO_MANY_ROWS |
DO $$
DECLARE
emp RECORD;
BEGIN
BEGIN
SELECT id, nombre, salario INTO STRICT emp
FROM empleados
WHERE activo = TRUE;
RAISE NOTICE 'Empleado activo: % con salario %', emp.nombre, emp.salario;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE NOTICE 'No hay empleados activos';
WHEN TOO_MANY_ROWS THEN
RAISE NOTICE 'Hay más de un empleado activo';
END;
END;
$$;
Rollback implícito¶
Cuando se produce una excepción no capturada, PostgreSQL hace un rollback automático de todo el procedimiento o función.
IF saldo < 0 THEN
RAISE EXCEPTION 'Saldo negativo: %', saldo;
END IF;
Si no se captura, revierte todos los cambios realizados dentro de la función.