【问题标题】:mysql stored procedure that calls itself recursively递归调用自身的mysql存储过程
【发布时间】:2011-03-27 04:06:14
【问题描述】:

我有下表:

id | parent_id | quantity
-------------------------
1  | null      | 5
2  | null      | 3
3  | 2         | 10
4  | 2         | 15
5  | 3         | 2
6  | 5         | 4
7  | 1         | 9

现在我需要 mysql 中的一个存储过程,它递归地调用自身并返回计算的数量。 例如,id 6 有 5 作为父级,而 3 作为父级,而 2 作为父级。 所以我需要计算4 * 2 * 10 * 3 (= 240) 作为结果。

我对存储过程还很陌生,以后不会经常使用它们,因为我更喜欢将业务逻辑放在我的程序代码中,而不是在数据库中。但在这种情况下,我无法避免。

也许 mysql 大师(就是你)可以在几秒钟内拼凑出一个工作语句。

【问题讨论】:

  • 这个问题似乎是针对类似的解决方案:stackoverflow.com/questions/1085287/hierarchical-data-in-mysql。基本上,这在 mysql 中很棘手!
  • 存储过程中的递归是允许的,但默认情况下是禁用的。要启用递归,请将 max_sp_recursion_depth 服务器系统变量设置为大于零的值
  • mysql 无法识别语句“WITH RECURSIVE TABLE() AS”?

标签: mysql stored-procedures recursion


【解决方案1】:

看看 Mike Hillyer 的 Managing Hierarchical Data in MySQL

它包含处理分层数据的完整示例。

【讨论】:

  • +1 链接,非常有趣。我知道如何通过自连接获取我想要的数据,但我希望将其作为存储过程(函数),因为我确实需要在一个查询中多次使用它,如果我重用我的代码,这将大大降低可重复性一切从头再来。
  • 您需要继续阅读,因为这篇文章实际上并不是关于自联接,而是关于嵌套集,这是完全不同的事情。
【解决方案2】:

它只在mysql版本> = 5中工作

存储过程声明是这样的,

你可以给它一点改进,但​​这工作:

DELIMITER $$

CREATE PROCEDURE calctotal(
   IN number INT,
   OUT total INT
)

BEGIN

   DECLARE parent_ID INT DEFAULT NULL ;
   DECLARE tmptotal INT DEFAULT 0;
   DECLARE tmptotal2 INT DEFAULT 0;

   SELECT parentid   FROM test   WHERE id = number INTO parent_ID;   
   SELECT quantity   FROM test   WHERE id = number INTO tmptotal;     

   IF parent_ID IS NULL
    THEN
    SET total = tmptotal;
   ELSE     
    CALL calctotal(parent_ID, tmptotal2);
    SET total = tmptotal2 * tmptotal;   
   END IF;

END$$

DELIMITER ;

调用就像 (设置这个变量很重要):

SET @@GLOBAL.max_sp_recursion_depth = 255;
SET @@session.max_sp_recursion_depth = 255; 

CALL calctotal(6, @total);
SELECT @total;

【讨论】:

  • 看起来不错。我像函数一样实现了它,我总是得到错误“不允许递归存储函数和触发器”,但我的 addison-wesley “MySQL 5”书声称递归也适用于函数。最后的想法???
  • 存储的函数不能递归。来自官方网站:dev.mysql.com/doc/refman/5.0/en/stored-routines-syntax.html
  • 这是 mysql 团队的意思,但我想他们有理由拒绝这样做。我希望我能做到SELECT id, computed_quantity(id) FROM table。无论如何,在您的帮助下,我能够将其作为递归过程执行,但这需要我一些额外的 c# 客户端代码才能获得我想要的结果。
【解决方案3】:

如何避免程序:

SELECT quantity from (
 SELECT @rq:=parent_id as id, @val:=@val*quantity as quantity from (
  select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
 ) t # we have to inverse ids first in order to get this working...
 join
 ( select @rq:= 6 /* example query */, @val:= 1 /* we are going to multiply values */) tmp
 where id=@rq
) c where id is null;

Check out Fiddle!

注意!如果行的parent_id>id,这将不起作用。

干杯!

【讨论】:

    【解决方案4】:
    DELIMITER $$
    CREATE DEFINER=`arun`@`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
    BEGIN
      DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
    
      DECLARE cname VARCHAR(64);
      SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
      IF ilevel = 0 THEN
        DROP TEMPORARY TABLE IF EXISTS _descendants;
        CREATE TEMPORARY TABLE _descendants (
          childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
      );
      END IF;
      IF irows > 0 THEN
        BEGIN
          DECLARE cur CURSOR FOR
            SELECT
              f.account_id,f.parent_id,f.account_name,
              (SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
            FROM account t JOIN account f ON t.account_id=f.account_id
            WHERE t.parent_id=iroot and t.location_id=locid 
            ORDER BY childcount<>0,t.account_id;
          DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
          OPEN cur;
          WHILE NOT done DO
            FETCH cur INTO ichildid,iparentid,cname,ichildcount;
            IF NOT done THEN
              INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
              IF ichildcount > 0 THEN
                CALL recursivesubtree( ichildid, ilevel + 1 );
              END IF;
            END IF;
          END WHILE;
          CLOSE cur;
        END;
      END IF;
    
      IF ilevel = 0 THEN
        -- Show result table headed by name that corresponds to iroot:
        SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
        SET @sql = CONCAT('SELECT   CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
                      ' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
        PREPARE stmt FROM @sql;
        EXECUTE stmt;
        DROP PREPARE stmt;
      END IF;
    END$$
    DELIMITER ;
    

    【讨论】:

      猜你喜欢
      • 2016-02-18
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      • 2015-08-08
      • 2013-11-11
      • 2013-11-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多