服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - Mysql - 在MySQL中,如何轻松找到所有子节点?

在MySQL中,如何轻松找到所有子节点?

2023-11-04 01:02未知服务器之家 Mysql

背景 项目中遇到一个需求,要求查出菜单节点的所有节点,在网上查了一下,大多数的方法用到了存储过程,由于线上环境不能随便添加存储过程。 因此在这里采用类似递归的方法对组织下的所有子节点进行查询。 准备 创建组织

背景

项目中遇到一个需求,要求查出菜单节点的所有节点,在网上查了一下,大多数的方法用到了存储过程,由于线上环境不能随便添加存储过程。

因此在这里采用类似递归的方法对组织下的所有子节点进行查询。

准备

创建组织表:

CREATE TABLE groups (
  `group_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '组织ID',
  `parent_id` int(11) DEFAULT NULL COMMENT '父节点ID',
  `group_name` varchar(128) DEFAULT NULL COMMENT '组织名称',
  PRIMARY KEY (`group_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

写入数据:

INSERT INTO groups VALUES (0, null, '系统管理组织');
INSERT INTO groups VALUES (1, 0, '中国电信股份有限公司');
INSERT INTO groups VALUES (2, 1, '万州分公司');
INSERT INTO groups VALUES (3, 1, '涪陵分公司');
INSERT INTO groups VALUES (4, 2, '龙都支局');
INSERT INTO groups VALUES (5, 2, '新田支局');
INSERT INTO groups VALUES (6, 3, '马武支局');
INSERT INTO groups VALUES (7, 3, '南沱支局');
INSERT INTO groups VALUES (8, 4, '党群工作部');
INSERT INTO groups VALUES (9, 5, '客户服务部');
INSERT INTO groups VALUES (10, 6, '采购和供应链管理事业部');
INSERT INTO groups VALUES (11, 7, '网络和信息安全管理部');

树状结构:

- 系统管理组织
  - 中国电信股份有限公司
    - 万州分公司
      - 龙都支局
        - 党群工作部
      - 新田支局
        - 客户服务部
    - 涪陵分公司
      - 马武支局
        - 采购和供应链管理事业部
      - 南沱支局
        - 网络和信息安全管理部

实现

查询

select
 group_id,group_name
from
 (
 select
  t1.group_id,
  t1.parent_id,
  t1.group_name,
  t2.pids,
  if(find_in_set(parent_id, @pids) > 0,@pids := concat(@pids, ',', group_id),0) as ischild
 from
  (select group_id,parent_id,group_name from `groups` ) t1,
  (select @pids := #{groupId} as pids) t2) t3
 where
  ischild != 0;

比如,要查询的万州分公司下所有子节点,只需将#{groupId}变更为万州分公司的组织ID即可:

group_id|group_name|
--------+----------+
       4|龙都支局      |
       5|新田支局      |
       8|党群工作部     |
       9|客户服务部     |

语句解析

  • t1:该子查询从groups表中选择group_id,parent_id,group_name
group_id|parent_id|group_name |
--------+---------+-----------+
       0|         |系统管理组织     |
       1|        0|中国电信股份有限公司 |
       2|        1|万州分公司      |
       3|        1|涪陵分公司      |
       4|        2|龙都支局       |
       5|        2|新田支局       |
       6|        3|马武支局       |
       7|        3|南沱支局       |
       8|        4|党群工作部      |
       9|        5|客户服务部      |
      10|        6|采购和供应链管理事业部|
      11|        7|网络和信息安全管理部 |
  • t2:该子查询初始化一个用户定义变量@pids,并为其赋予一个名为groupId
pids|
----+
   2|
  • if(find_in_set(parent_id, @pids) > 0,@pids := concat(@pids, ',', group_id),0):这一部分使用find_in_set函数检查parent_id是否存在于@pids变量中。如果存在,则将当前group_id添加到@pids变量并返回;否则返回0
group_id|parent_id|group_name |pids|ischild  |
--------+---------+-----------+----+---------+
       0|         |系统管理组织     |   2|0        |
       1|        0|中国电信股份有限公司 |   2|0        |
       2|        1|万州分公司      |   2|0        |
       3|        1|涪陵分公司      |   2|0        |
       4|        2|龙都支局       |   2|2,4      |
       5|        2|新田支局       |   2|2,4,5    |
       6|        3|马武支局       |   2|0        |
       7|        3|南沱支局       |   2|0        |
       8|        4|党群工作部      |   2|2,4,5,8  |
       9|        5|客户服务部      |   2|2,4,5,8,9|
      10|        6|采购和供应链管理事业部|   2|0        |
      11|        7|网络和信息安全管理部 |   2|0        |
  • 使用where子句过滤结果,只包括那些ischild不等于0的行
group_id|group_name|
--------+----------+
       4|龙都支局      |
       5|新田支局      |
       8|党群工作部     |
       9|客户服务部     |

MySQL 8.0版本

引入了通用表表达式(CTE),可以使用CTE来进行递归查询

WITH RECURSIVE subordinates AS (
    SELECT group_id, group_name, parent_id
    FROM groups
    WHERE parent_id = 2  -- 指定父节点ID
    
    UNION ALL
    
    SELECT g.group_id, g.group_name, g.parent_id
    FROM groups g
    INNER JOIN subordinates s ON s.group_id = g.parent_id
)
SELECT * FROM subordinates;
  • 使用了WITH RECURSIVE子句,它创建了一个名为subordinates的递归公共表达式(CTE)
  • 从groups表中选择group_id,group_name和parent_id字段,其中parent_id = 2,也就是选择parent_id=2直接子组
  • 将groups表(别名为'g')与subordinates(别名为's')进行内连接。连接条件是'g'的parent_id等于's'的group_id。这意味着我们正在查找先前找到的每个子组的子组
  • 从subordinates中选择所有行
group_id|group_name|parent_id|
--------+----------+---------+
       4|龙都支局      |        2|
       5|新田支局      |        2|
       8|党群工作部     |        4|
       9|客户服务部     |        5|

代码递归

@Test
    public void test1() {
        List<Map<String, Object>> groupList = new ArrayList<>();
        groupList = queryListParentId(2,groupList);
        System.out.println(groupList);

        groupList.clear();
        System.out.println("=====================");

        List<String>list = new ArrayList<>();
        list.add("3");
        groupList = queryListParentId2(list,groupList);
        System.out.println(groupList);
    }
    
    //方式一,循环遍历查询
    public List<Map<String, Object>> queryListParentId(Integer parentId,List<Map<String, Object>> groupList) {
        String sql = "select group_id,group_name from groups where parent_id = "+ parentId;
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        if(!CollectionUtils.isEmpty(list)){
            groupList.addAll(list);
            for (Map<String, Object> map : list){
                queryListParentId((Integer) map.get("group_id"),groupList);
            }
        }
        return groupList;
    }
    
    //方式二,使用find_in_set函数
    public List<Map<String, Object>> queryListParentId2(List<String> parentId,List<Map<String, Object>> groupList) {
    String join = String.join(",", parentId);
    String sql = "select group_id,group_name from groups where find_in_set(parent_id,'"+ join+"')";
    List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
    if(!CollectionUtils.isEmpty(list)){
        groupList.addAll(list);
        List<String> collect = list.stream().map(map -> map.get("group_id")+"").collect(Collectors.toList());
        queryListParentId2(collect,groupList);
    }
    return groupList;
}
[{group_id=4, group_name=龙都支局}, {group_id=5, group_name=新田支局}, {group_id=8, group_name=党群工作部}, {group_id=9, group_name=客户服务部}]
=====================
[{group_id=6, group_name=马武支局}, {group_id=7, group_name=南沱支局}, {group_id=10, group_name=采购和供应链管理事业部}, {group_id=11, group_name=网络和信息安全管理部}]

延伸 · 阅读

精彩推荐
  • Mysql浅谈mysql 树形结构表设计与优化

    浅谈mysql 树形结构表设计与优化

    在诸多的管理类,办公类等系统中,树形结构展示随处可见,本文主要介绍了mysql 树形结构表设计与优化,具有一定的参考价值,感兴趣的小伙伴们可以参...

    小码农叔叔5242021-11-16
  • Mysql解决MySQl查询不区分大小写的方法讲解

    解决MySQl查询不区分大小写的方法讲解

    今天小编就为大家分享一篇关于解决MySQl查询不区分大小写的方法讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起...

    Veir_dev5592019-06-25
  • MysqlMySQL 数据备份与还原的示例代码

    MySQL 数据备份与还原的示例代码

    这篇文章主要介绍了MySQL 数据备份与还原的相关知识,本文通过示例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    逆心2972019-06-23
  • MysqlMySQL锁的知识点总结

    MySQL锁的知识点总结

    在本篇文章里小编给大家整理了关于MySQL锁的知识点总结以及实例内容,需要的朋友们学习下。...

    别人放弃我坚持吖4362020-12-14
  • MysqlMySQL数据库varchar的限制规则说明

    MySQL数据库varchar的限制规则说明

    本文我们主要介绍了MySQL数据库中varchar的限制规则,并以一个实际的例子对限制规则进行了说明,希望能够对您有所帮助。 ...

    mysql技术网4192019-11-23
  • Mysqlmysql 不能插入中文问题

    mysql 不能插入中文问题

    当向mysql5.5插入中文时,会出现类似错误 ERROR 1366 (HY000): Incorrect string value: '\xD6\xD0\xCE\xC4' for column ...

    MYSQL教程网5722019-11-25
  • Mysql详解MySQL中的分组查询与连接查询语句

    详解MySQL中的分组查询与连接查询语句

    这篇文章主要介绍了MySQL中的分组查询与连接查询语句,同时还介绍了一些统计函数的用法,需要的朋友可以参考下 ...

    GALAXY_ZMY5442020-06-03
  • MysqlERROR: Error in Log_event::read_log_event()

    ERROR: Error in Log_event::read_log_event()

    ERROR: Error in Log_event::read_log_event(): read error, data_len: 438, event_type: 2 ...

    MYSQL教程网6412020-03-13