How to Fix MySQL Error 1329: No Data — Zero Rows Fetched, Selected, or Processed

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

  1. Cursor FETCH after the last row — A WHILE or REPEAT loop in a stored procedure calls FETCH cursor_name INTO ... after all rows have been consumed. Without a NOT FOUND handler to set a done flag and exit the loop, MySQL raises 1329 and the procedure aborts.

  2. SELECT ... INTO with no matching rows — A SELECT col INTO var FROM tbl WHERE ... statement finds zero rows. Unlike a regular SELECT, the INTO form raises 1329 instead of simply returning an empty result set.

  3. Missing or mis-scoped handler — A DECLARE CONTINUE HANDLER FOR NOT FOUND exists but is declared after the cursor declaration or outside the BEGIN...END block where the cursor is used, so MySQL does not invoke it.

  4. 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

  1. Add a CONTINUE HANDLER FOR NOT FOUND to 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...END block. When FETCH exhausts the cursor, MySQL sets done = TRUE and continues execution; the IF done THEN LEAVE check exits the loop cleanly.

  2. Guard SELECT ... INTO with 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 proceeding
    

    Or 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;
    
  3. Check handler declaration order. In MySQL, DECLARE statements inside a BEGIN...END block must appear in this order: local variables, conditions, cursors, then handlers. Placing a DECLARE HANDLER before 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;
      ...
    END
    
  4. Use DIAGNOSTICS or 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 02000 is a standard SQL completion condition. MySQL maps it to error 1329 inside stored routines; the same SQLSTATE is used by FETCH exhausting a cursor in other SQL databases (PostgreSQL, DB2).
  • A NOT FOUND handler declared with DECLARE CONTINUE HANDLER FOR NOT FOUND catches all 02000 conditions in its scope, including both cursor exhaustion and SELECT ... INTO with no rows.
  • If you need to distinguish between "cursor exhausted" and "SELECT INTO found nothing" within the same block, use separate nested BEGIN...END blocks, each with its own handler and done flag.
  • When MySQL raises 1329 unhandled, the procedure returns control to the caller with an error. JDBC throws SQLException, the Python mysql-connector raises mysql.connector.errors.DatabaseError, and PDO raises a PDOException — all with SQLSTATE 02000.
  • 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.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.