ERROR 1329 (02000): No data - zero rows fetched, selected, or processed is raised inside a stored procedure or function when a FETCH statement reads past the last row of a cursor, or when no rows are affected by a SELECT INTO statement. The error symbol is ER_SP_FETCH_NO_DATA.
Impact
Error 1329 belongs to SQLSTATE class 02000, which the SQL standard defines as "no data" — a completion condition rather than a fatal error. However, in MySQL stored procedures, an unhandled 02000 condition causes the procedure to terminate immediately, just as a fatal error would. Any open transactions remain open and any partially completed work is left uncommitted until the caller explicitly commits or rolls back.
Developers most commonly encounter this when iterating over a cursor without a NOT FOUND handler, or when a SELECT ... INTO query finds no matching rows. Application-level MySQL clients (JDBC, mysqlclient, mysql2, PDO) surface the error as a raised exception or a non-zero return code from the stored procedure call, which can be confusing because "no data found" feels like a normal outcome rather than an error.
Common Causes
Cursor
FETCHafter the last row — AWHILEorREPEATloop in a stored procedure callsFETCH cursor_name INTO ...after all rows have been consumed. Without aNOT FOUNDhandler to set a done flag and exit the loop, MySQL raises1329and the procedure aborts.SELECT ... INTOwith no matching rows — ASELECT col INTO var FROM tbl WHERE ...statement finds zero rows. Unlike a regularSELECT, theINTOform raises1329instead of simply returning an empty result set.Missing or mis-scoped handler — A
DECLARE CONTINUE HANDLER FOR NOT FOUNDexists but is declared after the cursor declaration or outside theBEGIN...ENDblock where the cursor is used, so MySQL does not invoke it.Calling a procedure that wraps a cursor without checking the return value — Application code calls a stored procedure without inspecting whether it completed normally or was cut short by an unhandled
1329.
Troubleshooting and Resolution Steps
Add a
CONTINUE HANDLER FOR NOT FOUNDto cursor loops. This is the standard pattern for cursor iteration in MySQL:DELIMITER $$ CREATE PROCEDURE process_orders() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE order_id INT; DECLARE cur CURSOR FOR SELECT id FROM orders WHERE status = 'pending'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; read_loop: LOOP FETCH cur INTO order_id; IF done THEN LEAVE read_loop; END IF; -- process each row here END LOOP; CLOSE cur; END$$ DELIMITER ;The handler must be declared after the cursor declaration and within the same
BEGIN...ENDblock. WhenFETCHexhausts the cursor, MySQL setsdone = TRUEand continues execution; theIF done THEN LEAVEcheck exits the loop cleanly.Guard
SELECT ... INTOwith a handler or a count check. Either declare a handler:DECLARE CONTINUE HANDLER FOR NOT FOUND SET var = NULL; SELECT price INTO product_price FROM products WHERE sku = p_sku; -- If no row found, product_price remains NULL; check it before proceedingOr count rows first:
IF (SELECT COUNT(*) FROM products WHERE sku = p_sku) > 0 THEN SELECT price INTO product_price FROM products WHERE sku = p_sku; END IF;Check handler declaration order. In MySQL,
DECLAREstatements inside aBEGIN...ENDblock must appear in this order: local variables, conditions, cursors, then handlers. Placing aDECLARE HANDLERbefore its associated cursor declaration causes a syntax error; placing it outside the relevant block means it will not fire.BEGIN -- 1. Variables DECLARE done INT DEFAULT FALSE; -- 2. Cursors DECLARE cur CURSOR FOR SELECT id FROM t; -- 3. Handlers (must come after cursors they handle) DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; ... ENDUse
DIAGNOSTICSor test the procedure interactively to confirm where the abort is occurring:CALL process_orders(); GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE, @msg = MESSAGE_TEXT; SELECT @sqlstate, @msg;
Additional Information
- SQLSTATE
02000is a standard SQL completion condition. MySQL maps it to error 1329 inside stored routines; the same SQLSTATE is used byFETCHexhausting a cursor in other SQL databases (PostgreSQL, DB2). - A
NOT FOUNDhandler declared withDECLARE CONTINUE HANDLER FOR NOT FOUNDcatches all02000conditions in its scope, including both cursor exhaustion andSELECT ... INTOwith no rows. - If you need to distinguish between "cursor exhausted" and "SELECT INTO found nothing" within the same block, use separate nested
BEGIN...ENDblocks, each with its own handler and done flag. - When MySQL raises
1329unhandled, the procedure returns control to the caller with an error. JDBC throwsSQLException, the Pythonmysql-connectorraisesmysql.connector.errors.DatabaseError, and PDO raises aPDOException— all with SQLSTATE02000. - MariaDB shares this error code and behavior; the same handler patterns apply on MariaDB 10.x.
Frequently Asked Questions
Why does my stored procedure stop running halfway through without any obvious error?
An unhandled CONTINUE HANDLER FOR NOT FOUND (SQLSTATE 02000) causes the procedure to abort silently from the caller's perspective. Add DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; (or an EXIT HANDLER if you want to propagate it) and trace the done flag to find where the early exit is occurring.
Does error 1329 roll back my transaction?
No. Error 1329 is a completion condition, not a transactional error. MySQL aborts procedure execution but does not automatically roll back any open transaction. You must handle the rollback explicitly in an EXIT HANDLER or in application code.
Can I raise 1329 myself to signal "no rows found" to the caller?
Yes. Use SIGNAL SQLSTATE '02000' SET MESSAGE_TEXT = 'No matching record found'; inside a procedure. The caller will receive SQLSTATE 02000 with your custom message. This is useful for procedures that wrap a lookup and want to communicate "not found" semantically.
Why does SELECT ... INTO raise an error when zero rows are found, but a plain SELECT does not?
A regular SELECT returns an empty result set, which is a valid response. SELECT ... INTO var is designed to assign exactly one row to variables; zero rows means the assignment cannot be completed, so MySQL raises the 02000 condition. If more than one row is returned, MySQL raises error 1172 instead.