You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL存储过程双游标报错ER_SP_CURSOR_NOT_OPEN求助

问题分析与解决方案

核心错误原因

1. ER_SP_CURSOR_NOT_OPEN 错误

你的EXIT HANDLER会在触发NOT FOUND时尝试关闭两个游标,但第一个游标cur_lotes_envasado在完成循环后已被手动关闭。当第二个游标循环触发NOT FOUND时,HANDLER尝试关闭已关闭的游标,导致报错。

2. DECLARE位置错误

MySQL语法强制要求所有DECLARE语句(变量、游标、HANDLER)必须放在BEGIN块的最顶部,不能在任何执行语句(如SET、SELECT INTO)之后声明,否则会触发DECLARE is not valid at this position错误。

修复后的完整存储过程

CREATE DEFINER=`soporte`@`localhost` PROCEDURE `insertarItemEnvasado`(
    IN ordenID_in           INT,                -- ID de la Orden
    IN lote_orden_in        VARCHAR(45),        -- Lote de la Orden de Envasado
    IN bod_origenID_in      INT,                -- ID Bodega de Origen
    IN bod_destinoID_in     INT,                -- ID Bodega de Destino
    IN productoID_in        INT,                -- ID Producto a Envasar
    IN cantidad_kg_envasar  DECIMAL(10,2),      -- Cantidad KG a Envasar
    IN envaseID_in          INT,                -- ID Envase
    IN capacidad_envase     DECIMAL(10,2),      -- Capacidad de Envase
    IN cantidad_envase      DECIMAL(10,2),      -- Cantidad de Envases
    IN codigo_final_in      VARCHAR(45),        -- Codigo del Producto Envasado Final
    IN userID_in            INT                 -- ID Usuario que hizo la Transaccion
)
BEGIN
    -- 所有DECLARE语句必须放在最顶部
    DECLARE total_kg_envasar    DECIMAL(10,2);
    DECLARE total_envases       INT;
    DECLARE kg_restante         DECIMAL(10,2);
    DECLARE envases_restantes   INT;
    DECLARE precio_unitario     DECIMAL(10,2);
    DECLARE precio_por_kg       DECIMAL(10,2);
    
    DECLARE lote_id INT;
    DECLARE lote_cantidad DECIMAL(10,2);
    DECLARE lote_precio DECIMAL(10,2);
    
    DECLARE envase_lote_id INT;
    DECLARE envase_cantidad INT;
    DECLARE precio_envase INT;
    
    -- 游标声明
    DECLARE cur_lotes_envasado CURSOR FOR
    SELECT id, precio, cantidadenvasar
    FROM controlstock.lotesenvasado
    WHERE ordenID = ordenID_in
    ORDER BY fecInsert;
    
    DECLARE cur_envases CURSOR FOR
    SELECT id, stock
    FROM controlstock.existencias
    WHERE productoID = envaseID_in AND bodegaID = bod_origenID_in AND status IN (1,2)
    ORDER BY fecInsert ASC;

    -- 为游标设置CONTINUE HANDLER,避免EXIT HANDLER提前终止存储过程
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET @not_found = 1;
    DECLARE @not_found INT;

    -- PASO 1: Verificar existencia total de lotes seleccionados y obtener precio por kg
    SELECT IFNULL(SUM(stock),0), AVG(precio)
    INTO total_kg_envasar, precio_por_kg
    FROM controlstock.lotesenvasado 
    WHERE ordenID = ordenID_in;

    -- Validar total KG para envasar
    IF total_kg_envasar < cantidad_kg_envasar THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'No hay suficientes KG. para envasar';
    END IF;

    -- PASO 2: Verificar Existencia de Envases y sacar precio unitario
    SELECT IFNULL(SUM(cantidad),0) 
    INTO total_envases
    FROM controlstock.productobodega 
    WHERE productoID = envaseID_in AND bodegaID = bod_origenID_in;
    
    SET precio_envase = (SELECT AVG(precio) FROM controlstock.existencias WHERE productoID = envaseID_in AND bodegaID = bod_origenID_in AND status IN (1,2));

    -- Validar total envases para envasar
    IF total_envases < cantidad_envase THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'No hay suficientes envases para envasar';
    END IF;

    -- Setear Kg Restantes
    SET kg_restante = cantidad_kg_envasar;
    
    -- Setear Envases Restantes
    SET envases_restantes = cantidad_envase;

    -- PASO 3: Descontar del producto granel
    SET @not_found = 0;
    OPEN cur_lotes_envasado;

    read_loop: LOOP
        FETCH cur_lotes_envasado INTO lote_id, lote_precio, lote_cantidad;
        
        -- 当无数据或KG已处理完时退出循环
        IF @not_found = 1 OR kg_restante <= 0 THEN
            LEAVE read_loop;
        END IF;

        IF lote_cantidad <= kg_restante THEN
            UPDATE controlstock.lotesenvasado
            SET stock = 0
            WHERE id = lote_id;
            SET kg_restante = kg_restante - lote_cantidad;
        ELSE
            UPDATE controlstock.lotesenvasado
            SET stock = lote_cantidad - kg_restante
            WHERE id = lote_id;
            SET kg_restante = 0;
        END IF;
    END LOOP;

    CLOSE cur_lotes_envasado;

    -- PASO 4: Descontar de los envases
    SET @not_found = 0;
    OPEN cur_envases;

    envase_loop: LOOP
        FETCH cur_envases INTO envase_lote_id, envase_cantidad;
        
        -- 当无数据或 envases 已处理完时退出循环
        IF @not_found = 1 OR envases_restantes <= 0 THEN
            LEAVE envase_loop;
        END IF;

        IF envase_cantidad <= envases_restantes THEN
            UPDATE controlstock.existencias
            SET stock = 0
            WHERE id = envase_lote_id;
            SET envases_restantes = envases_restantes - envase_cantidad;
        ELSE
            -- 修复语法错误:用逗号分隔多个赋值,而非AND
            UPDATE controlstock.existencias
            SET stock = envase_cantidad - envases_restantes, status = 2
            WHERE id = envase_lote_id;
            SET envases_restantes = 0;
        END IF;
    END LOOP;

    CLOSE cur_envases;

    -- PASO 5: Calcular el precio unitario del producto envasado
    SET precio_unitario = precio_por_kg * capacidad_envase + precio_envase;

    -- PASO 6: Añadir el producto envasado al inventario
    INSERT INTO controlstock.itemsenvasado (documentoID, productoID, bodegaID, lote, unidadID, precio, cantidad, stock, userID, status)
    VALUES (ordenID_in, productoID_in, bod_destinoID_in, lote_orden_in, 4, precio_unitario, cantidad_envase, cantidad_envase, userID_in, 1);

END

关键修复点

  • 调整DECLARE顺序:确保所有变量、游标、HANDLER声明都在BEGIN块的最顶部,符合MySQL语法要求。
  • 替换EXIT HANDLER为CONTINUE HANDLER:使用全局变量@not_found标记游标是否取完数据,避免EXIT HANDLER提前终止存储过程,同时避免尝试关闭已关闭的游标。
  • 修复UPDATE语法错误:第二个游标循环中的UPDATE语句,将AND改为逗号,正确设置多个字段值。
  • 循环退出条件优化:在每个游标循环中同时检查@not_found和剩余数量,确保循环能正常终止。

内容的提问来源于stack exchange,提问作者Kibuya Shanon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:08:10