zoukankan      html  css  js  c++  java
  • mysql 实现oracle start with connect by递归

    在Oracle 中我们知道有一个 Hierarchical Queries 通过CONNECT BY 我们可以方便的查了所有当前节点下的所有子节点。但很遗憾,在MySQL的目前版本中还没有对应的功能。

    在MySQL中如果是有限的层次,比如我们事先如果可以确定这个树的最大深度是4, 那么所有节点为根的树的深度均不会超过4,则我们可以直接通过left join 来实现。

    但很多时候我们无法控制树的深度。这时就需要在MySQL中用存储过程来实现或在你的程序中来实现这个递归。本文讨论一下几种实现的方法。

    样例数据:


    mysql> create table treeNodes
        -> (
        ->  id int primary key,
        ->  nodename varchar(20),
        ->  pid int
        -> );
    Query OK, 0 rows affected (0.09 sec)
    mysql> select * from treenodes;
    +----+----------+------+
    | id | nodename | pid  |
    +----+----------+------+
    |  1 | A        |    0 |
    |  2 | B        |    1 |
    |  3 | C        |    1 |
    |  4 | D        |    2 |
    |  5 | E        |    2 |
    |  6 | F        |    3 |
    |  7 | G        |    6 |
    |  8 | H        |    0 |
    |  9 | I        |    8 |
    | 10 | J        |    8 |
    | 11 | K        |    8 |
    | 12 | L        |    9 |
    | 13 | M        |    9 |
    | 14 | N        |   12 |
    | 15 | O        |   12 |
    | 16 | P        |   15 |
    | 17 | Q        |   15 |
    +----+----------+------+
    17 rows in set (0.00 sec)

    树形图如下


     1:A
      +-- 2:B
      |    +-- 4:D
      |    +-- 5:E
      +-- 3:C
           +-- 6:F
                +-- 7:G
     8:H
      +-- 9:I
      |    +-- 12:L
      |    |    +--14:N
      |    |    +--15:O
      |    |        +--16:P
      |    |        +--17:Q
      |    +-- 13:M
      +-- 10:J
      +-- 11:K  

    方法一:利用函数来得到所有子节点号

    创建一个function getChildLst, 得到一个由所有子节点号组成的字符串. 


    mysql> delimiter //
    mysql>
    mysql> CREATE FUNCTION `getChildLst`(rootId INT)
        -> RETURNS varchar(1000)
        -> BEGIN
        ->   DECLARE sTemp VARCHAR(1000);
        ->   DECLARE sTempChd VARCHAR(1000);
        ->
        ->   SET sTemp = '$';
        ->   SET sTempChd =cast(rootId as CHAR);
        ->
        ->   WHILE sTempChd is not null DO
        ->     SET sTemp = concat(sTemp,',',sTempChd);
        ->     SELECT group_concat(id) INTO sTempChd FROM treeNodes where FIND_IN_SET(pid,sTempChd)>0;
        ->   END WHILE;
        ->   RETURN sTemp;
        -> END
        -> //
    Query OK, 0 rows affected (0.00 sec)

    mysql>
    mysql> delimiter ;


    使用我们直接利用find_in_set函数配合这个getChildlst来查找


    mysql> select getChildLst(1);
    +-----------------+
    | getChildLst(1)  |
    +-----------------+
    | $,1,2,3,4,5,6,7 |
    +-----------------+
    1 row in set (0.00 sec)

    mysql> select * from treeNodes
        -> where FIND_IN_SET(id, getChildLst(1));
    +----+----------+------+
    | id | nodename | pid  |
    +----+----------+------+
    |  1 | A        |    0 |
    |  2 | B        |    1 |
    |  3 | C        |    1 |
    |  4 | D        |    2 |
    |  5 | E        |    2 |
    |  6 | F        |    3 |
    |  7 | G        |    6 |
    +----+----------+------+
    7 rows in set (0.01 sec)

    mysql> select * from treeNodes
        -> where FIND_IN_SET(id, getChildLst(3));
    +----+----------+------+
    | id | nodename | pid  |
    +----+----------+------+
    |  3 | C        |    1 |
    |  6 | F        |    3 |
    |  7 | G        |    6 |
    +----+----------+------+
    3 rows in set (0.01 sec)

    优点: 简单,方便,没有递归调用层次深度的限制 (max_sp_recursion_depth,最大255) ;

    缺点:长度受限,虽然可以扩大 RETURNS varchar(1000),但总是有最大限制的。

    MySQL目前版本( 5.1.33-community)中还不支持function 的递归调用。

    方法二:利用临时表和过程递归

    创建存储过程如下。createChildLst 为递归过程,showChildLst为调用入口过程,准备临时表及初始化。


    mysql> delimiter //
    mysql>
    mysql> # 入口过程
    mysql> CREATE PROCEDURE showChildLst (IN rootId INT)
        -> BEGIN
        ->  CREATE TEMPORARY TABLE IF NOT EXISTS tmpLst
        ->   (sno int primary key auto_increment,id int,depth int);
        ->  DELETE FROM tmpLst;
        ->
        ->  CALL createChildLst(rootId,0);
        ->
        ->  select tmpLst.*,treeNodes.* from tmpLst,treeNodes where tmpLst.id=treeNodes.id order by tmpLst.sno;
        -> END;
        -> //
    Query OK, 0 rows affected (0.00 sec)

    mysql>
    mysql> # 递归过程
    mysql> CREATE PROCEDURE createChildLst (IN rootId INT,IN nDepth INT)
        -> BEGIN
        ->  DECLARE done INT DEFAULT 0;
        ->  DECLARE b INT;
        ->  DECLARE cur1 CURSOR FOR SELECT id FROM treeNodes where pid=rootId;
        ->  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
        ->
        ->  insert into tmpLst values (null,rootId,nDepth);

        ->
        ->  OPEN cur1;
        ->
        ->  FETCH cur1 INTO b;
        ->  WHILE done=0 DO
        ->          CALL createChildLst(b,nDepth+1);
        ->          FETCH cur1 INTO b;
        ->  END WHILE;
        ->
        ->  CLOSE cur1;
        -> END;
        -> //
    Query OK, 0 rows affected (0.00 sec)
    mysql> delimiter ;

    调用时传入结点


    mysql> call showChildLst(1);
    +-----+------+-------+----+----------+------+
    | sno | id   | depth | id | nodename | pid  |
    +-----+------+-------+----+----------+------+
    |   4 |    1 |     0 |  1 | A        |    0 |
    |   5 |    2 |     1 |  2 | B        |    1 |
    |   6 |    4 |     2 |  4 | D        |    2 |
    |   7 |    5 |     2 |  5 | E        |    2 |
    |   8 |    3 |     1 |  3 | C        |    1 |
    |   9 |    6 |     2 |  6 | F        |    3 |
    |  10 |    7 |     3 |  7 | G        |    6 |
    +-----+------+-------+----+----------+------+

    7 rows in set (0.13 sec)

    Query OK, 0 rows affected, 1 warning (0.14 sec)

    mysql>
    mysql> call showChildLst(3);
    +-----+------+-------+----+----------+------+
    | sno | id   | depth | id | nodename | pid  |
    +-----+------+-------+----+----------+------+
    |   1 |    3 |     0 |  3 | C        |    1 |
    |   2 |    6 |     1 |  6 | F        |    3 |
    |   3 |    7 |     2 |  7 | G        |    6 |
    +-----+------+-------+----+----------+------+

    3 rows in set (0.11 sec)

    Query OK, 0 rows affected, 1 warning (0.11 sec)

    depth 为深度,这样可以在程序进行一些显示上的格式化处理。类似于oracle中的 level 伪列。sno 仅供排序控制。这样你还可以通过临时表tmpLst与数据库中其它表进行联接查询。

    MySQL中你可以利用系统参数 max_sp_recursion_depth 来控制递归调用的层数上限。如下例设为12.


    mysql> set max_sp_recursion_depth=12;
    Query OK, 0 rows affected (0.00 sec)

    优点 : 可以更灵活处理,及层数的显示。并且可以按照树的遍历顺序得到结果。

    缺点 : 递归有255的限制。

    方法三:利用中间表和过程

    (本方法由yongyupost2000提供样子改编)

    创建存储过程如下。由于MySQL中不允许在同一语句中对临时表多次引用,只以使用普通表tmpLst来实现了。当然你的程序中负责在用完后清除这个表。


    delimiter //

    drop PROCEDURE IF EXISTS  showTreeNodes_yongyupost2000//

    CREATE PROCEDURE showTreeNodes_yongyupost2000 (IN rootid INT)
    BEGIN
     DECLARE Level int ;
     drop TABLE IF EXISTS tmpLst;
     CREATE TABLE tmpLst (
      id int,
      nLevel int,
      sCort varchar(8000)
     );
     
     Set Level=0 ;
     INSERT into tmpLst SELECT id,Level,ID FROM treeNodes WHERE PID=rootid;
     WHILE ROW_COUNT()>0 DO
      SET Level=Level+1 ;
      INSERT into tmpLst
       SELECT A.ID,Level,concat(B.sCort,A.ID) FROM treeNodes A,tmpLst B
        WHERE  A.PID=B.ID AND B.nLevel=Level-1  ;
     END WHILE;
     
    END;
    //

    delimiter ;

    CALL showTreeNodes_yongyupost2000(0);

    执行完后会产生一个tmpLst表,nLevel 为节点深度,sCort 为排序字段。
    使用方法


    SELECT concat(SPACE(B.nLevel*2),'+--',A.nodename)
    FROM treeNodes A,tmpLst B
    WHERE A.ID=B.ID
    ORDER BY B.sCort;

    +--------------------------------------------+
    | concat(SPACE(B.nLevel*2),'+--',A.nodename) |
    +--------------------------------------------+
    | +--A                                       |
    |   +--B                                     |
    |     +--D                                   |
    |     +--E                                   |
    |   +--C                                     |
    |     +--F                                   |
    |       +--G                                 |
    | +--H                                       |
    |   +--J                                     |
    |   +--K                                     |
    |   +--I                                     |
    |     +--L                                   |
    |       +--N                                 |
    |       +--O                                 |
    |         +--P                               |
    |         +--Q                               |
    |     +--M                                   |
    +--------------------------------------------+
    17 rows in set (0.00 sec)

  • 相关阅读:
    CodeForces Gym 100500A A. Poetry Challenge DFS
    CDOJ 486 Good Morning 傻逼题
    CDOJ 483 Data Structure Problem DFS
    CDOJ 482 Charitable Exchange bfs
    CDOJ 481 Apparent Magnitude 水题
    Codeforces Gym 100637G G. #TheDress 暴力
    Gym 100637F F. The Pool for Lucky Ones 暴力
    Codeforces Gym 100637B B. Lunch 找规律
    Codeforces Gym 100637A A. Nano alarm-clocks 前缀和
    TC SRM 663 div2 B AABB 逆推
  • 原文地址:https://www.cnblogs.com/coprince/p/4555176.html
Copyright © 2011-2022 走看看