MySQL LOOP Statement
Master the MySQL LOOP statement — the unconditional infinite loop with manual termination via LEAVE. Covers labels, ITERATE for continue, the canonical cursor-iteration pattern with CONTINUE HANDLER FOR NOT FOUND, the strict variables→cursors→handlers DECLARE order, and when to use LOOP vs WHILE vs REPEAT.
The LOOP statement is MySQL's most basic looping construct — an unconditional, infinite loop. It runs its body forever unless you explicitly break out with a LEAVE statement (or terminate the procedure some other way). LOOP is the building block beneath the higher-level WHILE and REPEAT loops.
Use LOOP when you need full manual control over when the loop ends — for example, when iterating through a cursor and stopping based on a condition that doesn't fit either WHILE's pre-test or REPEAT's post-test pattern.
The optional label at the front gives the loop a name you can refer to with LEAVE or ITERATE. It's required as soon as you have nested loops — otherwise MySQL doesn't know which loop you mean.
Two control statements work with any labeled loop:
| Statement | Effect | Equivalent in other languages |
|---|---|---|
LEAVE label; | Exits the named loop immediately | break |
ITERATE label; | Skips the rest of this iteration; returns to the top of the named loop | continue |
The typical pattern: combine IF with LEAVE to make the loop terminate when some condition is reached:
Count from 1 to 5, printing each value, and stop. The simplest possible LOOP:
DROP PROCEDURE IF EXISTS simple_loop;
DELIMITER $$
CREATE PROCEDURE simple_loop()
BEGIN
DECLARE counter INT DEFAULT 0;
-- Loop until counter reaches 5
simple_loop_label: LOOP
SET counter = counter + 1;
SELECT counter;
IF counter >= 5 THEN
LEAVE simple_loop_label;
END IF;
END LOOP simple_loop_label;
END$$
DELIMITER ;
CALL simple_loop();
SELECT counter; inside the loop returns one result set per iteration. That's why the output above shows five separate single-row tables. In real-world stored procedures you'd usually accumulate values into a table or string variable instead — see the cursor example next.The most common real-world use of LOOP: walk through a query result set with a cursor, processing each row. The loop ends when the cursor's NOT FOUND condition fires.
DROP PROCEDURE IF EXISTS emp_details;
DELIMITER $$
CREATE PROCEDURE emp_details()
BEGIN
-- Step 1: Declare variables FIRST
DECLARE v_emp_id INT;
DECLARE v_salary DECIMAL(10, 2);
DECLARE v_done INT DEFAULT 0;
-- Step 2: Declare the cursor
DECLARE emp_cursor CURSOR FOR
SELECT employee_id, salary FROM employees;
-- Step 3: Declare the handler LAST
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
-- Open and walk
OPEN emp_cursor;
emp_loop: LOOP
FETCH emp_cursor INTO v_emp_id, v_salary;
IF v_done = 1 THEN
LEAVE emp_loop;
END IF;
-- Process the row — here we just print a formatted message
SELECT CONCAT('Employee ID: ', v_emp_id,
', Salary: ', v_salary) AS message;
END LOOP emp_loop;
CLOSE emp_cursor;
END$$
DELIMITER ;
CALL emp_details();
BEGIN ... END block, declarations must appear in this exact order:
- Local variables (
DECLARE x INT;) - Cursors (
DECLARE c CURSOR FOR ...;) - Handlers (
DECLARE ... HANDLER FOR ...;)
CONTINUE HANDLER FOR NOT FOUND pattern is how you detect the end of a cursor's result set. When FETCH runs out of rows, MySQL signals NOT FOUND; the handler catches that signal and sets your "done" flag. Then your loop's IF v_done = 1 THEN LEAVE exits cleanly. This is the standard MySQL idiom — every cursor loop you write will look roughly like this.To skip the rest of one iteration and jump back to the top, use ITERATE. Here we print only even numbers up to 10:
DROP PROCEDURE IF EXISTS even_only;
DELIMITER $$
CREATE PROCEDURE even_only()
BEGIN
DECLARE i INT DEFAULT 0;
even_loop: LOOP
SET i = i + 1;
IF i > 10 THEN
LEAVE even_loop; -- finished
END IF;
IF i % 2 = 1 THEN
ITERATE even_loop; -- skip odd numbers
END IF;
SELECT i AS even_number;
END LOOP even_loop;
END$$
DELIMITER ;
CALL even_only();
| Loop type | Termination | Test runs | Body runs at least once? |
|---|---|---|---|
LOOP | Manual — LEAVE when you decide | Wherever you write the IF | Yes (at least up to the LEAVE) |
WHILE | Automatic — when condition is false | Top of every iteration | No — body may run zero times |
REPEAT | Automatic — when UNTIL condition is true | Bottom of every iteration | Yes — always at least once |
Pick LOOP when termination doesn't naturally fit a top-of-loop or bottom-of-loop test — most cursor-driven processing falls into this bucket.
- Always label loops — even when you only have one. Makes
LEAVEandITERATEunambiguous and the code easier to follow. - Always have a guaranteed exit path. A LOOP with no reachable
LEAVEruns forever and ties up the connection. - Respect the DECLARE order: variables → cursors → handlers. MySQL enforces this strictly.
- Use
CONTINUE HANDLER FOR NOT FOUND, notEXIT HANDLER, for cursor loops. EXIT terminates the entireBEGIN ... ENDblock, which is usually not what you want. - Always
CLOSEcursors after the loop ends. MySQL closes them automatically when the procedure returns, but explicitCLOSEis clearer and frees resources sooner. - Use SQL where you can instead of cursor loops. If your loop is just doing
UPDATEorINSERTper row, a single set-based statement is almost always faster.
LOOPis MySQL's unconditional, infinite loop — you control when it ends withLEAVE.LEAVE label;exits the loop (likebreak);ITERATE label;jumps back to the top (likecontinue).- Always label the loop and put your termination check inside an
IFwithLEAVE. - The most common real-world LOOP pattern is cursor iteration — declare vars, declare cursor, declare
CONTINUE HANDLER FOR NOT FOUND, OPEN, FETCH inside the loop, LEAVE on done, CLOSE. - Choose LOOP over WHILE/REPEAT when the termination logic doesn't fit a single top-of-loop or bottom-of-loop test.