Curso de MySQL
Procedimientos almacenados y funciones en MySQL
Por Víctor Peña · Publicado el
Hola, ¿cómo están? Continuando con los temas complementarios del curso de MySQL, hoy veremos los procedimientos almacenados: bloques de código SQL guardados en la propia base de datos.
¡Empecemos!
Qué es un procedimiento almacenado
Es un conjunto de sentencias SQL guardado en la base con un nombre, que puedes ejecutar cuando quieras.
Es el equivalente a una función pero viviendo dentro del gestor de base de datos en lugar de en tu aplicación.
DELIMITER //
CREATE PROCEDURE listar_citas_por_estado(IN p_estado VARCHAR(20))
BEGIN
SELECT
c.fecha,
c.hora,
CONCAT(p.nombre, ' ', p.apellido) AS paciente,
c.costo
FROM citas c
JOIN pacientes p ON c.paciente_id = p.id
WHERE c.estado = p_estado
ORDER BY c.fecha;
END //
DELIMITER ;
Y se ejecuta así:
CALL listar_citas_por_estado('programada');
Qué es ese DELIMITER
Es lo primero que desconcierta, así que vale la pena explicarlo.
MySQL usa el punto y coma para saber dónde termina una sentencia. Pero un procedimiento contiene puntos y coma dentro. Sin avisar, MySQL cortaría en el primero y todo fallaría.
DELIMITER // le dice: «a partir de ahora, el fin de sentencia es //». Así los ; internos se respetan. Al terminar, DELIMITER ; restaura lo normal.
Puedes usar cualquier símbolo: //, $$ o ;;. Lo importante es no olvidar restaurarlo.
Tipos de parámetros
DELIMITER //
CREATE PROCEDURE calcular_ingresos(
IN p_desde DATE,
IN p_hasta DATE,
OUT p_total DECIMAL(12,2),
OUT p_cantidad INT
)
BEGIN
SELECT
IFNULL(SUM(costo), 0),
COUNT(*)
INTO p_total, p_cantidad
FROM citas
WHERE estado = 'atendida'
AND fecha BETWEEN p_desde AND p_hasta;
END //
DELIMITER ;
Uso:
CALL calcular_ingresos('2026-04-01', '2026-04-30', @total, @cantidad);
SELECT @total AS ingresos, @cantidad AS citas;
Los tres tipos:
| Tipo | Significa |
|---|---|
IN |
Entra al procedimiento. Es el valor por defecto |
OUT |
Sale del procedimiento |
INOUT |
Entra y sale modificado |
Fíjate en el prefijo p_ en los nombres de parámetro. Es una convención muy recomendable: si un parámetro se llama igual que una columna, MySQL no sabe a cuál te refieres y obtienes resultados absurdos.
Variables y control de flujo
Dentro de un procedimiento puedes declarar variables y usar condicionales y bucles:
DELIMITER //
CREATE PROCEDURE clasificar_paciente(IN p_paciente_id INT, OUT p_categoria VARCHAR(20))
BEGIN
DECLARE v_total_citas INT DEFAULT 0;
DECLARE v_gasto DECIMAL(12,2) DEFAULT 0;
SELECT COUNT(*), IFNULL(SUM(costo), 0)
INTO v_total_citas, v_gasto
FROM citas
WHERE paciente_id = p_paciente_id AND estado = 'atendida';
IF v_total_citas = 0 THEN
SET p_categoria = 'Sin historial';
ELSEIF v_gasto >= 1000 THEN
SET p_categoria = 'Frecuente';
ELSEIF v_total_citas >= 2 THEN
SET p_categoria = 'Recurrente';
ELSE
SET p_categoria = 'Nuevo';
END IF;
END //
DELIMITER ;
Las declaraciones DECLARE deben ir al inicio del bloque, antes de cualquier otra sentencia.
También existen bucles:
WHILE condicion DO
-- ...
END WHILE;
REPEAT
-- ...
UNTIL condicion END REPEAT;
Procedimientos con transacciones
Aquí es donde de verdad brillan: encapsulan una operación completa con su manejo de errores.
DELIMITER //
CREATE PROCEDURE reprogramar_cita(
IN p_cita_id INT,
IN p_nueva_fecha DATE,
IN p_nueva_hora TIME
)
BEGIN
DECLARE v_paciente INT;
DECLARE v_doctor INT;
DECLARE v_costo DECIMAL(10,2);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'No se pudo reprogramar la cita';
END;
START TRANSACTION;
SELECT paciente_id, doctor_id, costo
INTO v_paciente, v_doctor, v_costo
FROM citas WHERE id = p_cita_id;
UPDATE citas SET estado = 'cancelada' WHERE id = p_cita_id;
INSERT INTO citas (paciente_id, doctor_id, fecha, hora, costo, estado)
VALUES (v_paciente, v_doctor, p_nueva_fecha, p_nueva_hora, v_costo, 'programada');
COMMIT;
END //
DELIMITER ;
El DECLARE EXIT HANDLER FOR SQLEXCEPTION es el equivalente a un try/catch: si cualquier sentencia falla, hace ROLLBACK y lanza un error con mensaje propio.
Funciones almacenadas
Una función es parecida, pero devuelve un valor y se usa dentro de una consulta:
DELIMITER //
CREATE FUNCTION calcular_edad(p_nacimiento DATE)
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
RETURN TIMESTAMPDIFF(YEAR, p_nacimiento, CURDATE());
END //
DELIMITER ;
Y se usa como cualquier función de MySQL:
SELECT nombre, apellido, calcular_edad(fecha_nacimiento) AS edad
FROM pacientes;
Las características obligatorias:
DETERMINISTIC— con los mismos parámetros devuelve siempre lo mismo. Si no,NOT DETERMINISTIC.READS SQL DATAoNO SQL— indica si consulta la base.
MySQL las exige por seguridad en la replicación, y si las omites obtendrás un error.
Procedimiento o función
| Procedimiento | Función | |
|---|---|---|
| Se ejecuta con | CALL |
Dentro de una consulta |
| Devuelve | Ninguno o varios OUT |
Un solo valor |
| Puede modificar datos | Sí | Desaconsejado |
Puede devolver un SELECT |
Sí | No |
Regla simple: si calcula un valor, función. Si ejecuta una operación, procedimiento.
Gestionar procedimientos
-- Listar
SHOW PROCEDURE STATUS WHERE Db = 'clinica';
SHOW FUNCTION STATUS WHERE Db = 'clinica';
-- Ver el código
SHOW CREATE PROCEDURE listar_citas_por_estado;
-- Eliminar
DROP PROCEDURE IF EXISTS listar_citas_por_estado;
DROP FUNCTION IF EXISTS calcular_edad;
Para modificar uno hay que borrarlo y volver a crearlo: no existe CREATE OR REPLACE PROCEDURE.
Ventajas reales
Menos viajes al servidor. Un procedimiento que hace cinco operaciones se llama una vez, en lugar de cinco comunicaciones desde la aplicación.
Seguridad. Puedes dar permiso de ejecutar el procedimiento sin dar acceso a las tablas:
GRANT EXECUTE ON PROCEDURE clinica.reprogramar_cita TO 'app'@'localhost';
Lógica centralizada. Si varias aplicaciones —web, móvil, un proceso por lotes— comparten la misma base, la regla vive en un solo sitio.
Las desventajas, que son serias
Y aquí quiero ser honesto, porque los procedimientos generan opiniones encontradas y con razón.
No se versionan bien. Viven dentro de la base de datos, no en tu repositorio de Git. Saber quién cambió qué y cuándo es complicado.
Son difíciles de probar. No hay forma cómoda de escribir pruebas automatizadas sobre ellos.
El lenguaje es limitado. Comparado con PHP o cualquier lenguaje moderno, el SQL procedural es pobre y torpe para lógica compleja.
Atan tu sistema a MySQL. Migrar a PostgreSQL implica reescribirlos todos.
Dispersan la lógica. Si parte de las reglas está en la aplicación y parte en la base, entender el sistema completo exige mirar en dos lugares. Es una fuente constante de sorpresas para quien llega nuevo al proyecto.
Mi recomendación
En proyectos modernos, la lógica de negocio va en la aplicación, donde puedes versionarla, probarla y leerla con comodidad.
Los procedimientos valen la pena en tres casos concretos:
- Procesos masivos que mueven millones de filas y no conviene traer a la aplicación.
- Varias aplicaciones distintas compartiendo la misma base, donde la regla debe ser una sola.
- Sistemas heredados que ya funcionan así.
Fuera de eso, un buen JOIN desde la aplicación suele ser la respuesta.
Errores comunes
- Olvidar
DELIMITERy ver errores de sintaxis incomprensibles. - Parámetros con el mismo nombre que las columnas.
DECLAREfuera del inicio del bloque.- Omitir
DETERMINISTICen una función. - Meter toda la lógica de negocio en procedimientos y perder trazabilidad.
Para cerrar
Los procedimientos almacenados son una herramienta potente y con un costo real de mantenibilidad. Conviene saber usarlos —te los vas a encontrar en sistemas existentes— y ser selectivo al crearlos.
En la siguiente lección veremos los triggers, que son parientes cercanos y aún más delicados.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp