我的编程空间,编程开发者的网络收藏夹
学习永远不晚

MySQL树状结构表查询通解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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为每个数据记录的父级记录。

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

在这里插入图片描述


二、代码实现

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

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

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<>();    }        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);//建树        }    }        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;    }        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;    }        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类是本方法功能的主要实现类,在使用时只需调用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@AllArgsConstructorpublic class MyNode {    private Integer id;    private Integer parentId;    private String content;}

2. mapper文件

@Mapperpublic interface MyNodeMapper extends BaseMapper<MyNode> {}

3. 使用

@SpringBootTestclass 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中所有含有树状结构的表均可直接使用,只需建立对应的实体类,以及明确实体类中表示节点间父子关系的属性名

四、总结

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

来源地址:https://blog.csdn.net/qq_51763048/article/details/129268313

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

MySQL树状结构表查询通解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

MySQL树状结构表查询通解

文章目录 前言一、数据准备二、代码实现三、案例使用1. 建立数据表实体类2. mapper文件3. 使用 四、总结 前言 ​ 最近做了一个中医药方面的项目,该项目分为游戏端和服务端。笔者负责的是服务端的开发。在服务端的业务中
2023-08-19

MySQL如何实现查询树结构

这篇文章给大家分享的是有关MySQL如何实现查询树结构的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。MySQL 查询树结构1. 关于树结构此类结构的数据,通常需要表结构中含有id 、parentId等自关联字段,
2023-06-22

树形结构的菜单表设计与查询

开发中经常会遇到树形结构的场景,比如:导航菜单、组织机构等等,但凡是有这种父子层级结构的都是如此,一级类目、二级类目、三级类目。。。对于这种树形结构的表要如何设计呢?接下来一起探讨一下首先,想一个问题,用非关系型数据库存储可不可以?答案是肯定可以的,比如用mo
树形结构的菜单表设计与查询
2016-09-24

mysql递归查询树形结构怎么应用

在MySQL中实现递归查询树形结构可以使用递归查询语句(Recursive Query Statement)或者使用存储过程来实现。使用递归查询语句:递归查询语句使用了WITH RECURSIVE子句,可以在语句中递归引用自身。以下是一个使
2023-10-25

MySQL查询树形结构数据的两种方法

目录1. 递归查询2. 闭包表对于mysql查android询树形结构,可以使用递归查询或者闭包表来实现。以下是两种常用的方法:1. 递归查询使用递归查询可以遍历树形结构,获取父节点和子节点的关系。假设有一个名为 your_table
MySQL查询树形结构数据的两种方法
2023-11-11

MySql树形结构(多级菜单)查询设计方案

目录背景三级查询(层级固定,层级数少)多级查询(层级不固定/层级很深)遍历整个树:节点搜索(查找出这个节点所在的整个分支)总结背景又android很久没更新了,很幸运地新冠引发了严重的上呼吸道感染,大家羊过后注意休息和防护工作中(尤其是
2023-03-03

mysql的树形结构存储及查询实例分析

这篇文章主要介绍“mysql的树形结构存储及查询实例分析”,在日常操作中,相信很多人在mysql的树形结构存储及查询实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”mysql的树形结构存储及查询实例分析
2023-06-29

编程热搜

目录