【问题标题】:Getting two issues while using stored procedure in MySQL在 MySQL 中使用存储过程时遇到两个问题
【发布时间】:2016-09-17 23:32:26
【问题描述】:

以下是我正在处理的用于计算利息的存储过程的示例代码。此代码不可执行,因为根据在游标声明之前定义创建临时表块时发现它的问题,但是如果我最近在游标声明之后定义了相同的东西,那么它就会成功执行。

1- 我的问题是我在游标内使用该表,所以我必须在游标之后定义,否则我错过了什么??

CREATE PROCEDURE `sp_interest_calculation_test`(
    IN sub_type CHAR(1)
)
BEGIN
    DECLARE s_ledger_id INT;
    DECLARE s_start, s_end, s_tran INT DEFAULT 0;

    **DROP TABLE IF EXISTS tmp_interest;
    CREATE TEMPORARY TABLE IF NOT EXISTS tmp_interest(
        id int(11) NOT NULL AUTO_INCREMENT,
        ledger_id INT UNSIGNED,
        dr_amount INT,
        cr_amount INT,
        balance INT
    );**

    DECLARE cur_saving_acc CURSOR FOR 
    SELECT SQL_CALC_FOUND_ROWS 1;

    OPEN cur_saving_acc;

    SET s_end = (SELECT FOUND_ROWS());

    WHILE s_start<s_end DO
    FETCH cur_saving_acc INTO s_ledger_id;

     INSERT INTO tmp_interest(ledger_id)
     SELECT s_ledger_id;

     SELECT * FROM tmp_interest;

     /*Interest calculation logic ends here */
     SET s_start = s_start+1;

     END WHILE;

    CLOSE cur_saving_acc;

END

2- 成功执行上述存储过程(游标声明后定义临时表)后,调用SP时出现以下问题:

CALL sp_interest_calculation_test ('A');

“错误代码:1075 表定义不正确;只能有一个自动列,并且必须定义为一个键”

这是因为我错过了将 id 定义为主键,所以我简单地更改了列

id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY

我第一次在 MySQL 中使用存储过程,解决上述问题对我来说很忙,所以请您描述一下上述问题及其原因,并且无论如何定义自动编号列而不将其定义为主键?

注意:参数 sub_type 没有在任何地方使用,并且示例代码中的光标内没有执行更多操作,尽管我正在处理块内的重度兴趣计算。

非常感谢您,并希望大家对这个问题感兴趣。 (我没有找到任何关于这些问题的确切信息)。我也在寻找它的替代方案,只需计算每个分类帐的借方、贷方、余额,我在单个查询中尝试了没有循环但执行永远不会结束。

