首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MySQL层级查询实战:无函数实现部门父路径

MySQL层级查询实战:无函数实现部门父路径

原创
作者头像
舒一笑不秃头
发布2025-06-19 23:04:59
发布2025-06-19 23:04:59
3510
举报

## 本次需要击毙的MySQL函数

函数主要用于**获取部门的完整层级路径**,方便在应用程序或SQL查询中直接调用,快速获得部门的上下级关系信息。执行该函数之后简单使用SQL可以实现数据库中部门名称查询。例如下面sql

```java

select name,GetDepartmentParentNames(du.department_code, du.tenant_id) as department_full_name xxx from tableName

```

![](https://i-blog.csdnimg.cn/img_convert/2c7c4b3696d0ad64e19ef4c43115dcff.png)

```java

CREATE

DEFINER = xxx@`%`

FUNCTION GetDepartmentParentNames(_code VARCHAR(255), _tenant_id VARCHAR(255)) RETURNS TEXT

BEGIN

DECLARE _name TEXT;

DECLARE _parentCode VARCHAR(255);

DECLARE _tempName TEXT;

DECLARE _tempCode VARCHAR(255);

-- 根据传入的部门code和租户id,查询该部门的code、name和父级pid

SELECT code, name, pid INTO _tempCode, _tempName, _parentCode

FROM table1

WHERE code = _code AND tenant_id = _tenant_id;

-- 初始化_name变量,格式为 "code#name"

SET _name = CONCAT(_tempCode, '#', _tempName);

-- 通过循环,逐级查找父部门,直到父部门pid为'0'(表示无父部门)

WHILE _parentCode <> '0' DO

SELECT code, name, pid INTO _tempCode, _tempName, _parentCode

FROM table1

WHERE code = _parentCode AND tenant_id = _tenant_id;

-- 将当前父部门信息拼接到_name前面,格式依然是 "code#name",用逗号分隔

SET _name = CONCAT(CONCAT(_tempCode, '#', _tempName), ',', _name);

END WHILE;

-- 返回拼接好的字符串,包含从顶级父部门到当前部门的所有层级信息

RETURN _name;

END;

```

## 如何进行重构解决

分析函数的作用是通过递归的方式,基于部门code和tenant_id,逐级向上查找父部门,拼接出完整的部门层级名称字符串。

### 方案一采用MySQL8+的CTE实现

```java

WITH RECURSIVE dept_path AS (

SELECT code, name, pid, CAST(CONCAT(code, '#', name) AS CHAR(1000)) AS full_path

FROM table1

WHERE code = #{department_code} AND tenant_id = #{tenant_id}

UNION ALL

SELECT d.code, d.name, d.pid, CONCAT(CONCAT(d.code, '#', d.name), ',', dp.full_path)

FROM table1 d

JOIN dept_path dp ON dp.pid = d.code

WHERE d.tenant_id = #{tenant_id} AND d.pid <> '0'

)

SELECT full_path FROM dept_path WHERE pid = '0' LIMIT 1;

```

但是我不会这个咋办,那就换一种实现方式~

![](https://i-blog.csdnimg.cn/img_convert/55482bb9f9832aabd3778e1e14a8cff4.png)

### 方案二应用层或存储过程外部实现递归

> 涉及机密下述代码进行过脱敏处理

#### 1. 修改SQL查询部门部分,直接查询用户对应的部门编码和部门名称,不调用递归函数

```java

<select id="xxxxx" resultType="xxxxxxx">

SELECT du.user_code AS code, du.department_code AS departmentNo

FROM table1 du

WHERE du.tenant_id = #{tenantId}

<if test="userCodes != null and userCodes.size() > 0">

AND du.user_code IN

<foreach collection="userCodes" item="item" separator="," open="(" close=")">

#{item}

</foreach>

</if>

</select>

```

调用上述方法获取到集合之后需要利用Set集合进行去重

```java

// 提取所有部门code(包括父部门)用于查询部门信息

Set<String> allDeptCodes = new HashSet<>();

for (KbUserRoleInfo ud : userDepartments) {

allDeptCodes.add(ud.getDepartmentNo());

}

```

#### 2.新增查询部门信息相关信息sql

```java

<select id="listDepartmentsByCodes" resultType="xxxxxx">

SELECT code, name, pid

FROM table1

WHERE tenant_id = #{tenantId}

AND code IN

<foreach collection="codes" item="code" separator="," open="(" close=")">

#{code}

</foreach>

</select>

```

获取到部门相关信息之后,构建map结构,部门编码为key,部门信息为value

```java

List<KbDepartment> departments = sysUserMapper.listDepartmentsByCodes(tenantId, new ArrayList<>(allDeptCodes));

Map<String, KbDepartment> departmentMap = departments.stream()

.collect(Collectors.toMap(KbDepartment::getCode, d -> d));

```

#### 3. 递归补全所有部门信息编码需要使用编码查询部门信息

```java

/**

* 递归添加父部门code

*/

private void addParentDepartments(String deptCode, Map<String, KbDepartment> departmentMap, Set<String> expandedDeptCodes) {

KbDepartment dept = departmentMap.get(deptCode);

if (dept != null && dept.getPid() != null && !"0".equals(dept.getPid()) && !expandedDeptCodes.contains(dept.getPid())) {

expandedDeptCodes.add(dept.getPid());

addParentDepartments(dept.getPid(), departmentMap, expandedDeptCodes);

}

}

```

#### 4.根据完整部门编码获取完整部门信息

```java

// 再次查询所有部门信息(包含父部门)

List<KbDepartment> allDepartments = sysUserMapper.listDepartmentsByCodes(tenantId, new ArrayList<>(expandedDeptCodes));

Map<String, KbDepartment> allDepartmentMap = allDepartments.stream()

.collect(Collectors.toMap(KbDepartment::getCode, d -> d));

```

#### 5.构建用户部门信息映射

```java

// 由于一个用户会涉及多个组织假如,因此构建Map结构,如果k-v都是简单的String结构,会出现后面组织覆盖前面组织情况

Map<String, List<String>> userDeptFullPathMap = new HashMap<>();

for (KbUserRoleInfo ud : userDepartments) {

String fullPath = buildDepartmentFullPath(ud.getDepartmentNo(), allDepartmentMap);

userDeptFullPathMap.computeIfAbsent(ud.getCode(), k -> new ArrayList<>()).add(fullPath);

}

/**

* 递归构建部门完整路径字符串,格式:code#name,code#name,...

*/

private String buildDepartmentFullPath(String deptCode, Map<String, KbDepartment> departmentMap) {

KbDepartment dept = departmentMap.get(deptCode);

if (dept == null) {

return "";

}

if ("0".equals(dept.getPid())) {

return dept.getCode() + "#" + dept.getName();

}

String parentPath = buildDepartmentFullPath(dept.getPid(), departmentMap);

return parentPath + "," + dept.getCode() + "#" + dept.getName();

}

```

#### 6.部门名称格式化

```java

/**

* 重置部门名称

*/

public void resetDepartmentName() {

// 处理部门名称

String departmentName = this.getDepartmentName();

// 部门名称从SQL数据库查询的规则是,部门编号#部门名称, 例如:0001#技术部,000101#开发一组

// 这里将部门名称处理成为 技术部 > 开发一组,通过字符>连接起来

if (StrUtil.isBlank(departmentName)) {

return;

}

// 一个组织人员有多个部门的情况

List<String> multipartDepartmentNameList = StrUtil.split(departmentName, "/");

if (CollUtil.isEmpty(multipartDepartmentNameList)) {

return;

}

List<String> departmentNames = new ArrayList<>();

List<String> departmentShortNames = new ArrayList<>();

List<String> departmentNos = new ArrayList<>();

List<String> orgCodes = new ArrayList<>();

for (String oneDepartmentName : multipartDepartmentNameList) {

List<String> departmentNameList = StrUtil.split(oneDepartmentName, StrUtil.COMMA);

// 获取一级的组织编号

orgCodes.add(DepartmentUtils.getOrgCode(departmentNameList.get(0)));

// 设置最后一个部门名称,获取departmentNameList的末尾元素

String shortName = DepartmentUtils.splitDepartmentName(departmentNameList.get(departmentNameList.size() - 1));

departmentShortNames.add(shortName);

String sortNo = DepartmentUtils.splitDepartmentNo(departmentNameList.get(departmentNameList.size() - 1));

departmentNos.add(sortNo);

List<String> names = new LinkedList<>();

for (String name : departmentNameList) {

names.add(DepartmentUtils.splitDepartmentName(name));

}

departmentNames.add(StrUtil.join(" > ", names));

}

this.setOrgCode(StrUtil.join(StrUtil.COMMA, orgCodes));

// 设置部门名称

this.setDepartmentName(StrUtil.join(StrUtil.COMMA, departmentNames));

this.setDepartmentNo(StrUtil.join(StrUtil.COMMA, departmentNos));

this.setDepartmentShortName(StrUtil.join(StrUtil.COMMA, departmentShortNames));

}

```

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档