LOOP

LOOP ejecuta un bloque de sentencias repetidamente hasta que se usa LEAVE para salir del bucle. Es el bucle más básico de MySQL y requiere una condición de salida explícita para evitar un bucle infinito.

Los bucles son la tercera pieza fundamental del control de flujo en procedimientos (junto con IF y CASE). Mientras que las condicionales te permiten elegir qué código ejecutar, los bucles te permiten ejecutar el mismo código múltiples veces. Esto es esencial para procesar conjuntos de datos fila por fila, generar series de valores, acumular resultados o implementar cualquier lógica que requiera repetición.

MySQL ofrece tres tipos de bucles: LOOP, WHILE y REPEAT. LOOP es el más primitivo de los tres: no tiene condición propia, así que debes controlar tú mismo cuándo salir. Esto le da máxima flexibilidad pero también más responsabilidad.

Sintaxis

[etiqueta:] LOOP
    sentencias;
    IF condicion THEN
        LEAVE etiqueta;
    END IF;
END LOOP [etiqueta];

La etiqueta es un identificador que le das al bucle y que necesitas para que LEAVE sepa de qué bucle salir. Técnicamente la etiqueta es opcional, pero sin ella no puedes usar LEAVE (y sin LEAVE, el bucle se ejecutará infinitamente), así que en la práctica siempre la necesitas.

La etiqueta se escribe antes de LOOP seguida de dos puntos, y opcionalmente se repite después de END LOOP. Repetirla al final es buena práctica porque mejora la legibilidad, especialmente cuando tienes bucles anidados.

Ejemplo básico

DELIMITER //
 
CREATE PROCEDURE contar_hasta(IN limite INT)
BEGIN
    DECLARE contador INT DEFAULT 1;
    DECLARE resultado VARCHAR(500) DEFAULT '';
 
    bucle: LOOP
        IF contador > limite THEN
            LEAVE bucle;
        END IF;
 
        SET resultado = CONCAT(resultado, contador, ' ');
        SET contador = contador + 1;
    END LOOP bucle;
 
    SELECT TRIM(resultado) AS numeros;
END //
 
DELIMITER ;
CALL contar_hasta(5);
numeros
1 2 3 4 5

El flujo es sencillo: en cada iteración, primero se comprueba si hemos llegado al límite (y si es así, salimos con LEAVE), y luego se ejecuta la lógica del bucle. Este patrón de "comprobar al inicio y salir si toca" es el más habitual con LOOP y produce un comportamiento similar a un bucle WHILE: si la condición de salida se cumple desde el principio, el cuerpo no se ejecuta ni una vez.

También puedes colocar la comprobación al final del cuerpo, lo que garantizaría al menos una ejecución (como un do...while o un REPEAT). La posición del LEAVE te da control total sobre cuándo se evalúa la condición.

Generar datos con LOOP

Los bucles son ideales para generar series de datos o poblar tablas temporales con valores calculados:

DELIMITER //
 
CREATE PROCEDURE generar_series(IN inicio INT, IN fin INT, IN paso INT)
BEGIN
    DECLARE i INT;
 
    DROP TEMPORARY TABLE IF EXISTS tmp_series;
    CREATE TEMPORARY TABLE tmp_series (valor INT);
 
    SET i = inicio;
 
    insertar: LOOP
        IF i > fin THEN
            LEAVE insertar;
        END IF;
 
        INSERT INTO tmp_series VALUES (i);
        SET i = i + paso;
    END LOOP insertar;
 
    SELECT * FROM tmp_series;
    DROP TEMPORARY TABLE tmp_series;
END //
 
DELIMITER ;
CALL generar_series(10, 50, 10);
valor
10
20
30
40
50

Este tipo de procedimiento es útil para generar datos de prueba, crear rangos de fechas para reportes, o producir secuencias numéricas que luego se usan en JOINs con otras tablas.

LOOP con procesamiento de datos

Uno de los usos más comunes de LOOP es en combinación con cursores para procesar un conjunto de filas una por una. El cursor lee las filas y el LOOP itera sobre ellas hasta que no quedan más:

DELIMITER //
 
CREATE PROCEDURE ajustar_precios(IN porcentaje DECIMAL(5,2))
BEGIN
    DECLARE v_id INT;
    DECLARE v_precio DECIMAL(10,2);
    DECLARE v_nuevo DECIMAL(10,2);
    DECLARE v_done INT DEFAULT FALSE;
    DECLARE v_contador INT DEFAULT 0;
 
    DECLARE cur CURSOR FOR
        SELECT id, precio FROM productos WHERE precio > 1000 AND activo = TRUE;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
 
    OPEN cur;
 
    leer: LOOP
        FETCH cur INTO v_id, v_precio;
 
        IF v_done THEN
            LEAVE leer;
        END IF;
 
        SET v_nuevo = v_precio * (1 + porcentaje / 100);
        -- En producción: UPDATE productos SET precio = v_nuevo WHERE id = v_id;
        SET v_contador = v_contador + 1;
    END LOOP leer;
 
    CLOSE cur;
 
    SELECT v_contador AS productos_afectados;
END //
 
DELIMITER ;
CALL ajustar_precios(5.00);
productos_afectados
4

El patrón LOOP + cursor es tan habitual que lo verás en prácticamente cualquier procedimiento que necesite procesar filas individualmente. La estructura siempre es la misma: OPEN, LOOP (FETCH, comprobar v_done, procesar), CLOSE. Veremos los cursores en profundidad en su artículo dedicado.

Bucle infinito sin LEAVE

Sin LEAVE, el bucle se ejecuta indefinidamente. MySQL tiene un límite de tiempo de ejecución (max_execution_time) que eventualmente lo detendrá, pero mientras tanto consumirá recursos del servidor. Esto no es algo teórico: un bucle infinito accidental es uno de los errores más comunes al trabajar con procedimientos.

-- NUNCA hacer esto:
-- mi_loop: LOOP
--     SELECT 1;  -- Bucle infinito
-- END LOOP mi_loop;

Siempre incluye una condición de salida con LEAVE. Si el bucle depende de datos que podrían no cumplir la condición de salida en casos extremos, considera añadir un contador de seguridad como protección adicional: IF contador > 10000 THEN LEAVE bucle; END IF.

LOOP vs otros bucles

MySQL ofrece tres tipos de bucles, cada uno con un enfoque diferente para la condición de salida:

BucleCondiciónEvaluaciónMínimo de ejecuciones
LOOPExplícita con LEAVEDentro del cuerpo, donde tú decidas0 o más
WHILEEn la cabeceraAntes de cada iteración0
REPEATEn el pieDespués de cada iteración con UNTIL1

LOOP es el más flexible porque tú decides dónde y cuándo evaluar la condición de salida. WHILE y REPEAT son más estructurados y expresan la intención de forma más clara en la mayoría de los casos. En la práctica, muchos desarrolladores prefieren LOOP para patrones con cursores (donde la comprobación va justo después del FETCH) y WHILE o REPEAT para contadores y acumuladores simples.

Limpieza

DROP PROCEDURE IF EXISTS contar_hasta;
DROP PROCEDURE IF EXISTS generar_series;
DROP PROCEDURE IF EXISTS ajustar_precios;

En el siguiente artículo veremos el bucle WHILE, que evalúa la condición antes de cada iteración.

Escrito por Eduardo Lázaro