经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MySQL » 查看文章
Mysql 实现向上递归查找父节点并返回树结构的示例代码
来源:jb51  时间:2022/9/15 9:09:16  对本文有异议

通过mysql 8.0以下版本实现,一个人多角色id,一个角色对应某个节点menu_id,根节点的父节点存储为NULL, 向上递归查找父节点并返回树结构。如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示叶子与根。如果 传入角色ID 5,15,25,26,则只查找5,15的所有父节点,因为25,26无根节点

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

如果只有叶子,剔除掉; 如果只有根,只显示一个秃顶的根 ;如果既有叶子又有根则显示叶子与根。测试数据:

如果 传入角色ID【auth_id】: 5,15,25,26,则只查找5,15的所有父节点,因为25,26无根节点

测试数据:

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for Menu
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `Menu`;
  7. CREATE TABLE `Menu` (
  8. `menu_id` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '0',
  9. `sup_menu` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  10. `auth_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  11. PRIMARY KEY (`menu_id`) USING BTREE
  12. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
  13. -- ----------------------------
  14. -- Records of Menu
  15. -- ----------------------------
  16. BEGIN;
  17. INSERT INTO `Menu` VALUES ('1', NULL, '1');
  18. INSERT INTO `Menu` VALUES ('11', NULL, '11');
  19. INSERT INTO `Menu` VALUES ('12', '11', '12');
  20. INSERT INTO `Menu` VALUES ('13', '11', '13');
  21. INSERT INTO `Menu` VALUES ('14', '12', '14');
  22. INSERT INTO `Menu` VALUES ('15', '12', '15');
  23. INSERT INTO `Menu` VALUES ('16', '13', '16');
  24. INSERT INTO `Menu` VALUES ('17', '13', '17');
  25. INSERT INTO `Menu` VALUES ('2', '1', '2');
  26. INSERT INTO `Menu` VALUES ('22', '21', '26');
  27. INSERT INTO `Menu` VALUES ('25', '22', '25');
  28. INSERT INTO `Menu` VALUES ('3', '1', '3');
  29. INSERT INTO `Menu` VALUES ('4', '2', '4');
  30. INSERT INTO `Menu` VALUES ('5', '2', '5');
  31. INSERT INTO `Menu` VALUES ('6', '3', '6');
  32. INSERT INTO `Menu` VALUES ('7', '3', '7');
  33. COMMIT;
  34. 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. -- 依次按角色取1menu_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;

到此这篇关于Mysql 实现 向上递归查找父节点并返回树结构的文章就介绍到这了,更多相关Mysql递归查找父节点内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号