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_incluidos | total_acumulado | presupuesto |
|---|---|---|
| 7 | 447.89 | 500.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_ejecuto | valor_i |
|---|---|
| No | 10 |
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
