在一般的业务场景中,特别是针对相关的业务线相关的功能开发时,可能会遇到一些具有层级关系的数据关联结构。比如,一个员工可能属于一个领导管辖,而同时,这个领导也被另一个更高级别的领导管辖……,而本质上这些领导本身也是一个公司的员工,和普通员工有着相同的属性。因此,在为个功能设计相关的表结构时,单独为领导角色创建一个对应的表不是一个可靠的解决方案。

针对上文提到的业务情况,在一个员工只有一个领导的前提下,我提出我个人想到的解决思路:由于员工和领导具有相同的属性,那么可以尝试将领导和普通员工的数据放入同一个表中。同时,在表中新增一个字段 p_id,表示当前员工的领导的 id,这样,最后的结果将会是每条员工的的数据都包含一个指向对应领导的指针(对于 CEO 一类角色 p_id 设置为 NULL)。在这种情况下,数据将会通过 p_id 组成一种类似链表的结构:

为此,创建样例数据库脚本文件:

CREATE TABLE employee
(
id INT NOT NULL PRIMARY KEY,
name VARCHAR(32),
p_id INT
); INSERT INTO employee (id, name, p_id) VALUES (1, 'CEO', null);
INSERT INTO employee (id, name, p_id) VALUES (2, '董事长', 1);
INSERT INTO employee (id, name, p_id) VALUES (3, '经理', 2);
INSERT INTO employee (id, name, p_id) VALUES (4, '组长', 3);
INSERT INTO employee (id, name, p_id) VALUES (5, '员工', 4);

然而,这样的设计使得 SQL 的查询操作变得不是那么简单。使用以下的左外连接的方式可以查询对应的关联关系:

SELECT E.id, E.name AS "Employee", M.id, M.name AS "Manager"
FROM employee E
LEFT OUTER JOIN employee M
ON E.p_id = M.id;

id 是严格递增的前提下,可以通过相关的 WHERE 语句来查找指定记录的所有父级节点,例如,在上面的例子中,如果需要查询 id 为 \(3\) 的员工的所有的领导,那么加上 WHERE 语句可以达到这个目的:

SELECT E.id, E.name AS "Employee", M.id, M.name AS "Manager"
FROM employee E
LEFT OUTER JOIN employee M
ON E.p_id = M.id
WHERE E.id <= 3

这种方式可行的前提条件是 id 必须是严格递增的(p_id 必须小于当前记录的 id),因此对于一般的场景这种方式并不适用

在大部分的关系数据库管理系统中,都提供了递归查询的语句以支持递归查询(MySQL 8.0 后支持、PostgreSQL、SQL Server、Oracle 都支持),具体如下所示:

WITH RECURSIVE cte AS (
SELECT *, 1 AS lv -- lv 自定义列用于记录递归查询的深度
FROM employee
WHERE employee.id = 3 -- 这里为初始查询语句,这里我们将会将 id 为 3 的员工记录作为初始记录
UNION
SELECT employee.*, lv + 1 -- 每次递归调用时都需要将深度 +1
FROM employee
JOIN cte ON employee.id = cte.p_id -- cte 当前查询到的记录,通过这个记录递归进行查询
)
SELECT *
FROM cte
ORDER BY id; -- 该语句是实际的调用者

通过上面的语句进行查询,对应的查询结果如下(在 MySQL 8.0 以及 PostgreSQL 上测试):

+------+-----------+------+------+
| id | name | p_id | lv |
+------+-----------+------+------+
| 1 | CEO | NULL | 3 |
| 2 | 董事长 | 1 | 2 |
| 3 | 经理 | 2 | 1 |
+------+-----------+------+------+

相比较之前通过左外连接的方式进行的查询,递归查询的方式可以有效地查询相关的父节点。这种查询方式存在的一个缺点在于当递归查询的深度达到一定值时,查询可能会崩溃,对于这个问题,可以考虑更改数据库管理系统的相关设置。例如,对于 MySQL 来讲,可以通过设置相关的全局变量来设置最大递归查询深度:

