MySQL递归查询的几种实现方法

 更新时间:2024年10月28日 10:57:11   作者:芝麻\n  
本文主要介绍了MySQL递归查询的几种实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

背景

相信大家在平时开发的时候都遇见过以下这种树形数据

在这里插入图片描述

这种树形数据如何落库应该这里就不赘述了

核心就是使用额外一个字段parent_id保存父亲节点的id,如下图所示

在这里插入图片描述

这里的classpath指的是当前节点的路径,后续说明其作用

现有需求如下:
1、查询指定id的分类节点的所有子节点2、查询指定id的分类节点的所有父节点3、查询整棵分类树,可指定最大层级

常规操作

常规操作就是直接在程序层面控制递归,下面根据需求一 一演示代码。

PS:基础工程代码就不演示了,工程项目代码在评论区链接中获取

查询指定id的分类节点的所有子节点

NormalController

    /**
     * 返回指定nodeId的节点信息,包括所有孩子节点
     * @param nodeId
     * @return
     */
    @GetMapping("/childNodes/{nodeId}")
    public CategoryVO childNodes(@PathVariable("nodeId") Integer nodeId){
        return categoryService.normalChildNodes(nodeId);
    }

CategoryServiceImpl

    @Override
    public CategoryVO normalChildNodes(Integer nodeId) {
        // 查询当前节点信息
        Category category = getById(nodeId);
        return assembleChildren(category);
    }
    private CategoryVO assembleChildren(Category category) {
        // 组装vo信息
        CategoryVO categoryVO = BeanUtil.copyProperties(category, CategoryVO.class);
        // 如果没有子节点了,则退出递归
        List<Category> children = getChildren(category.getId());
        if (children == null || children.isEmpty()) {
            return categoryVO;
        }
        List<CategoryVO> childrenVOs = new ArrayList<>();
        for (Category child : children) {
            // 组装每一个孩子节点
            CategoryVO cv = assembleChildren(child);
            // 将其加入到当前层的孩子节点集合中
            childrenVOs.add(cv);
        }
        categoryVO.setChildren(childrenVOs);
        return categoryVO;
    }
    private List<Category> getChildren(int nodeId) {
        // 如果不存在父亲节点为nodeId的,则说明nodeId并不存在子节点
        return lambdaQuery().eq(Category::getParentId,nodeId).list();
    }

查询id为6的分类信息

在这里插入图片描述

查询指定id的分类节点的所有父节点

NormalController

    /**
     * 返回指定nodeId的节点父级集合,按照从下到上的顺序
     * @param nodeId
     * @return
     */
    @GetMapping("/parentNodes/{nodeId}")
    public List<Category> parentNodes(@PathVariable("nodeId") Integer nodeId){
        return categoryService.normalParentNodes(nodeId);
    }

CategoryServiceImpl

    @Override
    public List<Category> normalParentNodes(Integer nodeId) {
        Category category = getById(nodeId);
        // 找到其所有的父亲节点信息,即根据category的parentId一直查,直到查不到
        List<Category> parentCategories = new ArrayList<>();
        Category current = category;
        while (true) {
            Category parent = lambdaQuery().eq(Category::getId, current.getParentId()).one();
            if (parent == null) {
                break;
            }
            parentCategories.add(parent);
            current = parent;
        }
        return parentCategories;
    }

查询id为12的父级分类信息

在这里插入图片描述

查询整棵分类树,可指定最大层级

NormalController

    /**
     * 返回整棵分类树,可设置最大层级
     * @param maxLevel
     * @return
     */
    @GetMapping("/treeCategory")
    public List<CategoryVO> treeCategory(@RequestParam(value = "maxLevel",required = false) Integer maxLevel){
        return categoryService.normalTreeCategory(maxLevel);
    }

CategoryServiceImpl

    @Override
    public List<CategoryVO> normalTreeCategory(Integer maxLevel) {
        // 虚拟根节点
        CategoryVO root = new CategoryVO();
        root.setId(-1);
        root.setName("ROOT");
        root.setClasspath("/");
        // 队列,为了控制层级的
        Queue<CategoryVO> queue = new LinkedList<>();
        queue.offer(root);
        int level = 1;
        while (!queue.isEmpty()) {
            // 到达最大层级了
            if (maxLevel != null && maxLevel == level) {
                break;
            }
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                CategoryVO poll = queue.poll();
                if (poll == null) {
                    continue;
                }
                //得到当前层级的所有孩子节点
                List<Category> children = getChildren(poll.getId());
                // 有孩子节点
                if (children != null && !children.isEmpty()) {
                    List<CategoryVO> childrenVOs = new ArrayList<>();
                    // 构建孩子节点
                    for (Category child : children) {
                        CategoryVO cv = BeanUtil.copyProperties(child, CategoryVO.class);
                        childrenVOs.add(cv);
                        queue.offer(cv);
                    }
                    // 设置孩子节点
                    poll.setChildren(childrenVOs);
                }
            }
            // 层级自增
            level++;
        }
        // 返回虚拟节点的孩子节点
        return root.getChildren();
    }

