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 DATA o NO 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 Desaconsejado
Puede devolver un SELECT 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:

  1. Procesos masivos que mueven millones de filas y no conviene traer a la aplicación.
  2. Varias aplicaciones distintas compartiendo la misma base, donde la regla debe ser una sola.
  3. Sistemas heredados que ya funcionan así.

Fuera de eso, un buen JOIN desde la aplicación suele ser la respuesta.

Errores comunes

  • Olvidar DELIMITER y ver errores de sintaxis incomprensibles.
  • Parámetros con el mismo nombre que las columnas.
  • DECLARE fuera del inicio del bloque.
  • Omitir DETERMINISTIC en 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.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp