MySQL遞歸查詢的幾種實(shí)現(xiàn)方法
背景
相信大家在平時(shí)開發(fā)的時(shí)候都遇見過以下這種樹形數(shù)據(jù)
這種樹形數(shù)據(jù)如何落庫應(yīng)該這里就不贅述了
核心就是使用額外一個(gè)字段parent_id保存父親節(jié)點(diǎn)的id,如下圖所示
這里的classpath指的是當(dāng)前節(jié)點(diǎn)的路徑,后續(xù)說明其作用
現(xiàn)有需求如下:
1、查詢指定id的分類節(jié)點(diǎn)的所有子節(jié)點(diǎn)2、查詢指定id的分類節(jié)點(diǎn)的所有父節(jié)點(diǎn)3、查詢整棵分類樹,可指定最大層級(jí)
常規(guī)操作
常規(guī)操作就是直接在程序?qū)用婵刂七f歸,下面根據(jù)需求一 一演示代碼。
PS:基礎(chǔ)工程代碼就不演示了,工程項(xiàng)目代碼在評(píng)論區(qū)鏈接中獲取
查詢指定id的分類節(jié)點(diǎn)的所有子節(jié)點(diǎn)
NormalController
/** * 返回指定nodeId的節(jié)點(diǎn)信息,包括所有孩子節(jié)點(diǎn) * @param nodeId * @return */ @GetMapping("/childNodes/{nodeId}") public CategoryVO childNodes(@PathVariable("nodeId") Integer nodeId){ return categoryService.normalChildNodes(nodeId); }
CategoryServiceImpl
@Override public CategoryVO normalChildNodes(Integer nodeId) { // 查詢當(dāng)前節(jié)點(diǎn)信息 Category category = getById(nodeId); return assembleChildren(category); } private CategoryVO assembleChildren(Category category) { // 組裝vo信息 CategoryVO categoryVO = BeanUtil.copyProperties(category, CategoryVO.class); // 如果沒有子節(jié)點(diǎn)了,則退出遞歸 List<Category> children = getChildren(category.getId()); if (children == null || children.isEmpty()) { return categoryVO; } List<CategoryVO> childrenVOs = new ArrayList<>(); for (Category child : children) { // 組裝每一個(gè)孩子節(jié)點(diǎn) CategoryVO cv = assembleChildren(child); // 將其加入到當(dāng)前層的孩子節(jié)點(diǎn)集合中 childrenVOs.add(cv); } categoryVO.setChildren(childrenVOs); return categoryVO; } private List<Category> getChildren(int nodeId) { // 如果不存在父親節(jié)點(diǎn)為nodeId的,則說明nodeId并不存在子節(jié)點(diǎn) return lambdaQuery().eq(Category::getParentId,nodeId).list(); }
查詢id為6的分類信息
查詢指定id的分類節(jié)點(diǎn)的所有父節(jié)點(diǎn)
NormalController
/** * 返回指定nodeId的節(jié)點(diǎn)父級(jí)集合,按照從下到上的順序 * @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); // 找到其所有的父親節(jié)點(diǎn)信息,即根據(jù)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的父級(jí)分類信息
查詢整棵分類樹,可指定最大層級(jí)
NormalController
/** * 返回整棵分類樹,可設(shè)置最大層級(jí) * @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) { // 虛擬根節(jié)點(diǎn) CategoryVO root = new CategoryVO(); root.setId(-1); root.setName("ROOT"); root.setClasspath("/"); // 隊(duì)列,為了控制層級(jí)的 Queue<CategoryVO> queue = new LinkedList<>(); queue.offer(root); int level = 1; while (!queue.isEmpty()) { // 到達(dá)最大層級(jí)了 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; } //得到當(dāng)前層級(jí)的所有孩子節(jié)點(diǎn) List<Category> children = getChildren(poll.getId()); // 有孩子節(jié)點(diǎn) if (children != null && !children.isEmpty()) { List<CategoryVO> childrenVOs = new ArrayList<>(); // 構(gòu)建孩子節(jié)點(diǎn) for (Category child : children) { CategoryVO cv = BeanUtil.copyProperties(child, CategoryVO.class); childrenVOs.add(cv); queue.offer(cv); } // 設(shè)置孩子節(jié)點(diǎn) poll.setChildren(childrenVOs); } } // 層級(jí)自增 level++; } // 返回虛擬節(jié)點(diǎn)的孩子節(jié)點(diǎn) return root.getChildren(); }
查詢整棵分類樹
MySQL8新特性
MySQL8有一個(gè)新特性就是with共用表表達(dá)式,使用這個(gè)特性就可以在MySQL層面實(shí)現(xiàn)遞歸查詢。
我們先來看看從上至下的遞歸查詢的SQL語句,查詢id為1的節(jié)點(diǎn)的所有子節(jié)點(diǎn)
WITH recursive r as ( -- 遞歸基:由此開始遞歸 select id,parent_id,name from category where id = 1 union ALL -- 遞歸步:關(guān)聯(lián)查詢 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
查詢結(jié)果如下圖所示
舉一反三,則查詢id為12的所有父節(jié)點(diǎn)信息的就是從下至上的遞歸查詢,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 -- 因?yàn)槭菑南轮辽系牟椋詂作為子表,r作為父表 where r.parent_id = c.id ) select id,parent_id,name from r
結(jié)果如下圖所示
查詢指定id的分類節(jié)點(diǎn)的所有子節(jié)點(diǎn)
AdvancedController
/** * 返回指定nodeId的節(jié)點(diǎn)信息,包括所有孩子節(jié)點(diǎn) * @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); // 這里一定是第一個(gè),因?yàn)閏ategories集合中的是id為nodeId和其子分類的信息,結(jié)果assemble組裝后,只會(huì)存在一個(gè)根節(jié)點(diǎn) 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) { // 得到自身節(jié)點(diǎn) CategoryVO categoryVO = categoryMap.get(category.getId()); // 得到父親節(jié)點(diǎn) CategoryVO parent = categoryMap.get(category.getParentId()); // 沒有父親節(jié)點(diǎn)(此情況只會(huì)在數(shù)據(jù)庫中最上層節(jié)點(diǎn)的父節(jié)點(diǎn)id不為-1的時(shí)候出現(xiàn)) 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的分類信息
拓展
這里其實(shí)還有另一種利用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的分類節(jié)點(diǎn)的所有父節(jié)點(diǎn)
AdvancedController
/** * 返回指定nodeId的節(jié)點(diǎn)父級(jí)集合,按照從下到上的順序 * @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的所有父級(jí)分類信息
查詢整棵分類樹
AdvancedController
/** * 返回整棵分類樹 * @return */ @GetMapping("/treeCategory") public List<CategoryVO> treeCategory(){ return categoryService.advancedTreeCategory(); }
CategoryServiceImpl
@Override public List<CategoryVO> advancedTreeCategory() { return assemble(list()); }
查詢整棵分類樹
到此這篇關(guān)于MySQL遞歸查詢的幾種實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)MySQL遞歸查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL快速插入大量數(shù)據(jù)的解決方案和代碼示例
在這篇博客中,我們將深入探討如何高效插入大量數(shù)據(jù)到MySQL數(shù)據(jù)庫,無論你是數(shù)據(jù)庫新手還是經(jīng)驗(yàn)豐富的開發(fā)者,這篇文章都將為你提供實(shí)用的解決方案和代碼示例,幫助你解決插入3萬條數(shù)據(jù)需要20多秒的問題,需要的朋友可以參考下2024-08-08Mysql之索引的數(shù)據(jù)結(jié)構(gòu)詳解
索引是存儲(chǔ)引擎用于快速找到數(shù)據(jù)記錄的一種數(shù)據(jù)結(jié)構(gòu),類似于教科書的目錄部分,在MySQL中,索引可以加速數(shù)據(jù)查找,減少磁盤I/O的次數(shù),提高查詢速率,但是,創(chuàng)建和維護(hù)索引需要耗費(fèi)時(shí)間,并且索引需要占磁盤空間,在InnoDB中,索引的實(shí)現(xiàn)基于B+樹結(jié)構(gòu)2024-12-12Windows 10系統(tǒng)下徹底刪除卸載MySQL的方法教程
mysql數(shù)據(jù)庫的重新安裝是一個(gè)麻煩的問題,很難卸除干凈,下面這篇文章主要給大家介紹了關(guān)于在Windows 10系統(tǒng)下徹底刪除卸載MySQL的方法教程,對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-07-07mysql創(chuàng)建存儲(chǔ)過程實(shí)現(xiàn)往數(shù)據(jù)表中新增字段的方法分析
這篇文章主要介紹了mysql創(chuàng)建存儲(chǔ)過程實(shí)現(xiàn)往數(shù)據(jù)表中新增字段的方法,結(jié)合實(shí)例形式對(duì)比分析了通過存儲(chǔ)過程新增字段相關(guān)操作技巧,需要的朋友可以參考下2018-12-12解決mysql與navicat建立連接出現(xiàn)1251錯(cuò)誤
在本篇文章里小編給大家整理了一篇關(guān)于mysql與navicat建立連接出現(xiàn)1251錯(cuò)誤怎么解決的技術(shù)文章,需要的朋友們參考下。2019-08-08