概述

在做后台网站(平台/系统)业务开发时,经常遇到层级概念。比如我最近在全权负责(开发+测试+产品)的一款数据产品就有分类的层级概念,层级有3层;另外有数据集、图表、看板、组合看板、数据推送等功能点(概念),这些功能点名称都有层级的概念。

举个例子:创建一个一级分类(最顶级)数据集。背景知识:数据集其实就是多段SQL,SQL里面可以有删表后建表的语句(drop then create table),那可以在这个SQL里面创建一个最基础的表(table),只不过SQL的最后一个子句必须得是查询字句(数据概念体现点)。然后可以再创建一个二级分类的数据集,然后这个数据集的SQL可以使用一级分类数据集SQL里面的表(table),查询这个table,用来做图表。三级分类类推。

上图中,以/形式拼接返回多级分类名称,并给出层级的实现,参考附录。

分类表设计:

create table category (category_id      bigint auto_increment   primary key,category_name    varchar(100)            not null,type             int(1)                     not null comment '1:数据集 2:图表 3:看板',isactive         tinyint(1) default 1    not null comment '逻辑删除',parent_id        bigint                  null comment '父级id'
);

数据准备:

INSERT INTO category (category_id, category_name, type, isactive, parent_id) VALUES (869, '图表分类A', 2, 1, 898);
INSERT INTO category (category_id, category_name, type, isactive, parent_id) VALUES (882, '图表分类B', 2, 1, 869);
INSERT INTO category (category_id, category_name, type, isactive, parent_id) VALUES (888, '图表分类1', 2, 1, 898);
INSERT INTO category (category_id, category_name, type, isactive, parent_id) VALUES (898, '图表分类', 2, 1, null);

图表的表设计:

create table widget (widget_id         bigint auto_increment primary key,widget_name       varchar(100)          not null comment '图表名称',category_id       bigint                not null comment '分类id',isactive          tinyint(1) default 1  not null comment '逻辑删除字段'
);

问题

如何选择一级分类时,查询下面的二级以及三级分类呢?具体来说,查询条件里面指定图表的一级分类ID,如何查询其下的二级和三级分类的图表?即所谓的MySQL级联(父子)查询。

实现

在构思实现方案前先Google一下,发现级联查询有两种情况:自下向上和自上向下。

自下向上

MySQL实现方案一

即:给定子级查询父级。每个子级肯定只有一个父级,实现起来相对容易些。这里直接给出实现SQL:

SELECT category_id
FROM (SELECT @r           AS _id,(SELECT @r := parent_idFROM categoryWHERE category_id = _id)            AS parent_id,@l := @l + 1 AS lvlFROM (SELECT @r := 893, @l := 0) vars, -- 替换为#{childId}category hWHERE @r <> 0) T1JOIN category T2 ON T1._id = T2.category_id;

MySQL实现方案二

WITH RECURSIVE ... AS语法,MySQL 8.x 新增特性,递归查询,即临时表。注意一定得使用MySQL 8以上版本,否则会执行报错:

[42000][1064] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'recursive t(category_id, parentId) as ( select category_id, parent_id ' at line 1

这里直接给出SQL:

with recursive t(category_id, parentId) as (select category_id, parent_idfrom categorywhere category_id = 893 -- 替换为#{childId}union allselect category.category_id, category.parent_idfrom categoryjoin t on category.category_id = t.parentId)
select category.category_id, category.parent_id
from categoryjoin t on category.category_id = t.category_id;

可参考

  • MySQL系列之MySQL8.0新特性
  • https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/with.html

Java实现

很简单,代码略。

自上向下

即:给定父级查询全部子级。由于父级含有若干个子级,每个子级又有若干个子级,即形成一颗的概念。可通过MySQL或Java代码来实现:

MySQL实现方案一

select category_id
from (select t1.category_id,t1.parent_id,if(find_in_set(parent_id, @parentId) > 0, @parentId := concat(@parentId, ',', category_id), 0) as ischildfrom (select category_id, parent_idfrom category twhere isactive = 1) t1,(select @parentId := 899) t2 -- 替换为#{parentId}) t3
where ischild != 0;

MySQL实现方案二

使用WITH RECURSIVE ... AS语法,需要使用MySQL 8以上版本,直接给出查询SQL:

WITH RECURSIVE category_paths AS(SELECT category_id,parent_id,category_name,1 level -- 层级FROM categoryWHERE category.parent_id IS NULLand category_id = 88 -- 替换为#{parentId}UNION ALLSELECT e.category_id,e.parent_id,e.category_name,level + 1FROM category eINNER JOIN category_paths ep ON ep.category_id = e.parent_id)
SELECT category_id,parent_id,level,category_name
FROM category_paths
ORDER BY level;

查询结果:

Java实现

实体类定义:

@Data
public class Category {private Long categoryId;private String categoryName;private Integer type;private Boolean isactive;private Long parentId;/*** 非DB字段,表示第几级*/private Integer level;
}

CategoryServiceImpl.java实现类,由于最多只有3级,故而可以两层for循环嵌套实现,参考下面的附录,业务代码在保存分类时有个数限制。因此for循环嵌套情况下,性能绝对不是问题:

/*** 根据分类ID查询子级分类ID** @param categoryId 分类ID* @return 列表形式*/
public List<Long> getChildIds(Long categoryId) {List<Category> categoryList = categoryMapper.getCategoryListByParentId(categoryId);if (CollectionUtils.isEmpty(categoryList)) {return Lists.newArrayList(categoryId);}List<Long> result = Lists.newArrayList(categoryId);for (Category it : categoryList) {result.add(it.getCategoryId());List<Category> sonCategoryList = categoryMapper.getCategoryListByParentId(it.getCategoryId());for (Category item : sonCategoryList) {result.add(item.getCategoryId());}}return result;
}

CategoryMapper.java接口定义:

List<Category> getCategoryListByParentId(Long parentId);

CategoryMapper.xmlmapper定义:

<select id="getCategoryListByParentId" resultType="com.johnny.common.model.Category">SELECT category_id categoryId, parent_id parentId FROM categoryWHERE isactive = 1 AND parent_id = #{parentId}
</select>

附录

保存分类

@Value("${category.level.one:15}")
private Integer levelOne;
@Value("${category.level.two:10}")
private Integer levelTwo;
@Value("${category.level.three:10}")
private Integer levelThree;public String saveCategory(JSONObject jsonObject) {try {Category category = new Category();Long levelOneId = jsonObject.getLong("levelOneId");Long levelTwoId = jsonObject.getLong("levelTwoId");Integer type = jsonObject.getInteger("type");if (levelOneId == null && levelTwoId != null) {return JSONObject.toJSONString(ServiceUtil.returnError("非法情况:一级为空,二级不为空"));}// 一级分类ID为空,则新建一级分类,先判断一级分类个数if (null == levelOneId) {int categoryCount = categoryMapper.selectFirstLevelCategoryCount(type);if (categoryCount >= levelOne) {return JSONObject.toJSONString(ServiceUtil.returnError(String.format("一级分类不得超过%d个!", levelOne)));}// 分类名重复校验List<Category> list = categoryMapper.getCategoryListByCondition(jsonObject);if (CollectionUtils.isNotEmpty(list)) {return JSONObject.toJSONString(ServiceUtil.returnError("一级分类名不能重复"));}// 注意加上else} else if (null == levelTwoId) {// 一级分类ID不为空,二级分类ID为空,则新建二级分类,先判断所选一级分类下已有二级分类个数int categoryCount = categoryMapper.selectCategoryCountByParentId(levelOneId, type);if (categoryCount >= levelTwo) {return JSONObject.toJSONString(ServiceUtil.returnError(String.format("二级分类不得超过%d个!", levelTwo)));}List<Category> list = categoryMapper.getCategoryListByCondition(jsonObject);if (CollectionUtils.isNotEmpty(list)) {return JSONObject.toJSONString(ServiceUtil.returnError("二级分类名不能重复"));}category.setParentId(levelOneId);}// 一级二级分类ID都不为空,则新建一个三级分类,父类ID,为二级分类IDif (null != levelOneId && null != levelTwoId) {int categoryCount = categoryMapper.selectCategoryCountByParentId(levelTwoId, type);if (categoryCount >= levelThree) {return JSONObject.toJSONString(ServiceUtil.returnError(String.format("三级分类不得超过%d个!", levelThree)));}List<Category> list = categoryMapper.getCategoryListByCondition(jsonObject);if (CollectionUtils.isNotEmpty(list)) {return JSONObject.toJSONString(ServiceUtil.returnError("三级分类名不能重复"));}category.setParentId(levelTwoId);}category.setUserId(jsonObject.getString("userId"));category.setCategoryName(jsonObject.getString("categoryName"));category.setUpdateUserName(jsonObject.getString("updateUserName"));category.setType(type);int num = categoryMapper.insertSelective(category);if (num > 0) {return JSONObject.toJSONString(ServiceUtil.returnSuccess());} else {return JSONObject.toJSONString(ServiceUtil.returnError("添加分类失败!"));}} catch (Exception e) {logger.error("saveCategory error:{}", e.toString());return JSONObject.toJSONString(ServiceUtil.returnError(e.getMessage()));}
}

查询分类

categoryMapper.getCategoryById(id);,根据主键,即category_id查询,省略代码。

public String getCategoryList(JSONObject jsonObject) {try {// 分页PageHelper.startPage(jsonObject.getInteger("pageNo"), jsonObject.getInteger("pageSize"));// 代码省略List<Category> list = categoryMapper.getCategoryList(jsonObject);list.forEach(x -> {x.setCategoryName(this.getParentCategoryById(x.getCategoryId()).getT2());if (x.getParentId() == null) {x.setLevel(1);} else if (this.isLevelTwo(x)) {x.setLevel(2);} else {x.setLevel(3);}});PageInfo<Category> pageInfo = new PageInfo<>(list);return JSONObject.toJSONString(ServiceUtil.returnSuccessData(pageInfo));} catch (Exception e) {logger.error("getCategoryList error:{}", e.toString());return JSONObject.toJSONString(ServiceUtil.returnError(e.getMessage()));}
}/*** tuple.t1 为分类ID* tuple.t2 为分类名称*/
public Tuple<String, String> getParentCategoryById(Long categoryId) {Category result = categoryMapper.getCategoryById(categoryId);Long parentId = result.getParentId();Tuple<String, String> tuple = new Tuple<>();// 当父级ID为空时,此时为一级分类if (null == parentId) {tuple.setT1(Collections.singletonList(categoryId).toString());tuple.setT2(result.getCategoryName());return tuple;} else {Category parentResult = categoryMapper.getCategoryById(parentId);// 表明parentResult是一级分类,result为二级分类if (null == parentResult.getParentId()) {tuple.setT1(Arrays.asList(parentResult.getCategoryId(), categoryId).toString());tuple.setT2(parentResult.getCategoryName().concat("/").concat(result.getCategoryName()));return tuple;} else {// 用parentResult的parentId当做categoryId去查询信息,lastResult为一级,parentResult为二级,result为三级Category lastResult = categoryMapper.getCategoryById(parentResult.getParentId());tuple.setT1(Arrays.asList(lastResult.getCategoryId(), parentResult.getCategoryId(), categoryId).toString());tuple.setT2(lastResult.getCategoryName().concat("/").concat(parentResult.getCategoryName()).concat("/").concat(result.getCategoryName()));return tuple;}}
}/*** 判断是否是二级*/
private boolean isLevelTwo(Category item) {if (item.getParentId() == null) {return false;}Category po = categoryMapper.getCategoryById(item.getParentId());if (po == null) {return false;} else {return po.getParentId() == null;}
}

二元组定义

@Data
public class Tuple<T1, T2> {private T1 t1;private T2 t2;
}

参考

MySQL递归查询,实现上下级联查,父子级查询
你在用递归查询 Mysql 的树形结构吗

MySQL+Java实现父子级联查询相关推荐

  1. mysql 三个表级联查询,以主表为主数据表为辅

    SELECT u.code,u.name,p.pm10,d.voltage1 FROM t_device AS u LEFT JOIN (t_device_r_data AS p LEFT JOIN ...

  2. mysql级联查询_mysql 各种级联查询后更新(update select)

    mysql  各种级联查询后更新(update select). CREATE TABLE `tb1` ( `id` int(11) NOT NULL, `A` varchar(100) defaul ...

  3. MySQL中单句实现无限层次父子关系查询

    在 SQL Server 中,使用 CTE 表达式很容易做到无限层次父子关系查询:在不支持CTE表达式的版本中,借助函数递归也可以轻松实现. 在 MySQL 中,这个需求的实例稍显复杂, MySQL ...

  4. java JDBC连接MySQL数据库调用存储过程进行查询

    java JDBC连接MySQL数据库调用存储过程进行查询 主程序代码 工具类 文件信息 存储过程 结果截图 主程序代码 package Mysql;import util.JDBCUtils; im ...

  5. java中mysql分组查询_ES对应mysql的group by分组查询javaApi,多对多关系的分组查询...

    ES对应mysql的group by分组查询javaApi,多对多关系的分组查询 比如我这边有个下列订单索引数据,现在的需求是按用户(fmerchantId)和支付方式(fchannelId)进行分组 ...

  6. MyBatis实现级联查询及逆向生成

    MyBatis实现级联查询及逆向生成 一,级联查询 1.级联查询 N-1 ​ 以多的一方为主表 接口 //级联查询 N-1List<Emp> selectEmp(Map map); 映射文 ...

  7. ssm练习第二天_项目拆分moudle_基本增删改查_批量删除_一对一级联查询

    文章目录 一.ssm框架整合 1.创建maven的工程 1. 创建ssm_parent父工程(打包方式选择pom,必须的) 2. 创建ssm_dao子模块(打包方式是jar包) 3. 创建ssm_se ...

  8. mysql 一周一月_mysql查询当天、本周,本月,上一个月的数据

    今天 select * from 表名 where to_days(时间字段名) = to_days(now()); 昨天 SELECT * FROM 表名 WHERE TO_DAYS( NOW( ) ...

  9. mybatis一对多关联 创建_MyBatis多对多关联查询(级联查询)

    其实,MyBatis 没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换. 例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系,使用一个中间表(订 ...

最新文章

  1. LeetCode Wiggle Subsequence(动态规划)
  2. Linux命令之at
  3. map初始化_需要掌握的Go基础之map:文末附面试题一道
  4. linux安装mysql8.0_Linux系统:centos7下安装Jdk8、Tomcat8、MySQL5.7环境
  5. 解决pathForResource返回nil / 无法读取plist文件问题
  6. 搜索引擎提交注意事项
  7. 新浪微博爬虫设计(Python版)
  8. spring-boot基础知识
  9. ​阿里云SAE助力百富旅行实现Serverless+微服务完美结合
  10. 4.6 Data符号调制——16QAM
  11. 计算机说课稿模板小学数学,精选小学数学万能说课稿模板
  12. php 用户控件,一个使用用户控件(包括组件)的演示-.NET教程,组件控件开发
  13. 一文讲解AGV机器人的12种导航导引方式,收藏备用
  14. 对于分布式消息队列我有话说
  15. 易基因|植物育种:ChIP-seq(组蛋白)揭示H3K36me修饰影响温度诱导的植物可变剪接和开花
  16. 论文阅读:Factoring Statutory Reasoning as Language Understanding Challenges
  17. 巨人就在你的身边——14期英语反馈表彰大会有感
  18. 你不知道的HelloGitHub!
  19. php把图片合成视频,如何把照片做成视频 照片音乐视频制作 并插入几段短视频片段...
  20. 算法与数据结构之带头结点和不带头结点单链表存在的问题

热门文章

  1. springboot+vue美容院美妆化妆品商城管理系统nodejs
  2. C语言中while循环我爱你十次案例
  3. javascpirt + HTML实现点亮灯泡
  4. 转-Launchy:最强快速启动的完整优化指南
  5. LWN:9月份LPC上关于folio的一次讨论!
  6. JavaScript---分支与循环
  7. 波士顿动力机器人会跑了!机器狗学会上下楼!(视频)
  8. java 微信时间戳转换工具,微信小程序时间戳的转换及调用
  9. 2023中国智慧医院建设、装备及应用展览会
  10. 鸿蒙合香丸吃了好吗,合香丸的功效与副作用有哪些 盘点合香丸的两大作用和两大禁忌...