查询整棵分类树

在这里插入图片描述

在这里插入图片描述

MySQL8新特性

MySQL8有一个新特性就是with共用表表达式,使用这个特性就可以在MySQL层面实现递归查询。

我们先来看看从上至下的递归查询的SQL语句,查询id为1的节点的所有子节点

WITH recursive r as (
	-- 递归基:由此开始递归
	select id,parent_id,name from category where id = 1
	union ALL
	-- 递归步:关联查询
	select c.id,c.parent_id,c.name
	from category c inner join r 
	-- r作为父表,c作为子表,所以查询条件是c的parent_id=r.id
	where r.id = c.parent_id
)
select id,parent_id,name from r

查询结果如下图所示

在这里插入图片描述

举一反三,则查询id为12的所有父节点信息的就是从下至上的递归查询,SQL如下所示

WITH recursive r as (
	-- 递归基:从id为12的开始
	select id,parent_id,name from category where id = 12
	union ALL
	-- 递归步
	select c.id,c.parent_id,c.name
	from category c inner join r 
	-- 因为是从下至上的查,所以c作为子表,r作为父表
	where r.parent_id = c.id
)
select id,parent_id,name from r

结果如下图所示

在这里插入图片描述

查询指定id的分类节点的所有子节点

AdvancedController

    /**
     * 返回指定nodeId的节点信息,包括所有孩子节点
     * @param nodeId
     * @return
     */
    @GetMapping("/childNodes/{nodeId}")
    public CategoryVO childNodes(@PathVariable("nodeId") Integer nodeId){
        return categoryService.advancedChildNodes(nodeId);
    }

CategoryServiceImpl

    @Override
    public CategoryVO advancedChildNodes(Integer nodeId) {
        List<Category> categories = categoryMapper.advancedChildNodes(nodeId);
        List<CategoryVO> assemble = assemble(categories);
        // 这里一定是第一个,因为categories集合中的是id为nodeId和其子分类的信息,结果assemble组装后,只会存在一个根节点
        return assemble.get(0);
    }
    // 组装categories
    private List<CategoryVO> assemble(List<Category> categories){
        // 组装categories
        CategoryVO root = new CategoryVO();
        root.setId(-1);
        root.setChildren(new ArrayList<>());
        Map<Integer,CategoryVO> categoryMap = new HashMap<>();
        categoryMap.put(-1, root);
        for (Category category : categories) {
            CategoryVO categoryVO = BeanUtil.copyProperties(category, CategoryVO.class);
            categoryVO.setChildren(new ArrayList<>());
            categoryMap.put(category.getId(), categoryVO);
        }
        for (Category category : categories) {
            // 得到自身节点
            CategoryVO categoryVO = categoryMap.get(category.getId());
            // 得到父亲节点
            CategoryVO parent = categoryMap.get(category.getParentId());
            // 没有父亲节点(此情况只会在数据库中最上层节点的父节点id不为-1的时候出现)
            if (parent == null) {
                root.getChildren().add(categoryVO);
                continue;
            }
            parent.getChildren().add(categoryVO);
        }
        return root.getChildren();
    }

CategoryMapper

    <select id="advancedChildNodes" resultType="com.example.mysql8recursive.entity.Category">
        WITH recursive r as (select id, parent_id, name,classpath
                             from category
                             where id = #{nodeId}
                             union ALL
                             select c.id, c.parent_id, c.name,c.classpath
                             from category c
                                      inner join r
                             where r.id = c.parent_id)

        select id, parent_id, name, classpath
        from r
    </select>

查询分类id为6的分类信息

在这里插入图片描述

拓展

这里其实还有另一种利用mybatis的collection子查询的写法,一笔带过

    <resultMap id="BaseResultMap" type="com.example.mysql8recursive.entity.Category">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="parentId" column="parent_id"/>
        <result property="classpath" column="classpath"/>
    </resultMap>
    <resultMap id="CategoryVOResultMap" type="com.example.mysql8recursive.vo.CategoryVO" extends="BaseResultMap">
        <collection property="children"
                    column="id"
                    ofType="com.example.mysql8recursive.vo.CategoryVO"
                    javaType="java.util.ArrayList"
                    select="advancedChildNodes"
        >
        </collection>
    </resultMap>

    <select id="advancedChildNodes" resultMap="CategoryVOResultMap">
        select * from category where parent_id = #{id}
    </select>

