在 MySQL 中使用存储过程时出现两个问题

Getting two issues while using stored procedure in MySQL

下面是我用于计算利息的存储过程的示例代码。这段代码不是 executable 因为根据在游标声明之前定义创建临时 table 块时发现它的问题但是如果我最近在游标声明之后定义了同样的东西然后它执行成功。

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

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- 成功执行上述存储过程后(在游标声明后定义临时 table),调用 SP 时出现以下问题:

CALL sp_interest_calculation_test ('A');

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

这是因为我没有将id定义为主键所以我只是用

更改了列
id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY

我在 MySQL 第一次使用存储过程,修复上述问题对我来说很忙所以你能给我描述一下上述问题及其原因吗?无论如何,它定义了自动编号列没有将其定义为主键 ?

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

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

关于 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

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

DECLARE is permitted only inside a BEGIN ... END compound statement and must be at its start, before any other statements.

Declarations must follow a certain order. Cursor declarations must appear before handler declarations. Variable and condition declarations must appear before cursor or handler declarations.

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 字节线程堆栈,并且从未遇到过限制。