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ística | Función | Procedimiento |
|---|---|---|
| Invocación | SELECT fn_nombre() | CALL sp_nombre() |
| Valor de retorno | Obligatorio, un solo valor | Opcional, múltiples result sets |
| Uso en SQL | Sí, en SELECT, WHERE, etc. | No, solo con CALL |
| Parámetros | Solo IN | IN, OUT, INOUT |
| Transacciones | No puede usar COMMIT/ROLLBACK | Sí |
| Tablas temporales | No puede crear | Sí |
| Sentencias preparadas | No | Sí |
| RETURN | Obligatorio | No 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;| nombre | precio | precio_iva |
|---|---|---|
| ASUS ROG Zephyrus | 1899.99 | 2298.99 |
| Lenovo ThinkPad X1 | 1549.00 | 1874.29 |
| MacBook Air M3 | 1399.00 | 1692.79 |
| iPhone 15 Pro | 1299.99 | 1572.99 |
| Samsung Galaxy S24 | 899.99 | 1088.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
| Aspecto | Función | Procedimiento |
|---|---|---|
| Llamada por fila en SELECT | Puede ser lento en tablas grandes | No aplica |
| Cacheo de resultados | Posible si es DETERMINISTIC | No |
| Overhead de llamada | Mínimo | Mí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/WHERE | Función |
| Múltiples result sets | Procedimiento |
| Parámetros OUT | Procedimiento |
| Transacciones | Procedimiento |
| Lógica en triggers | Función |
| Operaciones INSERT/UPDATE/DELETE | Procedimiento |
| Validación reutilizable | Funció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
