WHILE

El bucle WHILE ejecuta un bloque de sentencias repetidamente mientras una condición sea verdadera. La condición se evalúa antes de cada iteración, por lo que si es falsa desde el inicio, el cuerpo nunca se ejecuta.

Si conoces el bucle while de cualquier lenguaje de programación, el WHILE de MySQL funciona exactamente igual. Es el bucle más intuitivo de los tres que ofrece MySQL: expresas la condición de continuación directamente en la cabecera, lo que hace el código más legible y menos propenso a errores que un LOOP con LEAVE manual.

WHILE es ideal cuando la condición de continuación se puede expresar de forma clara antes de ejecutar el cuerpo del bucle: "mientras el contador sea menor que 10", "mientras no se haya terminado de leer el cursor", "mientras la suma acumulada no supere el presupuesto".

Sintaxis

[etiqueta:] WHILE condicion DO
    sentencias;
END WHILE [etiqueta];

La etiqueta es opcional en WHILE (a diferencia de LOOP, donde es prácticamente obligatoria). Solo la necesitas si quieres usar LEAVE o ITERATE para salir del bucle o saltar a la siguiente iteración de forma explícita. Para bucles WHILE simples, puedes prescindir de ella.

Ejemplo básico

DELIMITER //
 
CREATE PROCEDURE cuenta_regresiva(IN inicio INT)
BEGIN
    DECLARE resultado VARCHAR(500) DEFAULT '';
 
    WHILE inicio > 0 DO
        SET resultado = CONCAT(resultado, inicio, ' ');
        SET inicio = inicio - 1;
    END WHILE;
 
    SELECT TRIM(resultado) AS cuenta;
END //
 
DELIMITER ;
CALL cuenta_regresiva(5);
cuenta
5 4 3 2 1

La condición inicio > 0 se evalúa antes de cada iteración. Cuando inicio llega a 0, la condición es falsa y el bucle termina. Observa que el parámetro IN se usa directamente como contador, lo cual es válido porque las modificaciones a un parámetro IN solo afectan dentro del procedimiento (es una copia local).

Calcular factorial

WHILE es especialmente natural para cálculos matemáticos iterativos donde la condición de parada es clara:

DELIMITER //
 
CREATE PROCEDURE factorial(IN n INT, OUT resultado BIGINT)
BEGIN
    SET resultado = 1;
 
    WHILE n > 1 DO
        SET resultado = resultado * n;
        SET n = n - 1;
    END WHILE;
END //
 
DELIMITER ;
CALL factorial(5, @fact);
SELECT @fact AS factorial_5;
factorial_5
120
CALL factorial(10, @fact);
SELECT @fact AS factorial_10;
factorial_10
3628800

Se usa BIGINT para el resultado porque los factoriales crecen muy rápido. Con INT (máximo ~2 mil millones), ya no podrías calcular factorial(13). Con BIGINT llegas hasta factorial(20) antes de desbordar.

WHILE con datos

WHILE funciona bien cuando necesitas combinar la iteración con lógica que interactúa con tablas. En este ejemplo, se genera un ranking de productos por valor de inventario:

DELIMITER //
 
CREATE PROCEDURE top_productos_por_valor(IN limite INT)
BEGIN
    DECLARE v_contador INT DEFAULT 0;
 
    DROP TEMPORARY TABLE IF EXISTS tmp_ranking;
    CREATE TEMPORARY TABLE tmp_ranking (
        posicion INT,
        nombre VARCHAR(100),
        valor_stock DECIMAL(12,2)
    );
 
    INSERT INTO tmp_ranking
    SELECT NULL, nombre, precio * stock AS valor
    FROM productos
    WHERE activo = TRUE
    ORDER BY valor DESC;
 
    -- Asignar posiciones manualmente
    SET @pos = 0;
    UPDATE tmp_ranking SET posicion = (@pos := @pos + 1);
 
    SELECT posicion, nombre, valor_stock
    FROM tmp_ranking
    WHERE posicion <= limite;
 
    DROP TEMPORARY TABLE tmp_ranking;