查询指定id的分类节点的所有父节点

AdvancedController

    /**
     * 返回指定nodeId的节点父级集合,按照从下到上的顺序
     * @param nodeId
     * @return
     */
    @GetMapping("/parentNodes/{nodeId}")
    public List<Category> parentNodes(@PathVariable("nodeId") Integer nodeId){
        return categoryService.advancedParentNodes(nodeId);
    }

CategorySericeImpl

    @Override
    public List<Category> advancedParentNodes(Integer nodeId) {
        return categoryMapper.advancedParentNodes(nodeId);
    }

CategoryMapper

    <select id="advancedParentNodes" resultType="com.example.mysql8recursive.entity.Category">
        WITH recursive r as (select id, parent_id, name, classpath
                             from category
                             where id = #{nodeId}
                             union ALL
                             select c.id, c.parent_id, c.name, c.classpath
                             from category c
                                      inner join r
                             where r.parent_id = c.id)

        select id, parent_id, name, classpath
        from r
    </select>

查询分类id为12的所有父级分类信息

在这里插入图片描述

查询整棵分类树

AdvancedController

    /**
     * 返回整棵分类树
     * @return
     */
    @GetMapping("/treeCategory")
    public List<CategoryVO> treeCategory(){
        return categoryService.advancedTreeCategory();
    }

CategoryServiceImpl

    @Override
    public List<CategoryVO> advancedTreeCategory() {
        return assemble(list());
    }

查询整棵分类树

在这里插入图片描述

在这里插入图片描述

到此这篇关于MySQL递归查询的几种实现方法的文章就介绍到这了,更多相关MySQL递归查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Mysql中LEFT JOIN和JOIN查询区别及原理详解

    Mysql中LEFT JOIN和JOIN查询区别及原理详解

    这篇文章主要介绍了Mysql中LEFT JOIN和JOIN查询区别及原理详解,Nested Loop Join 实际上就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果,需要的朋友可以参考下
    2023-08-08
  • MySQL与Mongo简单的查询实例代码

    MySQL与Mongo简单的查询实例代码

    本文通过一个实例给大家用MySQL和mongodb分别写一个查询,本文图片并茂给大家介绍的非常详细,感兴趣的朋友参考下吧
    2016-10-10
  • MySQL定时任务EVENT事件的使用方法

    MySQL定时任务EVENT事件的使用方法

    本文主要介绍了MySQL定时任务EVENT事件的使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • MySQL中Next-Key Lock底层原理实现

    MySQL中Next-Key Lock底层原理实现

    Next-KeyLock是MySQL InnoDB存储引擎中的一种锁机制,结合记录锁和间隙锁,用于高效并发控制并避免幻读,本文主要介绍了MySQL中Next-Key Lock底层原理实现,感兴趣的可以了解一下
    2025-03-03
  • MySQL中符号@的作用

    MySQL中符号@的作用

    本文主要介绍了MySQL中符号@的作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 浅谈MySQL InnoDB实现MVCC原理

    浅谈MySQL InnoDB实现MVCC原理

    本文主要介绍了MySQL InnoDB实现MVCC原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-03-03
  • php 不能连接数据库 php error Can''t connect to local MySQL server

    php 不能连接数据库 php error Can''t connect to local MySQL server

    php 不能连接数据库 php error Can't connect to local MySQL server through socket '/tmp/mysql.sock'
    2011-05-05
  • MySQL授权命令grant的使用方法小结

    MySQL授权命令grant的使用方法小结

    这篇文章主要介绍了MySQL授权命令grant的使用方法,本文实例,运行于 MySQL 5.0 及以上版本,介绍了MySQL 赋予用户权限命令的简单格式,本文给大家介绍的非常详细,需要的朋友参考下吧
    2021-12-12
  • MYSQL根据分组获取组内多条数据中符合条件的一条(实例详解)

    MYSQL根据分组获取组内多条数据中符合条件的一条(实例详解)

    这篇文章主要介绍了MYSQL根据分组获取组内多条数据中符合条件的一条,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • MySQL 元数据的使用小结

    MySQL 元数据的使用小结

    本文主要介绍了MySQL 元数据的使用,通过INFORMATION_SCHEMA数据库访问,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-08-08

最新评论