MySQL树状结构表查询通解

这篇具有很好参考价值的文章主要介绍了MySQL树状结构表查询通解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

​ 最近做了一个中医药方面的项目,该项目分为游戏端和服务端。笔者负责的是服务端的开发。在服务端的业务中包含两部分:系统信息管理模块、游戏端服务提供模块。由于中医药存在很多树状结构信息,因此在设计数据表时为了减少冗余度,就将很多数据表设计为了树状结构。树状结构的表能够更加有效的将数据进行管理,但在某些业务中存在查询某个节点所有子节点后父节点的需求,进而造成了查询效率低下。并且对于不同数据表而言,其字段均不相同,代码并不能复用。使得在开发过程中,存在大量重复性工作。笔者想,既然数据表都存在树状结构是否能写一个东西,能够对所有树状结构的数据表都能适用,并提高其查询效率。经过构思找到了一个实现思路。

​ 在很多项目中都存在树状结构的数据库,以表示数据间的关联和层次关系。这种数据库能够高效的描述数据间的关系,而且可以无限延申树状结构的深度。该树状结构的思想能够在数据库层面高效的解决数据存储问题,但在业务处理层面并不能高效的解决节点间父子节点的关系。因此,产生了数据库树状查询问题。对于不同的数据库有不同的解决方式:

  • Oracle数据库中存在树查询语句,可以直接查询出某个节点的所有父节点或子节点。

  • MySQL数据库中并没有Oracle数据库中的树查询语句,其解决思路大致可分为两种:

    • 方法一:将数据库中所有的数据一次性全部查询出来,而后在代码层面获取节点的父、子节点
    • 方法二:在代码层面以某个节点的唯一标识在代码层面进行递归,每次递归从数据库中查询树父、子节点

    在MySQL中方法二由于与数据库进行了多次交互,而与数据库的交互所花费的时间要远远大于代码层面所花费的时间,方法一与数据库只进行一次交互,因此该方法效率比方法二高。

​ 笔者基于方法一对该方法提高了该方法的复用性。方法一在解决树状查询时,通常是仅仅针对特定的一张表而言的,无法通用的解决MySQL树状查询问题,本方法在其基础上通用的解决了MySQL树状查询问题。即是凡是存在树状结构的数据表均可使用本方式进行树状查询。


一、数据准备

CREATE table arborescence (
id int PRIMARY KEY,
parent_id int,
content VARCHAR(200)
);
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (1, NULL, '节点1');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (2, 1, '节点2');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (3, 1, '节点3');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (4, 2, '节点4');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (5, 2, '节点5');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (6, 2, '节点6');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (7, 3, '节点7');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (8, 3, '节点8');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (9, 3, '节点9');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (10, 4, '节点10');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (11, 6, '节点11');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (12, 7, '节点12');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (13, 8, '节点13');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (14, 8, '节点14');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (15, 9, '节点15');
INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (16, 15, '节点16');

在该数据表中id为每个数据记录的唯一标识,parent_id为每个数据记录的父级记录。

以上数据的树状结构关系如图:

MySQL树状结构表查询通解


二、代码实现

在本方法中主要由两个类实现。

public class Tree <T>{
    T node;//当前节点
    List<Tree<T>> nextNode;//子节点

    public Tree(T node, List<Tree<T>> nextNode) {//有参构造
        this.node = node;
        this.nextNode = nextNode;
    }

    public Tree() {//无参构造
    }

    public Tree(T node) {
        this.node = node;
        this.nextNode=new ArrayList<>();
    }

    public T getNode() {
        return node;
    }

    public void setNode(T node) {
        this.node = node;
    }

    public List<Tree<T>> getNextNode() {
        return nextNode;
    }

    public void setNextNode(List<Tree<T>> nextNode) {
        this.nextNode = nextNode;
    }
}

​ Tree<T>类与常用的树状节点功能相同,记录当前节点及其直接子节点。主要关键点是使用到了泛型,从而保证了本方法的通用性。

public class TreeUtil <T>{
    public List<T> sonList,fatherList;//所有子节点、所有父节点
    public Tree<T> tree;//树

    public List<T> getSonList() {
        return sonList;
    }

