一条sql搞定递归向上汇总

本文详细介绍了一种使用SQL查询构建财务预算数据的树形结构的方法,并进一步优化了查询,以实现对各层级数据的金额汇总,适用于财务管理和预算分析。

1. 根据末级递归查询出上级,形成树形结构

SELECT DISTINCT ACC.ACCOUNTCODEID, ACC.PARENTCODEID, ACC.ACCOUNTCODE, ACC.ACCOUNTCODENAME, 
 ACC.FIRSTORDER, ACC.SECONDORDER,T.PLANMAINID, T.PLANMAINNAME, T.PLANMAINCODE,'Y' AS ISACCOUNT,
CASE WHEN NVL(T.ACCOUNTCODEID,0) =0 THEN 'Y' ELSE 'N' END AS ISADD,
T.COMPANYID, T.BUDGETYEAR, 
T.NAMTMONTH1,T.NAMTMONTH2,T.NAMTMONTH3,T.NAMTMONTH4,T.NAMTMONTH5,T.NAMTMONTH6,T.NAMTMONTH7,T.NAMTMONTH8,
T.NAMTMONTH9,T.NAMTMONTH10,T.NAMTMONTH11,T.NAMTMONTH12
FROM FM_ACCOUNTCODE ACC
LEFT JOIN (
SELECT COMPANYID, BUDGETYEAR, ACCOUNTCODEID, ACCOUNTCODE , ACCOUNTCODENAME, 0 AS PLANMAINID, NULL AS PLANMAINNAME,NULL AS PLANMAINCODE,
SUM(NAMTMONTH1) AS NAMTMONTH1,SUM(NAMTMONTH2) AS NAMTMONTH2,SUM(NAMTMONTH3) AS NAMTMONTH3,
SUM(NAMTMONTH4) AS NAMTMONTH4,SUM(NAMTMONTH5) AS NAMTMONTH5,SUM(NAMTMONTH6) AS NAMTMONTH6,
SUM(NAMTMONTH7) AS NAMTMONTH7,SUM(NAMTMONTH8) AS NAMTMONTH8,SUM(NAMTMONTH9) AS NAMTMONTH9,
SUM(NAMTMONTH10) AS NAMTMONTH10,SUM(NAMTMONTH11) AS NAMTMONTH11,SUM(NAMTMONTH12) AS NAMTMONTH12
FROM FM_CURRENTEFFECTIBUDGET WHERE COMPANYID = 6205 AND RECORDSTATUS <> 'INACTIVE'
--AND ISACCOUNTBUDGET = 'Y'
GROUP BY COMPANYID, BUDGETYEAR, ACCOUNTCODEID, ACCOUNTCODE , ACCOUNTCODENAME--,PLANMAINID, PLANMAINNAME, PLANMAINCODE
) T ON ACC.ACCOUNTCODEID = T.ACCOUNTCODEID
START WITH ACC.ACCOUNTCODEID = T.ACCOUNTCODEID
CONNECT BY PRIOR  ACC.PARENTCODEID = ACC.ACCOUNTCODEID
ORDER BY ACC.FIRSTORDER, ACC.SECONDORDER

2.根据1的sql,我们可以轻松得到所有层级的数据,但是呢,父级上并没有进行汇总下级的金额,所以改造为:

SELECT 
ACCOUNTCODEID, PARENTCODEID, ACCOUNTCODE, ACCOUNTCODENAME, FIRSTORDER, SECONDORDER,
PLANMAINID, PLANMAINNAME, PLANMAINCODE, ISACCOUNT,ISADD,
COMPANYID, BUDGETYEAR, 
SUM(NAMTMONTH1) AS NAMTMONTH1,
SUM(NAMTMONTH2) AS NAMTMONTH2,
SUM(NAMTMONTH3) AS NAMTMONTH3,
SUM(NAMTMONTH4) AS NAMTMONTH4,
SUM(NAMTMONTH5) AS NAMTMONTH5,
SUM(NAMTMONTH6) AS NAMTMONTH6,
SUM(NAMTMONTH7) AS NAMTMONTH7,
SUM(NAMTMONTH8) AS NAMTMONTH8,
SUM(NAMTMONTH9) AS NAMTMONTH9,
SUM(NAMTMONTH10) AS NAMTMONTH10,
SUM(NAMTMONTH11) AS NAMTMONTH11,
SUM(NAMTMONTH12) AS NAMTMONTH12
FROM (
SELECT CONNECT_BY_ROOT ACCOUNTCODEID ACCOUNTCODEID,CONNECT_BY_ROOT PARENTCODEID PARENTCODEID,
CONNECT_BY_ROOT ACCOUNTCODE ACCOUNTCODE,CONNECT_BY_ROOT ACCOUNTCODENAME ACCOUNTCODENAME,
CONNECT_BY_ROOT FIRSTORDER FIRSTORDER,CONNECT_BY_ROOT SECONDORDER SECONDORDER,
CONNECT_BY_ROOT COMPANYID COMPANYID,CONNECT_BY_ROOT BUDGETYEAR BUDGETYEAR,
CONNECT_BY_ROOT PLANMAINID PLANMAINID,CONNECT_BY_ROOT PLANMAINNAME PLANMAINNAME,
CONNECT_BY_ROOT PLANMAINCODE PLANMAINCODE,CONNECT_BY_ROOT ISACCOUNT ISACCOUNT,
CONNECT_BY_ROOT ISADD ISADD,
A.NAMTMONTH1,A.NAMTMONTH2,A.NAMTMONTH3,A.NAMTMONTH4,A.NAMTMONTH5,A.NAMTMONTH6,A.NAMTMONTH7,A.NAMTMONTH8,
A.NAMTMONTH9,A.NAMTMONTH10,A.NAMTMONTH11,A.NAMTMONTH12
FROM (
SELECT DISTINCT ACC.ACCOUNTCODEID, ACC.PARENTCODEID, ACC.ACCOUNTCODE, ACC.ACCOUNTCODENAME, 
 ACC.FIRSTORDER, ACC.SECONDORDER,T.PLANMAINID, T.PLANMAINNAME, T.PLANMAINCODE,'Y' AS ISACCOUNT,
CASE WHEN NVL(T.ACCOUNTCODEID,0) =0 THEN 'Y' ELSE 'N' END AS ISADD,
T.COMPANYID, T.BUDGETYEAR, 
T.NAMTMONTH1,T.NAMTMONTH2,T.NAMTMONTH3,T.NAMTMONTH4,T.NAMTMONTH5,T.NAMTMONTH6,T.NAMTMONTH7,T.NAMTMONTH8,
T.NAMTMONTH9,T.NAMTMONTH10,T.NAMTMONTH11,T.NAMTMONTH12
FROM FM_ACCOUNTCODE ACC
LEFT JOIN (
SELECT COMPANYID, BUDGETYEAR, ACCOUNTCODEID, ACCOUNTCODE , ACCOUNTCODENAME, 0 AS PLANMAINID, NULL AS PLANMAINNAME,NULL AS PLANMAINCODE,
SUM(NAMTMONTH1) AS NAMTMONTH1,SUM(NAMTMONTH2) AS NAMTMONTH2,SUM(NAMTMONTH3) AS NAMTMONTH3,
SUM(NAMTMONTH4) AS NAMTMONTH4,SUM(NAMTMONTH5) AS NAMTMONTH5,SUM(NAMTMONTH6) AS NAMTMONTH6,
SUM(NAMTMONTH7) AS NAMTMONTH7,SUM(NAMTMONTH8) AS NAMTMONTH8,SUM(NAMTMONTH9) AS NAMTMONTH9,
SUM(NAMTMONTH10) AS NAMTMONTH10,SUM(NAMTMONTH11) AS NAMTMONTH11,SUM(NAMTMONTH12) AS NAMTMONTH12
FROM FM_CURRENTEFFECTIBUDGET WHERE COMPANYID = 6205 AND RECORDSTATUS <> 'INACTIVE'
--AND ISACCOUNTBUDGET = 'Y'
GROUP BY COMPANYID, BUDGETYEAR, ACCOUNTCODEID, ACCOUNTCODE , ACCOUNTCODENAME--,PLANMAINID, PLANMAINNAME, PLANMAINCODE
) T ON ACC.ACCOUNTCODEID = T.ACCOUNTCODEID
START WITH ACC.ACCOUNTCODEID = T.ACCOUNTCODEID
CONNECT BY PRIOR  ACC.PARENTCODEID = ACC.ACCOUNTCODEID
ORDER BY ACC.FIRSTORDER, ACC.SECONDORDER
) A
CONNECT BY PRIOR ACCOUNTCODEID = PARENTCODEID
)
GROUP BY ACCOUNTCODEID, PARENTCODEID, ACCOUNTCODE, ACCOUNTCODENAME, FIRSTORDER, SECONDORDER,
COMPANYID, BUDGETYEAR,PLANMAINID, PLANMAINNAME, PLANMAINCODE,ISACCOUNT,ISADD ORDER BY FIRSTORDER, SECONDORDER