SET @@GLOBAL.max_sp_recursion_depth = 255; -- 设置全局最大查询深度

在实际的业务场景中,可能一个员工并不只有一个直系领导,还可能存在多个直接领导。对于这种情况,单独的 p_id 已经无法再满足实际的需要了。在这种情况下,需要建立一张单独的表来存储这些关联关系:

CREATE TABLE ep_rel (
id INT NOT NULL PRIMARY KEY , -- 当前表的主键
ep_id INT NOT NULL , -- 员工 id
p_id INT NOT NULL -- 员工领导的 id
); INSERT INTO ep_rel (id, ep_id, p_id) VALUES (1, 5, 4);
INSERT INTO ep_rel (id, ep_id, p_id) VALUES (2, 5, 3);
INSERT INTO ep_rel (id, ep_id, p_id) VALUES (3, 5, 2);
INSERT INTO ep_rel (id, ep_id, p_id) VALUES (4, 5, 1);

现在员工被设置为所有领导都会直接管辖了,和上文的递归查询类似,只是在进行递归查询的时候需要再进行一次内连接:

WITH RECURSIVE ep_cte AS (
SELECT *, 1 AS lv
FROM employee
WHERE id = 5 -- 初始员工记录查询 UNION ALL SELECT employee.*, lv + 1
FROM ep_cte
JOIN ep_rel ON ep_cte.id = ep_rel.ep_id
JOIN employee ON ep_rel.p_id = employee.id -- 关联到的父节点记录
)
SELECT * FROM ep_cte;

对应的查询结果如下:

+------+-----------+------+------+
| id | name | p_id | lv |
+------+-----------+------+------+
| 5 | 员工 | 4 | 1 |
| 4 | 组长 | 3 | 2 |
| 3 | 经理 | 2 | 2 |
| 2 | 董事长 | 1 | 2 |
| 1 | CEO | NULL | 2 |
+------+-----------+------+------+

注意这里通过 lv 列来标识递归的层级,因此 lv 相同说名查询处于同一递归查询深度

参考:

[1] https://web.archive.org/web/20180729174436/http://www.tomjewett.com/dbdesign/dbdesign.php?page=recursive.php

[2] https://www.sqlshack.com/mysql-recursive-queries/

[3] https://www.mysqltutorial.org/mysql-recursive-cte/

[4] https://stackoverflow.com/questions/20215744/how-to-create-a-mysql-hierarchical-recursive-query