【问题讨论】:

    标签: mysql sql stored-procedures primary-key temp-tables


    【解决方案1】:

    DECLARE 只允许在 BEGIN ... END 复合语句中使用,并且必须位于其开头,在任何其他语句之前。

    声明必须遵循一定的顺序。游标声明必须出现在处理程序声明之前。变量和条件声明必须出现在游标或处理程序声明之前。

    http://dev.mysql.com/doc/refman/5.7/en/declare.html

    这就是限制。

    现在,解决方法:添加嵌套的BEGIN ... END 块。

    DELIMITER $$
    CREATE PROCEDURE ...
    BEGIN
      DECLARE ... INT ... -- variable
      CREATE TEMPORARY TABLE... -- following the declarations, no more declarations allowed, unless...
      BEGIN -- resets the scope, changes the rules, allows more declarations
        DECLARE ... INT ... -- variables
        DECLARE ... CURSOR ...
        DECLARE CONTINUE HANDLER ...
        OPEN ...
        ...
      END;
    END $$
    

    外部块中的所有变量仍在内部块的范围内,除非内部块中的另一个变量名称冲突。

    外部块中的HANDLER 也在内部块中的信号范围内,除非在那里声明了冲突的处理程序,在这种情况下,内部处理程序将捕获异常,而外部句柄将捕获任何由内部处理程序,包括 RESIGNAL

    允许多个嵌套级别。 thread_stack 的大小可能是一个因素,但文档不清楚。在将其设为默认值之前,我一直在运行 262,144 字节的线程堆栈,并且从未遇到过限制。

    【讨论】:

    • 很好的回答迈克尔
    【解决方案2】:

    关于AUTO_INCREMENT 设置的一些注意事项:

    create table t1
    (   ai int not null auto_increment,
        b int primary key
    )ENGINE=InnoDB;
    -- Error 1075: AI must be a key
    
    create table t2
    (   ai int not null auto_increment,
        b int primary key,
        key(ai)
    )ENGINE=InnoDB;
    
    -- This is successful
    
    create table t3
    (   ai int not null auto_increment,
        b int primary key,
        c int not null,
        key(ai,c)
    )ENGINE=InnoDB;
    -- This is successful
    
    create table t4
    (   ai int not null auto_increment,
        b int primary key,
        c int not null,
        key(c,ai)
    )ENGINE=InnoDB;
    -- Error 1075: AI must be a key (ai is not left-most in composite)
    
    create table t5
    (   ai int auto_increment primary key,
        b int not null,
        c int not null
    )ENGINE=InnoDB;
    -- Success: This is the PREDOMINANT way of doing it
    
    create table t6
    (   ai int not null AUTO_INCREMENT,
        b int not null,
        c int not null,
        PRIMARY KEY(c,ai)
    )ENGINE=InnoDB;
    -- Error 1075: AI must be a key (ai is not left-most in PK)
    
    create table t7
    (   ai int not null AUTO_INCREMENT,
        b int not null,
        c int not null,
        PRIMARY KEY(ai,c)
    )ENGINE=InnoDB;
    -- Success
    
    create table t8
    (   ai int not null AUTO_INCREMENT,
        b int not null,
        c int not null,
        KEY(ai),
        KEY(c,ai)
    )ENGINE=InnoDB;
    insert t8(b,c) values(1,2);
    insert t8(b,c) values(33,44);
    select * from t8;
    +----+----+----+
    | ai | b  | c  |
    +----+----+----+
    |  1 |  1 |  2 |
    |  2 | 33 | 44 |
    +----+----+----+
    

    注意 ENGINE=MyISAM 的行为不同,例如允许 AI 最不左。

    存储过程:

    DROP PROCEDURE IF EXISTS `sp_interest_calculation_test`;
    DELIMITER $$
    CREATE PROCEDURE `sp_interest_calculation_test`(
        IN sub_type CHAR(1)
    )
    BEGIN
        DECLARE done INT DEFAULT FALSE;
        DECLARE l_ledger_id INT;
        DECLARE dr_sum DECIMAL(14,2) DEFAULT 0;
        DECLARE l_dr_amount, l_cr_amount, l_balance DECIMAL(14,2);
        DECLARE cur_saving_acc CURSOR 
           FOR SELECT ledger_id, dr_amount, cr_amount, balance FROM tmp_interest order by id;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
        DROP TABLE IF EXISTS tmp_interest;
        CREATE TEMPORARY TABLE IF NOT EXISTS tmp_interest(
            id int(11) NOT NULL AUTO_INCREMENT,
            ledger_id INT UNSIGNED,
            dr_amount DECIMAL(14,2),
            cr_amount DECIMAL(14,2),
            balance DECIMAL(14,2),
            KEY(id)
        );
        INSERT tmp_interest (ledger_id,dr_amount,cr_amount,balance) VALUES
        (101,100,0,200),(102,140,0,340),(103,0,50,290);
    
        OPEN cur_saving_acc;
        read_loop: LOOP
            FETCH cur_saving_acc INTO l_ledger_id, l_dr_amount, l_cr_amount, l_balance;
    
            IF done THEN
              LEAVE read_loop;
            END IF;
            SET dr_sum=dr_sum+l_dr_amount;
    
        END LOOP;
        CLOSE cur_saving_acc;
        SELECT CONCAT('sum of debits=',dr_sum) as outCol;
    END;$$
    DELIMITER ;
    

    测试:

    call sp_interest_calculation_test('s');
    +----------------------+
    | outCol               |
    +----------------------+
    | sum of debits=240.00 |
    +----------------------+
    

    上述CURSOR 设置是使用LEAVE 和处理程序执行FETCH 循环的正确方法。光标循环很挑剔。不要直接弄乱done 变量。你信任FETCH 和它的处理程序。所以让它自己处理done

    CURSORS 上的 MySQL 手册页。另请注意,游标表现得像垃圾。和他们一起玩很有趣。他们可以让你摆脱棘手的局面。但是,当您使用它们时,性能就会下降。

    【讨论】:

    • 非常感谢,但我在这个逻辑中首先使用了这种模式,但它会产生很多问题,有时它会在执行过程中自动结束,并且在几次执行中就出现了问题。所以我已经更改为上述内容,并且自 2-3 个月以来工作正常,但我认为 MySQL 中的 CURSOR 不值得信赖。你说的话 ?如果有人建议我替代方案,那么我将更改逻辑,让我在我的问题中提及它,调试也很乏味。为您提供非常有用的信息 +1。
    • 我永远不会使用不同于我展示的模式和手册中的模式。如果您希望我查看您的任何内容,请通过 Campaigns 聊天并联系我。我当然不会在那个while 中使用SQL_CALC_FOUND_ROWS,这不是最佳实践
    • 那么我确定我错过了一些东西,区别只是 SET done = TRUE; (你的) 和 SET 完成 = 1;我的,在 if 语句中以同样的方式离开。这有什么意义吗?当然我会很快联系你,然后我会描述整个场景和我的要求。再次感谢
    • 这就是为什么我说“光标表现得像垃圾”。高性能数据库引擎擅长集合和索引。使用游标的人要么对 sql 完全陌生,不能做集合和关系,要么他们正处于非常棘手的事情之中,找不到更好的方法。当人们想要将自己的步骤和程序思维投入其中时,高性能引擎就会被破坏。我去过那儿。我知道人们为什么使用游标。最好让他们摆脱它。尤其是在处理大数据时。
    猜你喜欢
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 2011-02-18
    • 2020-05-27
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多