此文章仅作为笔者工作中遇到的问题记录,同时希望能帮助到其他同学

<think>我们正在处理一个关于MySQL递归查询的问题。用户要求提供向上和向下递归查询的SQL语句示例。根据引用内容,我们知道MySQL支持多种递归查询方式,包括自定义函数、纯SQL实现(通常适用于较老版本)以及MySQL8.0+引入的WITHRECURSIVE语法。由于用户明确要求SQL语句示例,并且希望包括向上和向下两个方向,我们将重点使用MySQL8.0+的WITHRECURSIVE语法,因为这是官方推荐且较为现代的方式。同时,我们也会简单提及其他方法,但主要示例将使用WITHRECURSIVE。向下递归(从父节点到所有子节点):通常用于查找一个部门下的所有子部门,或者一个分类下的所有子分类等。向上递归(从子节点到根节点):通常用于查找一个部门的所有上级部门,直到根节点。我们将使用一个部门表(departments)作为示例,表结构假设如下:-id:部门ID(主键)-name:部门名称-parent_id:上级部门ID(如果是顶级部门,则为NULL)下面分别给出向下递归向上递归的示例。###向下递归(查找指定部门的所有下级部门)假设我们要查找ID为1的部门(例如“技术部”)的所有下级部门(包括直接下级的子部门,以及子部门的子部门,直到最末级)。```sqlWITHRECURSIVESubDepsAS(--初始查询:选择顶级部门(这里指定根部门ID为1)SELECTid,name,parent_idFROMdepartmentsWHEREid=1--这里也可以根据其他条件选择,比如根据部门名称选择UNIONALL--递归查询:查找当前部门的所有直接下级部门SELECTd.id,d.name,d.parent_idFROMdepartmentsdINNERJOINSubDepssdONd.parent_id=sd.id)SELECT*FROMSubDeps;```解释:1.初始查询部分:选择我们指定的起始部门(id=1)。2.递归部分:通过连接`departments`表和递归CTE(SubDeps)来查找所有直接下级(即`departments`表中parent_id等于当前递归结果id的记录)。3.最终查询:从CTE中选取所有记录,包括初始部门和所有递归找到的下级部门。###向上递归(查找指定部门的所有上级部门)假设我们要查找ID为5的部门(例如某个子部门)的所有上级部门(包括直接上级,以及上级的上级,直到根部门)。```sqlWITHRECURSIVESuperDepsAS(--初始查询:选择起始部门(这里指定部门ID为5)SELECTid,name,parent_idFROMdepartmentsWHEREid=5UNIONALL--递归查询:查找当前部门的直接上级部门SELECTd.id,d.name,d.parent_idFROMdepartmentsdINNERJOINSuperDepssdONd.id=sd.parent_id)SELECT*FROMSuperDeps;```解释:1.初始查询部分:选择我们指定的起始部门(id=5)。2.递归部分:通过连接`departments`表和递归CTE(SuperDeps)来查找所有直接上级(即`departments`表中id等于当前递归结果parent_id的记录)。注意这里连接条件是`d.id=sd.parent_id`,因为我们要找的是当前记录的父记录。3.最终查询:从CTE中选取所有记录,包括起始部门和所有递归找到的上级部门。###注意事项1.递归深度:MySQL默认对递归深度有限制(通过`cte_max_recursion_depth`系统变量控制,默认1000层)。如果递归层级超过限制,查询会报错。可以通过设置会话变量调整,例如:`SETSESSIONcte_max_recursion_depth=10000;`。2.循环引用:如果数据中存在循环引用(例如A的父部门是B,而B的父部门又是A),递归查询会陷入死循环。MySQL默认会检测循环(通过检查同一行是否在递归过程中重复出现),并报错(`Recursivequeryabortedafter1001iterations`)。可以通过添加循环检测条件避免,例如在向上递归中,我们可以检查是否到达了根节点(parent_id为NULL)来终止。3.结果顺序:在递归查询中,结果的顺序并不保证是层级顺序。如果需要按层级顺序输出,可以在CTE中增加一个表示层级的列(如level),并在最终查询时按层级排序。###扩展:增加层级路径的递归查询有时我们不仅需要查询出所有部门,还需要知道层级路径(例如每个部门的完整路径)。我们可以修改CTE,在递归过程中拼接路径。####向下递归并拼接路径(从根到叶子)```sqlWITHRECURSIVESubDepsAS(SELECTid,name,parent_id,CAST(nameASCHAR(255))ASpath--初始路径就是当前部门名称FROMdepartmentsWHEREid=1UNIONALLSELECTd.id,d.name,d.parent_id,CONCAT(sd.path,'>',d.name)--将当前部门名称添加到路径末尾FROMdepartmentsdINNERJOINSubDepssdONd.parent_id=sd.id)SELECT*FROMSubDeps;```####向上递归并拼接路径(从叶子到根)```sqlWITHRECURSIVESuperDepsAS(SELECTid,name,parent_id,CAST(nameASCHAR(255))ASpathFROMdepartmentsWHEREid=5UNIONALLSELECTd.id,d.name,d.parent_id,CONCAT(d.name,'>',sd.path)--将上级部门名称添加到路径前面FROMdepartmentsdINNERJOINSuperDepssdONd.id=sd.parent_id)SELECT*FROMSuperDeps;```注意:在向上递归拼接路径时,我们是从当前部门开始,然后不断将上级部门名称加到前面,因此路径是从根到当前部门的顺序(如:公司>技术部>后端组)。如果希望从当前部门到根的顺序(如:后端组>技术部>公司),则初始路径直接使用当前部门名称,然后在递归时用`CONCAT(sd.path,'>',d.name)`。###其他方法(简要说明)1.**自定义函数**:在MySQL8.0之前,没有递归CTE,可以通过创建存储函数来实现递归。但是自定义函数编写复杂,且性能可能较差,不推荐。2.**纯SQL实现**:对于层级固定的情况,可以通过多次LEFTJOIN来实现,但层级不固定时无法使用。###总结本文介绍了使用MySQL8.0+的递归CTE实现向下和向上递归查询的方法,并提供了拼接路径的示例。递归CTE是处理树形结构数据的强大工具,适用于部门层级、分类层级等场景。§§相关问题§§1.递归查询的性能如何优化?2.在MySQL5.7或更早版本中如何实现递归查询?3.递归查询中如何处理循环引用问题?</think>### MySQL递归查询:上下层级SQL语句示例 在MySQL中处理层级结构数据(如部门树、分类树)时,递归查询是关键技术。以下是基于MySQL 8.0+的`WITH RECURSIVE`语法实现**向下查询子节点**和**向上查询父节点**的完整示例: --- #### **1. 数据准备(示例表结构)** ```sql CREATE TABLE departments ( id INT PRIMARY KEY, name VARCHAR(50), parent_id INT NULL, INDEX (parent_id) ); INSERT INTO departments (id, name, parent_id) VALUES (1, '总公司', NULL), (2, '技术部', 1), (3, '市场部', 1), (4, '前端组', 2), (5, '后端组', 2), (6, 'UI设计组', 4); ``` --- #### **2. 向下递归查询(查找所有子节点)** **场景**:查询"技术部"(id=2)及其所有子部门 ```sql WITH RECURSIVE SubDeps AS ( -- 初始查询:选择根节点 SELECT id, name, parent_id, 0 AS level FROM departments WHERE id = 2 -- 技术部ID UNION ALL -- 递归查询:逐层查找子节点 SELECT d.id, d.name, d.parent_id, sd.level + 1 FROM departments d INNER JOIN SubDeps sd ON d.parent_id = sd.id ) SELECT * FROM SubDeps; ``` **结果示例**: | id | name | parent_id | level | |----|-----------|-----------|-------| | 2 | 技术部 | 1 | 0 | | 4 | 前端组 | 2 | 1 | | 5 | 后端组 | 2 | 1 | | 6 | UI设计组 | 4 | 2 | --- #### **3. 向上递归查询(查找所有父节点)** **场景**:查询"UI设计组"(id=6)的所有上级部门 ```sql WITH RECURSIVE ParentDeps AS ( -- 初始查询:选择当前节点 SELECT id, name, parent_id, 0 AS level FROM departments WHERE id = 6 -- UI设计组ID UNION ALL -- 递归查询:逐层查找父节点 SELECT d.id, d.name, d.parent_id, pd.level - 1 FROM departments d INNER JOIN ParentDeps pd ON d.id = pd.parent_id ) SELECT * FROM ParentDeps ORDER BY level; ``` **结果示例**: | id | name | parent_id | level | |----|-----------|-----------|-------| | 1 | 总公司 | NULL | -2 | | 2 | 技术部 | 1 | -1 | | 4 | 前端组 | 2 | 0 | | 6 | UI设计组 | 4 | 1 | > **关键说明**: > - `level`列表示层级深度(正数向下/负数向上) > - 向上查询时通过`INNER JOIN ParentDeps pd ON d.id = pd.parent_id`连接父表 > - 结果按`level`排序可直观显示层级路径[^3] --- #### **4. 拼接完整路径(增强版)** **生成部门完整路径**(如"总公司/技术部/前端组/UI设计组"): ```sql WITH RECURSIVE PathCTE AS ( SELECT id, name, parent_id, CAST(name AS CHAR(255)) AS full_path FROM departments WHERE parent_id IS NULL -- 从根节点开始 UNION ALL SELECT d.id, d.name, d.parent_id, CONCAT(pc.full_path, '/', d.name) FROM departments d INNER JOIN PathCTE pc ON d.parent_id = pc.id ) SELECT * FROM PathCTE; ``` --- #### **5. 兼容旧版本MySQL的解决方案** 对于 **MySQL 5.7及以下版本**(不支持`WITH RECURSIVE`),可通过存储过程实现递归: ```sql DELIMITER // CREATE PROCEDURE FindChildren(IN dep_id INT) BEGIN -- 创建临时表存储结果 CREATE TEMPORARY TABLE IF NOT EXISTS TempDepts ( id INT, name VARCHAR(50), level INT ); -- 递归逻辑实现 INSERT INTO TempDepts SELECT id, name, 0 FROM departments WHERE id = dep_id; REPEAT INSERT INTO TempDepts SELECT d.id, d.name, MAX(t.level)+1 FROM departments d JOIN TempDepts t ON d.parent_id = t.id WHERE d.id NOT IN (SELECT id FROM TempDepts); UNTIL ROW_COUNT() = 0 END REPEAT; SELECT * FROM TempDepts; DROP TEMPORARY TABLE TempDepts; END // DELIMITER ; -- 调用示例:CALL FindChildren(2); ``` > **注意**:存储过程方案性能较低,建议优先使用MySQL 8.0+的递归CTE[^1]。 --- ### 关键注意事项 1. **循环引用检测**:若数据存在循环(如A→B→A),MySQL会抛出`Recursive query aborted`错误,需确保数据无环路。 2. **递归深度限制**:通过`@@cte_max_recursion_depth`控制(默认1000层),超限时需调整: ```sql SET SESSION cte_max_recursion_depth = 10000; ``` 3. **性能优化**:确保`parent_id`字段有索引,大数据量时限制递归深度[^2]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

用心_承载未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值