SQL实战:03之SQL中的递归查询

文章目录

概述

最近刷题时遇到了一道需要根据组织层级来统计各个层级的一些数据,当时碰到时的第一想法就是需要使用递归来实现。但是以前在SQl中从来就没有用过递归查询,后面到网上一搜索,居然还真有递归查询的实现,也算是给自己扫了一下盲了。

SQL 中的递归实现

递归是一种通过自身调用自身来 逐步推理出计算结果的一种思想。在SQL中递归查询同样也是如此,通过自身调用自身来逐步构建查询结果。一般多用于处理具有层次结构的数据,如树形结构、组织结构图、文件系统目录等。

在SQL中,使用 WITH RECURSIVE 关键字来实现。

同编程语言中的递归函数调用类似,递归查询的执行过程也需要遵循以下三个原则:

  • 初始查询
    首次执行初始查询 ,得到一个初始结果集。
  • 递归调用
    将初始结果集作为输入,执行递归查询,并将递归查询的结果与初始结果集合并,作为下一次查询的输入。
  • 终止条件
    重复执行递归查询,直到没有新的行可以添加到结果集中,递归查询终止。
    递归查询的语法如下:
sql 复制代码
    WITH RECURSIVE table_name AS (
        --初始查询
        SELECT 1 AS level FROM XXX WHERE XXX
        UNION ALL
        --递归查询
        SELECT level+1  FROM table_name WHERE XXXX
        --终止条件是查询到所有的记录都做了处理,递归终止 
    )

例如使用递归生成一个连续的序列:

sql 复制代码
-- 递归查询生成数字序列
WITH RECURSIVE lianxu_numbers AS (
    -- 初始查询,生成第一个数字 1
    SELECT 1 AS num
    UNION ALL
    -- 递归查询(递归成员):生成下一个数字
    SELECT num + 1
    FROM lianxu_numbers
    WHERE num < 100 
    -- 小于100 是终止条件
)
SELECT * FROM numbers;

题目一:分析组织层级

复制代码
表:Employees

+----------------+---------+
| Column Name    | Type    | 
+----------------+---------+
| employee_id    | int     |
| employee_name  | varchar |
| manager_id     | int     |
| salary         | int     |
| department     | varchar |
+----------------+----------+
employee_id 是这张表的唯一主键。
每一行包含关于一名员工的信息,包括他们的 ID,姓名,他们经理的 ID,薪水和部门。
顶级经理(CEO)的 manager_id 是空的。
编写一个解决方案来分析组织层级并回答下列问题:

层级:对于每名员工,确定他们在组织中的层级(CEO 层级为 1,CEO 的直接下属员工层级为 2,以此类推)。
团队大小:对于每个是经理的员工,计算他们手下的(直接或间接下属)总员工数。
薪资预算:对于每个经理,计算他们控制的总薪资预算(所有手下员工的工资总和,包括间接下属,加上自己的工资)。
返回结果表以 层级 升序 排序,然后以预算 降序 排序,最后以 employee_name 升序 排序。

结果格式如下所示。

 

示例:

输入:

Employees 表:

+-------------+---------------+------------+--------+-------------+
| employee_id | employee_name | manager_id | salary | department  |
+-------------+---------------+------------+--------+-------------+
| 1           | Alice         | null       | 12000  | Executive   |
| 2           | Bob           | 1          | 10000  | Sales       |
| 3           | Charlie       | 1          | 10000  | Engineering |
| 4           | David         | 2          | 7500   | Sales       |
| 5           | Eva           | 2          | 7500   | Sales       |
| 6           | Frank         | 3          | 9000   | Engineering |
| 7           | Grace         | 3          | 8500   | Engineering |
| 8           | Hank          | 4          | 6000   | Sales       |
| 9           | Ivy           | 6          | 7000   | Engineering |
| 10          | Judy          | 6          | 7000   | Engineering |
+-------------+---------------+------------+--------+-------------+
输出:

+-------------+---------------+-------+-----------+--------+
| employee_id | employee_name | level | team_size | budget |
+-------------+---------------+-------+-----------+--------+
| 1           | Alice         | 1     | 9         | 84500  |
| 3           | Charlie       | 2     | 4         | 41500  |
| 2           | Bob           | 2     | 3         | 31000  |
| 6           | Frank         | 3     | 2         | 23000  |
| 4           | David         | 3     | 1         | 13500  |
| 7           | Grace         | 3     | 0         | 8500   |
| 5           | Eva           | 3     | 0         | 7500   |
| 9           | Ivy           | 4     | 0         | 7000   |
| 10          | Judy          | 4     | 0         | 7000   |
| 8           | Hank          | 4     | 0         | 6000   |
+-------------+---------------+-------+-----------+--------+
解释:

