Funciones vs Procedimientos

Tanto las funciones como los procedimientos almacenados encapsulan lógica SQL reutilizable, pero tienen diferencias fundamentales en cómo se llaman, qué devuelven y dónde se pueden usar.

Diferencias principales

CaracterísticaFunciónProcedimiento
InvocaciónSELECT fn_nombre()CALL sp_nombre()
Valor de retornoObligatorio, un solo valorOpcional, múltiples result sets
Uso en SQLSí, en SELECT, WHERE, etc.No, solo con CALL
ParámetrosSolo ININ, OUT, INOUT
TransaccionesNo puede usar COMMIT/ROLLBACK
Tablas temporalesNo puede crear
Sentencias preparadasNo
RETURNObligatorioNo disponible

Cuándo usar una función

Las funciones son ideales cuando necesitas un valor calculado que se usará dentro de consultas SQL:

-- Función: calcular precio con IVA
DELIMITER //
 
CREATE FUNCTION fn_con_iva(precio DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    RETURN ROUND(precio * 1.21, 2);
END //
 
DELIMITER ;
-- Se puede usar directamente en consultas
SELECT nombre, precio, fn_con_iva(precio) AS precio_iva
FROM productos
WHERE fn_con_iva(precio) > 1000
ORDER BY fn_con_iva(precio) DESC;
nombreprecioprecio_iva
ASUS ROG Zephyrus1899.992298.99
Lenovo ThinkPad X11549.001874.29
MacBook Air M31399.001692.79
iPhone 15 Pro1299.991572.99
Samsung Galaxy S24899.991088.99

Cuándo usar un procedimiento

Los procedimientos son ideales para operaciones complejas que involucran múltiples pasos, transacciones o múltiples conjuntos de resultados:

-- Procedimiento: procesar pedido completo
DELIMITER //
 
CREATE PROCEDURE sp_resumen_pedido(IN ped_id INT)
BEGIN
    -- Datos del pedido
    SELECT p.id, p.fecha, p.total, p.estado,
           c.nombre AS cliente
    FROM pedidos p
    JOIN clientes c ON p.cliente_id = c.id
    WHERE p.id = ped_id;
 
    -- Detalle del pedido
    SELECT pr.nombre, dp.cantidad, dp.precio_unitario,
           dp.cantidad * dp.precio_unitario AS subtotal
    FROM detalle_pedidos dp
    JOIN productos pr ON dp.producto_id = pr.id
    WHERE dp.pedido_id = ped_id;
END //
 
DELIMITER ;
CALL sp_resumen_pedido(1);

Ejemplo comparativo

El mismo problema resuelto de dos formas:

Como función

DELIMITER //
 
CREATE FUNCTION fn_total_vendido(prod_id INT)
RETURNS INT
READS SQL DATA
BEGIN
    DECLARE v_total INT;
 
    SELECT COALESCE(SUM(cantidad), 0) INTO v_total
    FROM detalle_pedidos WHERE producto_id = prod_id;
 
    RETURN v_total;
END //
 
DELIMITER ;
-- Ventaja: se integra en consultas
SELECT nombre, precio, stock,
       fn_total_vendido(id) AS vendidos
FROM productos
WHERE fn_total_vendido(id) > 0
ORDER BY fn_total_vendido(id) DESC
LIMIT 5;

Como procedimiento

DELIMITER //
 
CREATE PROCEDURE sp_total_vendido(IN prod_id INT, OUT total INT)
BEGIN
    SELECT COALESCE(SUM(cantidad), 0) INTO total
    FROM detalle_pedidos WHERE producto_id = prod_id;
END //
 
DELIMITER ;
-- Requiere variable de sesión, no se integra en SELECT
CALL sp_total_vendido(1, @total);
SELECT @total;

Restricciones de las funciones

Las funciones tienen más restricciones que los procedimientos:

  • No pueden devolver múltiples result sets
  • No pueden usar CALL para llamar procedimientos que devuelvan result sets
  • No pueden usar sentencias DDL dinámicas
  • No pueden iniciar o finalizar transacciones
  • No pueden usar PREPARE / EXECUTE
  • No pueden crear tablas temporales

Rendimiento

AspectoFunciónProcedimiento
Llamada por fila en SELECTPuede ser lento en tablas grandesNo aplica
Cacheo de resultadosPosible si es DETERMINISTICNo
Overhead de llamadaMínimoMínimo

Si una función se llama una vez por fila en un SELECT con millones de filas, puede afectar al rendimiento. En esos casos, considera una subconsulta o JOIN en su lugar.

Resumen de decisión

Necesitas...Usa
Un valor calculado en SELECT/WHEREFunción
Múltiples result setsProcedimiento
Parámetros OUTProcedimiento
TransaccionesProcedimiento
Lógica en triggersFunción
Operaciones INSERT/UPDATE/DELETEProcedimiento
Validación reutilizableFunción

Limpieza

DROP FUNCTION IF EXISTS fn_con_iva;
DROP FUNCTION IF EXISTS fn_total_vendido;
DROP PROCEDURE IF EXISTS sp_resumen_pedido;
DROP PROCEDURE IF EXISTS sp_total_vendido;

Con esto completamos la sección de funciones almacenadas. En la siguiente sección exploraremos los triggers en MySQL.

Escrito por Eduardo Lázaro