    public void setSonList(List<T> sonList) {
        this.sonList = sonList;
    }

    public List<T> getFatherList() {
        return fatherList;
    }

    public void setFatherList(List<T> fatherList) {
        this.fatherList = fatherList;
    }

    public Tree<T> getTree() {
        return tree;
    }

    public void setTree(Tree<T> tree) {
        this.tree = tree;
    }

    public TreeUtil() {
        this.sonList = new ArrayList<>();
        this.fatherList=new ArrayList<>();
        this.tree=new Tree<>();
    }

    /**
     * 根据制定字段建立树和对应后代节点集合
     * @param list 所有节点
     * @param head 对象类型
     * @param fatherFiled 父节点字段
     * @param sonFiled 当前节点字段
     * @param start 起始字段的值
     */
    public void buildListAndTree(List<T> list,Class head,String fatherFiled,String sonFiled,String start) {//根据某个节点建树和集合
        Map<String,T> sonMap=new HashMap<>();//以自身唯一标识建map
        Map<String,List<T>> fatherMap=new HashMap<>();//以父节点唯一标识建map
        for (T temp:list) {
            try {
                Field field1 = head.getDeclaredField(sonFiled);//自身唯一标识
                field1.setAccessible(true);
                Object o1 = field1.get(temp);
                sonMap.put(o1.toString(),temp);
                Field field2 = head.getDeclaredField(fatherFiled);//父节点唯一标识
                field2.setAccessible(true);
                Object o2 = field2.get(temp);
                if (o2==null) continue;//该节点为根节点不存在父节点
                if (fatherMap.containsKey(o2.toString())) {//已经含有该父节点
                    List<T> ts = fatherMap.get(o2.toString());
                    ts.add(temp);
                }else {//不含该父节点
                    List<T> tempList=new ArrayList<>();
                    tempList.add(temp);
                    fatherMap.put(o2.toString(),tempList);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (sonMap.containsKey(start)) {
            T startNode = sonMap.get(start);//起始节点
            this.sonList = buildSonList(fatherMap,startNode,sonFiled,head);//建子节点
            this.fatherList=buildFatherList(sonMap,startNode,head,fatherFiled);//建父节点
            this.tree=buildTree(fatherMap,startNode,head,sonFiled);//建树
        }
    }

    /**
     * 根据指定节点获得该节点的所有子节点
     * @param fatherMap 父节点唯一标识
     * @param startNode 起始节点
     * @param sonFiled 当前节点唯一标识属性
     * @param head 类
     * @return
     */
    private List<T> buildSonList(Map<String,List<T>> fatherMap, T startNode, String sonFiled, Class head){//建集合
        List<T> result=new ArrayList<>();
        Queue<T> queue=new LinkedList<>();
        queue.add(startNode);//队列
        while (!queue.isEmpty()) {
            T curNode = queue.poll();
            try {
                Field field = head.getDeclaredField(sonFiled);
                field.setAccessible(true);
                Object o = field.get(curNode);
                if (fatherMap.containsKey(o.toString())) {
                    List<T> sons = fatherMap.get(o.toString());//当前节点所有子节点
                    queue.addAll(sons);
                }
                result.add(curNode);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 根据起始节点获得所有父节点
     * @param sonMap 节点集
     * @param startNode 起始节点
     * @param head 类
     * @param fatherFiled 父节点唯一标识属性
     * @return
     */
    private List<T> buildFatherList(Map<String,T> sonMap,T startNode,Class head,String fatherFiled){
        List<T> result=new ArrayList<>();
        try {
            Field field = head.getDeclaredField(fatherFiled);
            field.setAccessible(true);
            while (field.get(startNode)!=null) {
                result.add(startNode);
                startNode=sonMap.get(field.get(startNode));
            }
            result.add(startNode);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Collections.reverse(result);
        return result;
    }

    /**
     * 根据起始节点复现树状结构
     * @param fatherMap 父节点集
     * @param startNode 起始节点
     * @param head 类
     * @param sonFiled 当前节点唯一标识属性
     * @return
     */
    private Tree<T> buildTree(Map<String,List<T>> fatherMap,T startNode, Class head,String sonFiled){//建树
        try {
            Field field = head.getDeclaredField(sonFiled);//当前节点唯一标识
            field.setAccessible(true);
            Object o = field.get(startNode);
            if (fatherMap.containsKey(o.toString())) {//存在子节点
                List<T> sons = fatherMap.get(o.toString());
                List<Tree<T>> treeSon=new ArrayList<>();
                for (T son:sons) {
                    Tree<T> temp = buildTree(fatherMap, son, head, sonFiled);
                    treeSon.add(temp);
                }
                Tree<T> root=new Tree<>(startNode,treeSon);
                return root;
            }else {//不存在子节点
                return new Tree<T>(startNode,null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

​ TreeUtil<T>类是本方法功能的主要实现类,在使用时只需调用buildListAndTree()方法,需传入5个参数。

  • 第一个参数:数据表中所有有效节点数据
  • 第二个参数:数据表实体类的Class类
  • 第三个参数:实体类中表示父节点字段的属性名(即是上面数据表中parent_id、后续实体类中parentId属性)
  • 第四个参数:实体类中表示当前节点唯一标识字段的属性名(即是上面数据表中id、后续实体类中id属性)
  • 第五个参数:起始节点的唯一标识(即是起始节点的id值)
public void buildListAndTree(List<T> list,Class head,String fatherFiled,String sonFiled,String start) {//根据某个节点建树和集合
        Map<String,T> sonMap=new HashMap<>();//以自身唯一标识建map
        Map<String,List<T>> fatherMap=new HashMap<>();//以父节点唯一标识建map
        for (T temp:list) {
            try {
                Field field1 = head.getDeclaredField(sonFiled);//自身唯一标识
                field1.setAccessible(true);
                Object o1 = field1.get(temp);
                sonMap.put(o1.toString(),temp);
                Field field2 = head.getDeclaredField(fatherFiled);//父节点唯一标识
                field2.setAccessible(true);
                Object o2 = field2.get(temp);
                if (o2==null) continue;//该节点为根节点不存在父节点
                if (fatherMap.containsKey(o2.toString())) {//已经含有该父节点
                    List<T> ts = fatherMap.get(o2.toString());
                    ts.add(temp);
                }else {//不含该父节点
                    List<T> tempList=new ArrayList<>();
                    tempList.add(temp);
                    fatherMap.put(o2.toString(),tempList);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (sonMap.containsKey(start)) {
            T startNode = sonMap.get(start);//起始节点
            this.sonList = buildSonList(fatherMap,startNode,sonFiled,head);//建子节点
            this.fatherList=buildFatherList(sonMap,startNode,head,fatherFiled);//建父节点
            this.tree=buildTree(fatherMap,startNode,head,sonFiled);//建树
        }
    }

三、案例使用

以上面数据库中数据为例,所使用的时mybatis-plus框架:

1. 建立数据表实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyNode {
    private Integer id;
    private Integer parentId;
    private String content;
}

2. mapper文件

@Mapper
public interface MyNodeMapper extends BaseMapper<MyNode> {
}

3. 使用

@SpringBootTest
class DemoApplicationTests {
    @Resource
    public MyNodeMapper myNodeMapper;

    @Test
    void contextLoads() {
        List<MyNode> list = myNodeMapper.selectList(null);//查询出数据表中所有有效数据
        TreeUtil<MyNode> util1=new TreeUtil<>();
        util1.buildListAndTree(list,MyNode.class,"parentId","id","1");//以节点唯一标识为1的节点为起始节点
        System.out.println("------------------------以1为起始节点------------------------");
        System.out.println("************父节点************");
        for (MyNode myNode:util1.getFatherList()) System.out.println(myNode.toString());
        System.out.println("************子节点************");
        for (MyNode myNode:util1.getSonList()) System.out.println(myNode.toString());
        TreeUtil<MyNode> util2=new TreeUtil<>();
        util2.buildListAndTree(list,MyNode.class,"parentId","id","3");//以节点唯一标识为3的节点为起始节点
        System.out.println("------------------------以3为起始节点------------------------");
        System.out.println("************父节点************");
        for (MyNode myNode:util2.getFatherList()) System.out.println(myNode.toString());
        System.out.println("************子节点************");
        for (MyNode myNode:util2.getSonList()) System.out.println(myNode.toString());
        TreeUtil<MyNode> util3=new TreeUtil<>();
        util3.buildListAndTree(list,MyNode.class,"parentId","id","16");//以节点唯一标识为16的节点为起始节点
        System.out.println("------------------------以16为起始节点------------------------");
        System.out.println("************父节点************");
        for (MyNode myNode:util3.getFatherList()) System.out.println(myNode.toString());
        System.out.println("************子节点************");
        for (MyNode myNode:util3.getSonList()) System.out.println(myNode.toString());
    }

}

结果:

------------------------以1为起始节点------------------------
************父节点************
MyNode(id=1, parentId=null, content=节点1)
************子节点************
MyNode(id=1, parentId=null, content=节点1)
MyNode(id=2, parentId=1, content=节点2)
MyNode(id=3, parentId=1, content=节点3)
MyNode(id=4, parentId=2, content=节点4)
MyNode(id=5, parentId=2, content=节点5)
MyNode(id=6, parentId=2, content=节点6)
MyNode(id=7, parentId=3, content=节点7)
MyNode(id=8, parentId=3, content=节点8)
MyNode(id=9, parentId=3, content=节点9)
MyNode(id=10, parentId=4, content=节点10)
MyNode(id=11, parentId=6, content=节点11)
MyNode(id=12, parentId=7, content=节点12)
MyNode(id=13, parentId=8, content=节点13)
MyNode(id=14, parentId=8, content=节点14)
MyNode(id=15, parentId=9, content=节点15)
MyNode(id=16, parentId=15, content=节点16)
------------------------以3为起始节点------------------------
************父节点************
MyNode(id=1, parentId=null, content=节点1)
MyNode(id=3, parentId=1, content=节点3)
************子节点************
MyNode(id=3, parentId=1, content=节点3)
MyNode(id=7, parentId=3, content=节点7)
MyNode(id=8, parentId=3, content=节点8)
MyNode(id=9, parentId=3, content=节点9)
MyNode(id=12, parentId=7, content=节点12)
MyNode(id=13, parentId=8, content=节点13)
MyNode(id=14, parentId=8, content=节点14)
MyNode(id=15, parentId=9, content=节点15)
MyNode(id=16, parentId=15, content=节点16)
------------------------以16为起始节点------------------------
************父节点************
MyNode(id=1, parentId=null, content=节点1)
MyNode(id=3, parentId=1, content=节点3)
MyNode(id=9, parentId=3, content=节点9)
MyNode(id=15, parentId=9, content=节点15)
MyNode(id=16, parentId=15, content=节点16)
************子节点************
MyNode(id=16, parentId=15, content=节点16)

使用本方法后,对于MySQL中所有含有树状结构的表均可直接使用,只需建立对应的实体类,以及明确实体类中表示节点间父子关系的属性名文章来源地址https://www.toymoban.com/news/detail-473545.html

四、总结

  • 本方法所用到的技术点并不是那么高深,所包含的只是Java的基本内容预计基础的数据结构。如:Java中的泛型、反射,数据结构中的队列、多叉树等基础知识。
    • 泛型的使用使得本方法对任何数据表都具有复用性
    • 反射的使用能够根据动态获取实体类对象的指定属性值
    • 多叉树是本方法中构建树状结构的关键数据结构
    • 在构建树状结构时队列的使用使得构建效率得到了提升
  • 本方法虽然可以解决树状查询的问题,但还存在一些问题,如:在处理大量数据时,存在内存溢出问题
  • 除技术上的总结外,其它非编码能力的提升也很大
    • 在实际开发中要善于发现重复性的工作,并对该工作进行抽象,进而得到一个通用性的解
    • 代码能力需要不断的实践,这个实践并不是说不断做大量重复性的工作,要将所学到的东西付诸实际开发中

到了这里,关于MySQL树状结构表查询通解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • three.js实现3d球体树状结构布局——树状结构的实现

        three.js实现3d球体树状结构布局——添加入场、出场、点击放大等动画     three-spritetext: 用来绘制文字。THREE.TextGeometry绘制文字存在模糊问题,而且转动camera时three-spritetext不需要手动处理让文字始终面向camera。     three.meshline: 用来绘制线。THREE.LineBasicMaterial绘制线存

    2024年02月08日
    浏览(53)
  • 数据结构<1>——树状数组

    前言:树状数组能解决的问题线段树一定可以解决。然后关于线段树的内容会在2中讲解。 树状数组,也叫Fenwick Tree和BIT(Binary Indexed Tree),是一种支持 单点修改 和 区间查询 的,代码量小的数据结构。 那神马是单点修改和区间查询?我们来看一道题。 洛谷P3374(模板): 在本题中

    2024年01月25日
    浏览(61)
  • 【高级数据结构】树状数组

    目录 树状数组1 (单点修改,区间查询) 树状数组2(区间修改,单点查询) 树状数组1 (单点修改,区间查询) 题目链接:洛谷 树状数组1 题目描述 如题,已知一个数列,你需要进行下面两种操作: 将某一个数加上 x 求出某区间每一个数的和 输入格式 第一行包含两个正

    2024年02月15日
    浏览(48)
  • django 实现:闭包表—树状结构

    闭包表模型 闭包表(Closure Table)是一种通过空间换时间的模型,它是用一个专门的关系表(其实这也是我们推荐的归一化方式)来记录树上节点之间的层级关系以及距离。 场景 我们 基于 django orm 实现一个文件树,文件夹直接可以实现无限嵌套 models id file_name 1 AAA 2 aaa.pdf

    2024年02月07日
    浏览(44)
  • 【算法 & 高级数据结构】树状数组:一种高效的数据结构(二)

    🚀 个人主页 :为梦而生~ 关注我一起学习吧! 💡 专栏 :算法题、 基础算法、数据结构~赶紧来学算法吧 💡 往期推荐 : 【算法基础 数学】快速幂求逆元(逆元、扩展欧几里得定理、小费马定理) 【算法基础】深搜 数据结构各内部排序算法总结对比及动图演示(插入排序

    2024年03月26日
    浏览(85)
  • 【算法 & 高级数据结构】树状数组:一种高效的数据结构(一)

    🚀 个人主页 :为梦而生~ 关注我一起学习吧! 💡 专栏 :算法题、 基础算法~赶紧来学算法吧 💡 往期推荐 : 【算法基础 数学】快速幂求逆元(逆元、扩展欧几里得定理、小费马定理) 【算法基础】深搜 树状数组 (Binary Indexed Tree,BIT)是一种数据结构,用于高效地处理

    2024年03月11日
    浏览(68)
  • 【算法与数据结构】--前言

    欢迎来到《算法与数据结构》专栏!这个专栏将引领您进入计算机科学领域中最重要、最精彩的领域之一:算法与数据结构。不管您是一名初学者,还是已经拥有一定编程经验的开发者,都可以从这里找到有益的知识和实践。 在计算机科学的世界里,算法和数据结构是至关重

    2024年02月07日
    浏览(246)
  • 数据结构前言

    数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。 上面是百度百科的定义, 通俗的来讲数据结构就是数据元素集合与数据元素集合或者数据元素与数据元素之间的组成形式。 举个简单明了的例子: 就像一个图书馆

    2024年02月09日
    浏览(53)
  • 【数据结构】前言概况 - 树

    🚩 纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:数据结构 🔥该文章针对树形结构作出前言,以保证可以对树初步认知。  线性结构是一种相对简单的数据结构,元素之间按照一定的顺序排列,每个元素最多有两个接口:前驱和后继。这种结构相对直观

    2024年02月07日
    浏览(67)
  • C语言数据结构(0)——前言

    欢迎来到博主的新专栏——C语言与数据结构 博主id:代码小豪 在前两个专栏当中,博主已经大致的讲过了C语言中的大部分使用方法。大家都知道,学习英语时,首先掌握的是单词,随后学习语法,如此才能融会贯通的学习英语。如果学英文只会单词,那么阅读虽然不成问题

    2024年01月17日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包