在MySQL中使用存储过程时遇到两个问题

时间:2016-09-17 23:32:26

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

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

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没有在任何地方使用,并且在示例代码中的游标内没有执行更多操作,尽管我正在处理块内的大量兴趣计算。

非常感谢你,希望你能对这个问题感兴趣。 (我没有找到任何确切的问题)。我也在寻找它的替代方案,我只需要为每个分类账计算借方,贷方,余额,我在单一查询中没有循环地尝试过,但执行永远不会结束。

2 个答案:

答案 0 :(得分:1)

关于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设置是使用FETCH和处理程序执行LEAVE循环的正确方法。游标循环很挑剔。不要直接弄乱done变量。您信任FETCH及其处理程序。因此,让它自己处理done

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

答案 1 :(得分: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字节的线程堆栈,因为它是默认的,并且从未遇到过限制。