同一例程中两个游标出现 MySQL 存储过程错误“ER_SP_CURSOR_NOT_OPEN:游标未打开”

问题描述 投票:0回答:1

在库存系统的这个存储过程中,我想根据容器的类型及其容量(以千克为单位)处理产品的包装订单(以千克为单位)。 这最终将包装产品的存在存储在表“itemsenvasado”中。

我使用了两个光标,因为:

  • 对于库存超过一批的产品
  • 对于库存多于一批的集装箱
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 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;
    
    -- Cursor para Procesar los lotes de producto granel
    DECLARE cur_lotes_envasado CURSOR FOR
    SELECT id, precio, cantidadenvasar
    FROM controlstock.lotesenvasado
    WHERE ordenID = ordenID_in
    ORDER BY fecInsert;
    
    -- Cursor para Procesar los lotes de envases
    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; -- Asumiendo que 'fecInsert' indica la antigüedad del lote

   -- Manejadores de Salida
    DECLARE EXIT HANDLER FOR NOT FOUND 
    BEGIN
        CLOSE cur_lotes_envasado;
        CLOSE cur_envases;
    END;
    
    -- 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 y de los envases
    OPEN cur_lotes_envasado;

    read_loop: LOOP
        FETCH cur_lotes_envasado INTO lote_id, lote_precio, lote_cantidad;

        -- Terminar Bucle cuando no quedan KG
        IF 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;

    OPEN cur_envases;

    envase_loop: LOOP
        FETCH cur_envases INTO envase_lote_id, envase_cantidad;

        IF 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
            UPDATE controlstock.existencias
            SET stock = envase_cantidad - envases_restantes AND status = 2
            WHERE id = envase_lote_id;
            SET envases_restantes = 0;
        END IF;
    END LOOP;

    CLOSE cur_envases;

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

    -- PASO 5: 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

当我运行它时,它仅执行第一个光标,然后出现错误

ER_SP_CURSOR_NOT_OPEN:光标未打开

我尝试移动光标,但在 MySQL Workbench 中出现错误

DECLARE 在此位置无效,期待 END

mysql stored-procedures database-cursor
1个回答
0
投票

当通过第一个光标获得 NOT FOUND 时,将触发 EXIT 处理程序。执行其语句(关闭游标)后,流程EXIT(离开)您的存储过程。

您必须使用 CONTINUE 处理程序,它设置一个变量,但将流程返回到触发点,在 FETCH 之后立即检查变量,如果设置了变量,则退出循环。

示意图:

CREATE PROCEDURE ..
DECLARE done INT DEFAULT FALSE;
DECLARE CURSOR cursor1 ...
DECLARE CURSOR cursor2 ...
DECLARE CONTINUE HANDLER FOR NOT FOUND BEGIN SET done = TRUE; END;
BEGIN
    ...
    OPEN cursor1;
    DO
        FETCH cursor1 INTO ...
        IF done THEN LEAVE ...   -- check does 1st cursor is empty
                                 -- if true then leave cycle but continue execution   
        ...
    LOOP;
    SET done = FALSE;            -- reset variable value for 2nd cursor checking
    ...
    OPEN cursor2;
    DO
        FETCH cursor2 INTO ...
        IF done THEN LEAVE ...
    ...
    LOOP;
    ... 
© www.soinside.com 2019 - 2024. All rights reserved.