组织结构:
Alice(ID:1)是 CEO(层级 1)没有经理。
Bob(ID:2)和 Charlie(ID:3)是 Alice 的直接下属(层级 2)
David(ID:4),Eva(ID:5)从属于 Bob,而 Frank(ID:6)和 Grace(ID:7)从属于 Charlie(层级 3)
Hank(ID:8)从属于 David,而 Ivy(ID:9)和 Judy(ID:10)从属于 Frank(层级 4)
层级计算:
CEO(Alice)层级为 1
每个后续的管理层级都会使层级数加 1
团队大小计算:
Alice 手下有 9 个员工(除她以外的整个公司)
Bob 手下有 3 个员工(David,Eva 和 Hank)
Charlie 手下有 4 个员工(Frank,Grace,Ivy 和 Judy)
David 手下有 1 个员工(Hank)
Frank 手下有 2 个员工(Ivy 和 Judy)
Eva,Grace,Hank,Ivy 和 Judy 没有直接下属(team_size = 0)
预算计算:
Alice 的预算:她的工资(12000)+ 所有员工的工资(72500)= 84500
Charlie 的预算:他的工资(10000)+ Frank 的预算(23000)+ Grace 的工资(8500)= 41500
Bob 的预算:他的工资 (10000) + David 的预算(13500)+ Eva 的工资(7500)= 31000
Frank 的预算:他的工资 (9000) + Ivy 的工资(7000)+ Judy 的工资(7000)= 23000
David 的预算:他的工资 (7500) + Hank 的工资(6000)= 13500
没有直接下属的员工的预算等于他们自己的工资。
注意:

结果先以层级升序排序
在同一层级内,员工按预算降序排序,然后按姓名升序排序

题解

sql 复制代码
with recursive a1 as (
   select employee_id,manager_id,1 as a
   from employees
   
   union all

   select a1.employee_id,e.manager_id,a+1
   from employees e  join a1 on a1.manager_id=e.employee_id
)
,
--level:层级
a2 as (
    select employee_id, count(*) level
       from a1
       group by employee_id)/*level*/
,
--下属人数
a3 as (
    select manager_id, count(*) team_size
       from a1
       where manager_id is not null
       group by manager_id)/*size*/
,
--工资统计
a4 as (
    select a1.manager_id, sum(salary) budget
       from a1
                left join employees e using (employee_id)
       where a1.manager_id is not null
       group by a1.manager_id
)

select employee_id,employee_name,level,ifnull(team_size,0) team_size,salary+ifnull(budget,0) budget
from a2 left join employees using(employee_id)
left join a3 on a2.employee_id=a3.manager_id
left join a4 on a2.employee_id=a4.manager_id
order by level,budget desc,employee_name;

题目二:树节点求解

复制代码
表:Tree

+-------------+------+
| Column Name | Type |
+-------------+------+
| id          | int  |
| p_id        | int  |
+-------------+------+
id 是该表中具有唯一值的列。
该表的每行包含树中节点的 id 及其父节点的 id 信息。
给定的结构总是一个有效的树。
 

树中的每个节点可以是以下三种类型之一:

"Leaf":节点是叶子节点。
"Root":节点是树的根节点。
"lnner":节点既不是叶子节点也不是根节点。
编写一个解决方案来报告树中每个节点的类型。

以 任意顺序 返回结果表。

结果格式如下所示。

 

示例 1:


输入:
Tree table:
+----+------+
| id | p_id |
+----+------+
| 1  | null |
| 2  | 1    |
| 3  | 1    |
| 4  | 2    |
| 5  | 2    |
+----+------+
输出:
+----+-------+
| id | type  |
+----+-------+
| 1  | Root  |
| 2  | Inner |
| 3  | Leaf  |
| 4  | Leaf  |
| 5  | Leaf  |
+----+-------+
解释:
节点 1 是根节点,因为它的父节点为空,并且它有子节点 2 和 3。
节点 2 是一个内部节点,因为它有父节点 1 和子节点 4 和 5。
节点 3、4 和 5 是叶子节点,因为它们有父节点而没有子节点。

题解

步骤一:通过递归查询出每个节点的上级节点和下级节点分布

sql 复制代码
with recursive a1 AS (
    select id,p_id, 1 as level from Tree
    union all
    select a1.id,e.p_id, level+1 as level from Tree e join a1 on a1.p_id = e.id
)
select * from a1;

输出:

复制代码
| id | p_id | level |
| -- | ---- | ----- |
| 1  | null | 1     |
| 2  | 1    | 1     |
| 3  | 1    | 1     |
| 4  | 2    | 1     |
| 5  | 2    | 1     |
| 3  | null | 2     |
| 2  | null | 2     |
| 5  | 1    | 2     |
| 4  | 1    | 2     |
| 4  | null | 3     |
| 5  | null | 3     |

得到上面这张零时表后,就可以根据这张临时表统计出每个节点的下级节点的数量,每个节点所在的层次。

步骤二:分组统计

  • 统计节点所在层级
sql 复制代码
select id, count(*) as level from a1 group by id;
  • 统计几点的下级节点(包括间接节点)的数量
sql 复制代码
select p_id, count(*) as son_nums from a1 where p_id is not null group by p_id;

子节点数为0的节点就为叶子节点。

相关推荐
唯♧15 分钟前
pg数据库删除模式
数据库·oracle
noravinsc2 小时前
django.db.models.query_utils.DeferredAttribute object
数据库·django·sqlite
阿桨5 小时前
【Prometheus-MySQL Exporter安装配置指南,开机自启】
数据库·mysql
红烧柯基6 小时前
解决redis序列号和反序列化问题
java·数据库·redis
小黄人20256 小时前
【KWDB 创作者计划】一款面向 AIoT 的多模数据库实战体验
数据库·云计算·kwdb
API_technology6 小时前
《淘宝 API 数据湖构建:实时商品详情入湖 + Apache Kafka 流式处理指南》
数据库·分布式·数据挖掘·kafka·apache
DDDiccc6 小时前
黑马Redis(四)
数据库·redis·mybatis
麓殇⊙6 小时前
MySQL--数据引擎详解
数据库·mysql
rainFFrain6 小时前
MySQL的数据类型
数据库·mysql