SQL 的递归查询的更多相关文章

  1. MS SQL Server递归查询

    原文:MS SQL Server递归查询 刚才在论坛上看到网友一个要求.参考如下,Insus.NET分析一下,可以使用MS SQL Server的递归查询,得到结果.准备一张表: 根据网友提供的数据, ...

  2. sql 树形递归查询

    sql 树形递归查询: with ProductClass(ClassId,ClassName) as ( union all select c.ClassId,c.ClassName from Cl ...

  3. SQL SERVER递归查询

    SQL SERVER 进行递归查询 有如下数据表

  4. 【Sql Server】SQL SERVER 递归查询

    SQL SERVER 2005之前的版本只能用函数方法实现,SQL SERVER 2005之后新增了CTE功能,可以利用CTE实现递归查询: CTE:公用表达式Common Table Express ...

  5. sql with 递归查询

    用with实现递归查询 1.数据准备 假定有一个表DiGui,有两个字段Id int ParentId int Id ParentId 4 0 5 0 7 0 2 1 8 5 15 5 9 7 14 ...

  6. sql语句递归查询(start with)

    在做项目中遇到一个问题,就是同一个表中的数据存在级联关系,但是只要查出来的末级数据,纠结了好久,好不容易找到了一个博主的分享,在这里做个记录,也是和大家一起分享学习一下这位大神的技术,共勉 写代码时碰 ...

  7. [SQL]T-Sql 递归查询(给定节点查所有父节点、所有子节点的方法)

    T-Sql 递归查询(给定节点查所有父节点.所有子节点的方法)   -- 查找所有父节点with tab as( select Type_Id,ParentId,Type_Name from Sys_ ...

  8. SQL 语句递归查询 With AS 查找所有子节点

    create table #EnterPrise (   Department nvarchar(50),--部门名称   ParentDept nvarchar(50),--上级部门   Depar ...

  9. Sql Server递归查询(转)

    有如下数据表 假如我们要查询ID为003的数据的所有子节点我们可以使用CTE 递归查询完成... if OBJECT_ID('tb','N') is not null drop table tb; c ...

  10. SQL SEVER 递归查询

    with ts as ( --首先要查询出最原始父级的信息 union all --全连接 select a.fitemclassid,a.fitemid, a.fnumber,a.Fparentid ...

随机推荐

  1. 20个最佳实践提升Terraform工作流程|Part 2

    在上一部分,我们一同探讨了构建 Terraform 项目的一些策略,以及使用 Terraform 管理 IaC 的部分最佳实践.今天,我们将继续深入研究将 Terraform 代码提升到新水平的具体要 ...

  2. JuiceFS 目录配额功能设计详解

    JuiceFS 在最近 v1.1 版本中加入了社区中呼声已久的目录配额功能.已发布的命令支持为目录设置配额.获取目录配额信息.列出所有目录配额等.完整的详细信息,请查阅文档. 在设计此功能时,对于它的 ...

  3. TopCoder 15903 EllysNim

    TopCoder 15903 EllysNim(https://vjudge.net/problem/TopCoder-15903) \(n\)看似有点东西,实际上就只是一个贪心... 设\(i\)表 ...

  4. 【matplotlib 实战】--南丁格尔玫瑰图

    南丁格尔玫瑰图是一种用极坐标下的柱状图或堆叠柱状图来展示数据的图表. 虽然南丁格尔玫瑰图外观类似饼图,但是表示数据的方式不同,它是以半径来表示数值的,而饼图是以扇形的弧度来表达数据的. 所以,南丁格尔 ...

  5. 数组操作 filter和find

    现在有个需求: 后端给一个list过来,1-4,有就显示,没有前面四个card就空着,从第五个开始,有数据就循环出来,区别他们的kind字段. 这里想了很久怎么写,最后决定,洗数据. 就是从给给的数据 ...

  6. SNN_文献阅读_Text Classification in Memristor-based Spiking Neural Networks

    SNN中局部学习和非局部学习,基于梯度的规则都需要对用于表示单个连续值的脉冲训练窗口上的累积误差进行平均,这种方法在更新权重时考虑了每一个脉冲的影响.在计算速度和空间效率等方面,特别是当代表单个数值的 ...

  7. 03Java学习_注释和代码规范

    注释和代码规范 目录 注释和代码规范 注释 注释介绍 单行注释 多行注释 文档注释 代码规范 注释 注释介绍 用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性(可读性):注释 是一个程序员必 ...

  8. 牛客多校第一场 A. Alice and Bob (暴力SG)

    题目大概 有两堆石子,有两个人拿,一个人从一堆中拿\(k\)个,那么就必须从另一堆中拿\(s*k\)个,Alice先拿,问是否必赢. 解题: 数据不大,看到前\(20\)名队伍没有推结论做的..除了打 ...

  9. DiscuzQ官方最新v3.0.220211源码编译搭建教程和官方部署教程,适合二开(已本地编译通过,无任何错误)

    经过长达半个月的研究! 完成这篇DiscuzQ官方最新版本v3.0.220211的源码编译和官方部署教程.适合喜欢二次开发的小伙伴们,已经通过本地编译测试,保证没有任何错误. 具体教程在我搭建的dzq ...

  10. python01-基础概念与环境搭建

    学习目标 了解硬件 & 操作系统 & 软件(应用系统)之间的关系. 了解常见的操作系统都有哪些. 了解编译器和解释器的区别和作用. 了解编程语言进行分类 了解Python解释器的种类 ...