END //
 
DELIMITER ;
CALL top_productos_por_valor(5);

WHILE con acumulador

Un patrón frecuente es usar WHILE para acumular valores hasta alcanzar un límite. Este ejemplo recorre productos ordenados por precio y los suma hasta que el total supera un presupuesto dado. Es un problema que no se puede resolver fácilmente con SQL puro, pero que con un cursor y WHILE se expresa de forma natural:

DELIMITER //
 
CREATE PROCEDURE sumar_precios_hasta(IN limite DECIMAL(10,2))
BEGIN
    DECLARE v_nombre VARCHAR(100);
    DECLARE v_precio DECIMAL(10,2);
    DECLARE v_suma DECIMAL(10,2) DEFAULT 0;
    DECLARE v_contador INT DEFAULT 0;
    DECLARE v_done INT DEFAULT FALSE;
 
    DECLARE cur CURSOR FOR
        SELECT nombre, precio FROM productos
        WHERE activo = TRUE ORDER BY precio;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
 
    OPEN cur;
 
    FETCH cur INTO v_nombre, v_precio;
 
    WHILE NOT v_done AND (v_suma + v_precio) <= limite DO
        SET v_suma = v_suma + v_precio;
        SET v_contador = v_contador + 1;
        FETCH cur INTO v_nombre, v_precio;
    END WHILE;
 
    CLOSE cur;
 
    SELECT v_contador AS productos_incluidos,
           v_suma AS total_acumulado,
           limite AS presupuesto;
END //
 
DELIMITER ;
CALL sumar_precios_hasta(500.00);
productos_incluidostotal_acumuladopresupuesto
7447.89500.00

Observa que la condición del WHILE combina dos comprobaciones: NOT v_done (que aún hay filas por leer) y (v_suma + v_precio) <= limite (que no superamos el presupuesto). El primer FETCH se hace antes del bucle para tener un valor inicial con el que evaluar la condición. Dentro del bucle, el FETCH se hace al final para cargar el siguiente valor antes de la próxima evaluación.

Condición falsa desde el inicio

Una característica fundamental de WHILE es que puede no ejecutarse nunca. Si la condición es falsa desde el primer momento, el cuerpo se salta completamente:

DELIMITER //
 
CREATE PROCEDURE ejemplo_while_vacio()
BEGIN
    DECLARE i INT DEFAULT 10;
    DECLARE ejecuto VARCHAR(5) DEFAULT 'No';
 
    -- La condición es falsa desde el inicio
    WHILE i < 5 DO
        SET ejecuto = 'Sí';
        SET i = i + 1;
    END WHILE;
 
    SELECT ejecuto AS se_ejecuto, i AS valor_i;
END //
 
DELIMITER ;
CALL ejemplo_while_vacio();
se_ejecutovalor_i
No10

El cuerpo del WHILE no se ejecuta ni una vez porque 10 < 5 es falso. Esta es la diferencia principal con REPEAT (que veremos a continuación): REPEAT siempre ejecuta el cuerpo al menos una vez porque evalúa la condición después.

Esto significa que WHILE es la opción segura cuando no estás seguro de que haya datos que procesar. Si el cursor está vacío, si el parámetro es 0, o si la condición inicial no se cumple, WHILE simplemente no hace nada, lo cual suele ser el comportamiento correcto.

Limpieza

DROP PROCEDURE IF EXISTS cuenta_regresiva;
DROP PROCEDURE IF EXISTS factorial;
DROP PROCEDURE IF EXISTS top_productos_por_valor;
DROP PROCEDURE IF EXISTS sumar_precios_hasta;
DROP PROCEDURE IF EXISTS ejemplo_while_vacio;

En el siguiente artículo veremos el bucle REPEAT, que garantiza al menos una ejecución del cuerpo.

Escrito por Eduardo Lázaro