Mysql 实现 向上递归查找父节点并返回树结构

需求:通过mysql 8.0以下版本实现,一个人多角色id,一个角色对应某个节点menu_id,根节点的父节点存储为NULL, 向上递归查找父节点并返回树结构。

如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示叶子与根。
测试数据:
Mysql 实现 向上递归查找父节点并返回树结构
如果 传入角色ID【auth_id】:   5,15,25,26,则只查找5,15的所有父节点,因为25,26无根节点
Mysql 实现 向上递归查找父节点并返回树结构

测试数据:

SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;  -- ---------------------------- -- Table structure for Menu -- ---------------------------- DROP TABLE IF EXISTS `Menu`; CREATE TABLE `Menu` (   `menu_id` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '0',   `sup_menu` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,   `auth_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,   PRIMARY KEY (`menu_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;  -- ---------------------------- -- Records of Menu -- ---------------------------- BEGIN; INSERT INTO `Menu` VALUES ('1', NULL, '1'); INSERT INTO `Menu` VALUES ('11', NULL, '11'); INSERT INTO `Menu` VALUES ('12', '11', '12'); INSERT INTO `Menu` VALUES ('13', '11', '13'); INSERT INTO `Menu` VALUES ('14', '12', '14'); INSERT INTO `Menu` VALUES ('15', '12', '15'); INSERT INTO `Menu` VALUES ('16', '13', '16'); INSERT INTO `Menu` VALUES ('17', '13', '17'); INSERT INTO `Menu` VALUES ('2', '1', '2'); INSERT INTO `Menu` VALUES ('22', '21', '26'); INSERT INTO `Menu` VALUES ('25', '22', '25'); INSERT INTO `Menu` VALUES ('3', '1', '3'); INSERT INTO `Menu` VALUES ('4', '2', '4'); INSERT INTO `Menu` VALUES ('5', '2', '5'); INSERT INTO `Menu` VALUES ('6', '3', '6'); INSERT INTO `Menu` VALUES ('7', '3', '7'); COMMIT;  SET FOREIGN_KEY_CHECKS = 1; 

 方法一:纯存储过程实现

 1 -- 纯存储过程实现  2 DELIMITER //  3 -- 如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示  4 DROP PROCEDURE if EXISTS  query_menu_by_authid;  5 CREATE PROCEDURE query_menu_by_authid(IN roleIds varchar(1000))  6   7 BEGIN  8 -- 用于判断是否结束循环  9 declare done int default 0;   10 -- 用于存储结果集 11 declare menuid bigint;  12 declare temp_menu_ids VARCHAR(3000); 13 declare temp_sup_menus VARCHAR(3000); 14 declare return_menu_ids VARCHAR(3000); 15  16 -- 定义游标 17 declare idCur cursor for select menu_id from Menu where  FIND_IN_SET(auth_id,roleIds) ;  18 -- 定义 设置循环结束标识done值怎么改变 的逻辑  19 declare continue handler for not FOUND set done = 1; 20  21  22 open idCur ;  23 FETCH idCur INTO menuid; 24 -- 临时变量存储menu_id集合 25 SET temp_menu_ids = ''; 26 -- 返回存储menu_id集合 27 SET return_menu_ids = ''; 28  29 WHILE done<> 1 DO  30 --  只查找 单个 auth_id  相关的menu_id 31 -- 通过authid, 查找出menu_id, sup_menu is null 32  33 SELECT   34 GROUP_CONCAT(T2._menu_id) as t_menu_id, 35 GROUP_CONCAT(T2._sup_menu) as t_sup_menu  36 into temp_menu_ids,temp_sup_menus 37 FROM 38      (   39        SELECT  40        -- 保存当前节点。(从叶节点往根节点找,@r 保存当前到哪个位置了)。@r 初始为要找的节点。 41        -- _menu_id 当前节点 42        DISTINCT @r as _menu_id,  43              ( 44            SELECT  45              CASE   46                         WHEN sup_menu IS NULL THEN @r:= 'NULL' 47                         ELSE @r:= sup_menu 48              END 49              FROM Menu   50              WHERE  _menu_id = Menu.menu_id 51              ) AS _sup_menu, 52        -- 保存当前的Level 53        @l := @l + 1 AS level 54        FROM 55        ( SELECT @r := menuid, @l := 0 56        ) vars, Menu AS temp 57         -- 如果该节点没有父节点,则会被置为0 58         WHERE  @r <> 0       59         ORDER BY @l DESC 60        ) T2 61       INNER JOIN Menu T1 62     ON T2._menu_id = T1.menu_id   63  ORDER BY T2.level DESC ; 64  65  -- 满足必须要有根节点NULL字符,则表明有根,否则不拼接给返回值 66  IF FIND_IN_SET('NULL',temp_sup_menus) > 0  THEN  67  SET return_menu_ids = CONCAT(temp_menu_ids,',',return_menu_ids); 68  END IF; 69   70 FETCH idCur INTO menuid; 71 END WHILE; 72 CLOSE  idCur;  73  74 -- 返回指定menu_id 的数据集合 75 select Menu.menu_id,Menu.sup_menu,Menu.auth_id  76 FROM Menu  77 WHERE FIND_IN_SET(menu_id,return_menu_ids) 78 ORDER BY Menu.menu_id*1 ASC ; 79     80 END; 81 // 82 DELIMITER; 83  84 CALL  query_menu_by_authid('5,15,25,26'); 85 CALL  query_menu_by_authid('5,17'); 86 CALL  query_menu_by_authid('5,11');

方法二:函数+存储过程实现

 1 -- 函数+存储过程实现  2 -- 根据叶子节点查找所有父节点及其本身节点。如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示.  3 DROP FUNCTION  IF EXISTS `getParentList`;  4 CREATE FUNCTION `getParentList`(in_menu_id varchar(255))  5 RETURNS varchar(3000)   6 BEGIN  7     DECLARE sTemp VARCHAR(3000);  8     DECLARE sTempPar VARCHAR(3000);   9     SET sTemp = '';  10     SET sTempPar = in_menu_id;  11   12     -- 循环递归 13     WHILE sTempPar is not null DO  14         -- 判断是否是第一个,不加的话第一个会为空 15         IF sTemp != '' THEN 16             SET sTemp = concat(sTemp,',',sTempPar); 17         ELSE 18             SET sTemp = sTempPar; 19         END IF; 20         SET sTemp = concat(sTemp,',',sTempPar);  21         SELECT group_concat(sup_menu)  22                 INTO sTempPar  23                 FROM Menu  24                 where sup_menu<>menu_id  25                 and FIND_IN_SET(menu_id,sTempPar) > 0;  26     END WHILE;  27     RETURN sTemp;  28 END; 29  30  31 DELIMITER // 32 -- 如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示 33 DROP PROCEDURE if EXISTS  select_menu_by_authids ; 34 CREATE PROCEDURE select_menu_by_authids(IN roleIds varchar(3000)) 35  36 BEGIN 37 -- 用于判断是否结束循环 38 declare done int default 0;   39 -- 用于存储结果集 40 declare menuid varchar(255);  41 declare set_menu_ids VARCHAR(3000); 42 --  检查是否单叶子节点 单叶子节点 sup_menu is not null 43 -- sup_menu 是否为null 44 declare _sup_menu int default -1; 45  46 -- 定义游标 47 declare idCur cursor for select menu_id from Menu where  FIND_IN_SET(auth_id,roleIds) ;  48 -- 定义 设置循环结束标识done值怎么改变 的逻辑  49 declare continue handler for not FOUND set done = 1; 50  51 OPEN idCur ;  52 FETCH idCur INTO menuid; 53 -- 临时变量存储menu_id集合 54 SET set_menu_ids = ''; 55  56 WHILE done<> 1 DO  57 SELECT  sup_menu  58 INTO _sup_menu 59 FROM Menu  60 WHERE FIND_IN_SET(menu_id,getParentList(menuid))  61 ORDER BY sup_menu ASC 62 LIMIT 1; 63  64 -- 查找指定角色对应的menu_id ,sup_menu is null 则说明有根,则进行拼接 65 IF _sup_menu is NULL THEN 66 SELECT  CONCAT(set_menu_ids, GROUP_CONCAT(menu_id),',') INTO set_menu_ids  67 FROM Menu 68 where FIND_IN_SET(menu_id,getParentList(menuid)) ; 69 END IF; 70  71 FETCH idCur INTO menuid; 72 END WHILE; 73 CLOSE  idCur;  74  75 -- 返回指定menu_id 的数据集合 76 SELECT Menu.menu_id,Menu.sup_menu,Menu.auth_id  77 FROM Menu  78 WHERE FIND_IN_SET(menu_id,set_menu_ids) 79 ORDER BY Menu.menu_id*1 ASC  ; 80     81 END ; 82 // 83 DELIMITER ; 84  85 CALL  select_menu_by_authids('5,15,25,26'); 86 CALL  select_menu_by_authids('5,17'); 87 CALL  select_menu_by_authids('5,11');

方法三:纯函数实现

 1 -- 根据叶子节点查找所有父节点及其本身节点。如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示.  2 DROP FUNCTION  IF EXISTS `getParentLists`;  3 -- 参数1角色id 字符串逗号隔开; 参数2 角色id 个数  4 CREATE FUNCTION `getParentLists`(in_roleIds varchar(1000),count_roleIds INT)  5 RETURNS VARCHAR(3000)   6 BEGIN  7     -- 临时存放通过单个角色查找的单个menu_id  8         DECLARE sMenu_id_by_roleId VARCHAR(1000);   9     -- 临时存放通过单个角色查找的多个menu_id 10     DECLARE sMenu_ids_by_roleId VARCHAR(1000); 11         -- 临时存放通过多个角色查找的多个menu_id 12     DECLARE sMenu_ids_by_roleIds VARCHAR(1000); 13         -- 函数返回的menu_id 集合 14         DECLARE sReturn_menu_ids VARCHAR(3000); 15         -- 当前角色 16     DECLARE current_roleId_rows INT DEFAULT 0; 17          18         SET sMenu_id_by_roleId = ''; 19     SET sMenu_ids_by_roleIds = '';  20         SET sReturn_menu_ids = '';  21     22          -- 循环多角色 23         WHILE current_roleId_rows < count_roleIds DO 24  25                 -- 依次按角色取1条menu_id  26                 SELECT menu_id  27                 INTO sMenu_id_by_roleId   28                 FROM Menu  29                 WHERE FIND_IN_SET(auth_id, in_roleIds)  30                 ORDER BY menu_id DESC  31                 LIMIT current_roleId_rows, 1 ;  32              33                 SET sMenu_ids_by_roleId = sMenu_id_by_roleId;  34         WHILE sMenu_ids_by_roleId IS NOT NULL DO  35          36                         -- 判断是否是第一个,不加的话第一个会为空 37                         IF sMenu_ids_by_roleIds != ''  THEN 38                                 SET sMenu_ids_by_roleIds = CONCAT(sMenu_ids_by_roleIds,',',sMenu_ids_by_roleId); 39                         ELSE 40                                 SET sMenu_ids_by_roleIds = sMenu_ids_by_roleId; 41                         END IF; 42                          43                         -- 通过角色id 拼接 所有的父节点,重点拼接根节点,根节点置为字符NULL,用于后面判断是否有根             44                         SELECT  45                         GROUP_CONCAT( 46                         CASE   47                         WHEN sup_menu IS NULL THEN  'NULL' 48                         ELSE sup_menu 49                         END 50                         )  51                         INTO sMenu_ids_by_roleId  52                         FROM Menu  53                         WHERE FIND_IN_SET(menu_id,sMenu_ids_by_roleId) > 0;  54                          55        END WHILE;  56              SET current_roleId_rows=current_roleId_rows+1;     57               58              -- 满足必须要有根节点NULL字符,则表明有根,否则不拼接给返回值 59              IF  FIND_IN_SET('NULL',sMenu_ids_by_roleIds) > 0 THEN 60                          SET sReturn_menu_ids = CONCAT(sReturn_menu_ids,',',sMenu_ids_by_roleIds); 61              END IF; 62               63              -- 清空通过单个角色查到的多个menu_id, 避免重复拼接 64              SET sMenu_ids_by_roleIds = '';     65    END WHILE; 66          67    RETURN sReturn_menu_ids;  68 END; 69  70 SELECT Menu.menu_id,Menu.sup_menu,Menu.auth_id  71 FROM Menu  72 WHERE FIND_IN_SET(menu_id, getParentLists('15,25,5,26',4)) 73 ORDER BY Menu.menu_id+0 ASC; 74  75 SELECT Menu.menu_id,Menu.sup_menu,Menu.auth_id  76 FROM Menu  77 WHERE FIND_IN_SET(menu_id, getParentLists('17,5',2)) 78 ORDER BY Menu.menu_id*1 ASC; 79  80 SELECT Menu.menu_id,Menu.sup_menu,Menu.auth_id  81 FROM Menu  82 WHERE FIND_IN_SET(menu_id, getParentLists('11,5',2)) 83 ORDER BY Menu.menu_id*2 ASC;

 欢迎大家提出更优解决方案。谢谢。

发表评论

评论已关闭。

相关文章

当前内容话题
  • 0