如何递归地对 SQL 中的所有子树求和?
How to SUM all subchildren tree in SQL recursively?
你好,我在这个问题上纠结了一段时间><
我在树结构中有 4 个类别。
tenant_category_transaction_view:
我想要每个类别children“sumSubtotal”的总和
类似的东西:
我已经非常接近了...但是有些东西我不明白 >><"
with recursive cte (sumSubtotal, sumQuantity, id, idParentCategory, treeSum, depth) as (
select root.sumSubtotal, -- STEP 1
root.sumQuantity,
root.id,
root.idParentCategory,
root.sumSubtotal as treeSum,
0 as depth
from tenant_category_transaction_view as root
union all -- LOOP THROUGH ALL ROOT ROWS AND ADD ROWS TO THE CTE WITH THE INNER JOIN
select child.sumSubtotal, -- STEP 3
child.sumQuantity,
child.id,
child.idParentCategory,
(cte.treeSum + child.sumSubtotal) AS treeSum,
(cte.depth + 1) AS depth
from tenant_category_transaction_view AS child
inner join cte on child.idParentCategory = cte.id -- STEP 2
)
select sumSubtotal, sumQuantity, id, idParentCategory, treeSum, depth -- STEP 4
from cte
以上查询结果:
我似乎生成了正确的 treeSum 但只有一个分支颠倒了
你能帮我一把吗?
感谢您的宝贵时间:)
我更新了 fiddle 以包含问题中提供的确切架构/数据,包括空问题。它还包括我建议的更改示例。
该解决方案基本上采用给定数据并在内部对其进行转换(在 CTE 术语 nodes
中),以便 2 个顶级类别行 link 成为一个公共行,id 为 0,因此我提供的原始逻辑可用于将其视为一个类别的分层列表。
首先,我们递归地找到所有分支列表。每个分支都由相应的根标识。然后,我们汇总每个 root/branch.
的节点数量
WITH RECURSIVE nodes AS (
SELECT id, COALESCE(idParentCategory, 0) AS idParentCategory
, sumSubtotal, sumQuantity
FROM tenant_category_transaction_view
UNION
SELECT 0, null, 0, 0
)
, cte AS (
SELECT t.*, t.id AS root
, idParentCategory AS idParentCategory0
, sumSubtotal AS sumSubtotal0
, sumQuantity AS sumQuantity0
FROM nodes AS t
UNION ALL
SELECT t.* , t0.root
, t0.idParentCategory0
, t0.sumSubtotal0
, t0.sumQuantity0
FROM cte AS t0
JOIN nodes AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(idParentCategory0) AS idParentCategory
, MIN(sumSubtotal0) AS sumSubtotal
, MIN(sumQuantity0) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
GROUP BY root
ORDER BY root
;
结果:
root
idParentCategory
sumSubtotal
sumQuantity
total
0
null
0
0
9890
1
0
9800
98
9800
4
0
20
1
90
5
4
30
1
70
6
5
40
1
40
设置:
CREATE TABLE tenant_category_transaction_view (
id int primary key
, idParentCategory int
, sumSubtotal int
, sumQuantity int
);
INSERT INTO tenant_category_transaction_view VALUES
(1, null, 9800, 98)
, (4, null, 20, 1)
, (5, 4, 30, 1)
, (6, 5, 40, 1)
;
以下使用建议对原table和数据进行微调。而不是 id 为 1 和 4 的行的 2 个顶部空父引用,添加一个顶行(例如,id 为 99)并让 id 为 2 和 4 的行引用 parent = 99.
WITH RECURSIVE cte AS (
SELECT t.*, t.id AS root
FROM tenant_category_transaction_view AS t
UNION ALL
SELECT t.*, t0.root
FROM cte AS t0
JOIN tenant_category_transaction_view AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(t2.idParentCategory) AS idParentCategory
, MIN(t2.sumSubtotal) AS sumSubtotal
, MIN(t2.sumQuantity) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
JOIN tenant_category_transaction_view AS t2
ON t1.root = t2.id
GROUP BY root
ORDER BY root
;
结果:
root
idParentCategory
sumSubtotal
sumQuantity
total
99
null
0
0
9890
1
99
9800
98
9800
4
99
20
1
90
5
4
30
1
70
6
5
40
1
40
此外,这可以写成基于 t2.id 的聚合,这是主键,由于功能依赖,允许稍微简化。
WITH RECURSIVE cte AS (
SELECT t.*, t.id AS root
FROM tenant_category_transaction_view AS t
UNION ALL
SELECT t.*, t0.root
FROM cte AS t0
JOIN tenant_category_transaction_view AS t
ON t.idParentCategory = t0.id
)
SELECT t2.id
, t2.idParentCategory
, t2.sumSubtotal
, t2.sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
JOIN tenant_category_transaction_view AS t2
ON t1.root = t2.id
GROUP BY t2.id
ORDER BY t2.id
;
最后,我们可以通过在递归逻辑中携带其他根值来删除最后一个 JOIN:
WITH RECURSIVE cte AS (
SELECT t.*, t.id AS root
, idParentCategory AS idParentCategory0
, sumSubtotal AS sumSubtotal0
, sumQuantity AS sumQuantity0
FROM tenant_category_transaction_view AS t
UNION ALL
SELECT t.* , t0.root
, t0.idParentCategory0
, t0.sumSubtotal0
, t0.sumQuantity0
FROM cte AS t0
JOIN tenant_category_transaction_view AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(idParentCategory0) AS idParentCategory
, MIN(sumSubtotal0) AS sumSubtotal
, MIN(sumQuantity0) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
GROUP BY root
ORDER BY root
;
设置:
DROP TABLE IF EXISTS tenant_category_transaction_view;
CREATE TABLE tenant_category_transaction_view (
id int primary key
, idParentCategory int
, sumSubtotal int
, sumQuantity int
);
INSERT INTO tenant_category_transaction_view VALUES
(99, null, 0, 0)
, ( 1, 99, 9800, 98)
, ( 4, 99, 20, 1)
, ( 5, 4, 30, 1)
, ( 6, 5, 40, 1)
;
解决方法其实很简单:-)
- 在 CTE 中添加代表根节点的 hard-coded 行(其中 id 为 NULL)。请注意,我们可以在 CTE 中使用多个锚点查询(不引用 CTE 名称的查询)。
- 创建从所有节点到它们所有后代(包括它们自己)的所有路径。每条路径由一行表示,其中包含起始节点 ID、后代节点 ID 和后代节点的数量。请注意 null-safe 等于运算符 (<=>)
的使用
- 按起始节点 id 聚合,并为每个节点汇总其所有后代的数量
P.S.
“递归”查询没有任何递归。这是一个误导性的名称。这些实际上是迭代查询,而不是递归。
with recursive cte (root_id, id, sumSubtotal) as
(
select null
,null
,0
union all
select id
,id
,sumSubtotal
from tenant_category_transaction_view as tctv
union all
select cte.root_id
,tctv.id
,tctv.sumSubtotal
from cte
join tenant_category_transaction_view as tctv
on tctv.idParentCategory <=> cte.id
)
select root_id
,sum(sumSubtotal)
from cte
group by root_id
+---------+-------------+
| root_id | sumSubtotal |
+---------+-------------+
| null | 9890 |
| 1 | 9800 |
| 4 | 90 |
| 5 | 70 |
| 6 | 40 |
+---------+-------------+
你好,我在这个问题上纠结了一段时间><
我在树结构中有 4 个类别。
tenant_category_transaction_view:
我想要每个类别children“sumSubtotal”的总和
类似的东西:
我已经非常接近了...但是有些东西我不明白 >><"
with recursive cte (sumSubtotal, sumQuantity, id, idParentCategory, treeSum, depth) as (
select root.sumSubtotal, -- STEP 1
root.sumQuantity,
root.id,
root.idParentCategory,
root.sumSubtotal as treeSum,
0 as depth
from tenant_category_transaction_view as root
union all -- LOOP THROUGH ALL ROOT ROWS AND ADD ROWS TO THE CTE WITH THE INNER JOIN
select child.sumSubtotal, -- STEP 3
child.sumQuantity,
child.id,
child.idParentCategory,
(cte.treeSum + child.sumSubtotal) AS treeSum,
(cte.depth + 1) AS depth
from tenant_category_transaction_view AS child
inner join cte on child.idParentCategory = cte.id -- STEP 2
)
select sumSubtotal, sumQuantity, id, idParentCategory, treeSum, depth -- STEP 4
from cte
以上查询结果:
我似乎生成了正确的 treeSum 但只有一个分支颠倒了
你能帮我一把吗?
感谢您的宝贵时间:)
我更新了 fiddle 以包含问题中提供的确切架构/数据,包括空问题。它还包括我建议的更改示例。
该解决方案基本上采用给定数据并在内部对其进行转换(在 CTE 术语 nodes
中),以便 2 个顶级类别行 link 成为一个公共行,id 为 0,因此我提供的原始逻辑可用于将其视为一个类别的分层列表。
首先,我们递归地找到所有分支列表。每个分支都由相应的根标识。然后,我们汇总每个 root/branch.
的节点数量WITH RECURSIVE nodes AS (
SELECT id, COALESCE(idParentCategory, 0) AS idParentCategory
, sumSubtotal, sumQuantity
FROM tenant_category_transaction_view
UNION
SELECT 0, null, 0, 0
)
, cte AS (
SELECT t.*, t.id AS root
, idParentCategory AS idParentCategory0
, sumSubtotal AS sumSubtotal0
, sumQuantity AS sumQuantity0
FROM nodes AS t
UNION ALL
SELECT t.* , t0.root
, t0.idParentCategory0
, t0.sumSubtotal0
, t0.sumQuantity0
FROM cte AS t0
JOIN nodes AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(idParentCategory0) AS idParentCategory
, MIN(sumSubtotal0) AS sumSubtotal
, MIN(sumQuantity0) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
GROUP BY root
ORDER BY root
;
结果:
root | idParentCategory | sumSubtotal | sumQuantity | total |
---|---|---|---|---|
0 | null | 0 | 0 | 9890 |
1 | 0 | 9800 | 98 | 9800 |
4 | 0 | 20 | 1 | 90 |
5 | 4 | 30 | 1 | 70 |
6 | 5 | 40 | 1 | 40 |
设置:
CREATE TABLE tenant_category_transaction_view (
id int primary key
, idParentCategory int
, sumSubtotal int
, sumQuantity int
);
INSERT INTO tenant_category_transaction_view VALUES
(1, null, 9800, 98)
, (4, null, 20, 1)
, (5, 4, 30, 1)
, (6, 5, 40, 1)
;
以下使用建议对原table和数据进行微调。而不是 id 为 1 和 4 的行的 2 个顶部空父引用,添加一个顶行(例如,id 为 99)并让 id 为 2 和 4 的行引用 parent = 99.
WITH RECURSIVE cte AS (
SELECT t.*, t.id AS root
FROM tenant_category_transaction_view AS t
UNION ALL
SELECT t.*, t0.root
FROM cte AS t0
JOIN tenant_category_transaction_view AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(t2.idParentCategory) AS idParentCategory
, MIN(t2.sumSubtotal) AS sumSubtotal
, MIN(t2.sumQuantity) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
JOIN tenant_category_transaction_view AS t2
ON t1.root = t2.id
GROUP BY root
ORDER BY root
;
结果:
root | idParentCategory | sumSubtotal | sumQuantity | total |
---|---|---|---|---|
99 | null | 0 | 0 | 9890 |
1 | 99 | 9800 | 98 | 9800 |
4 | 99 | 20 | 1 | 90 |
5 | 4 | 30 | 1 | 70 |
6 | 5 | 40 | 1 | 40 |
此外,这可以写成基于 t2.id 的聚合,这是主键,由于功能依赖,允许稍微简化。
WITH RECURSIVE cte AS (
SELECT t.*, t.id AS root
FROM tenant_category_transaction_view AS t
UNION ALL
SELECT t.*, t0.root
FROM cte AS t0
JOIN tenant_category_transaction_view AS t
ON t.idParentCategory = t0.id
)
SELECT t2.id
, t2.idParentCategory
, t2.sumSubtotal
, t2.sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
JOIN tenant_category_transaction_view AS t2
ON t1.root = t2.id
GROUP BY t2.id
ORDER BY t2.id
;
最后,我们可以通过在递归逻辑中携带其他根值来删除最后一个 JOIN:
WITH RECURSIVE cte AS (
SELECT t.*, t.id AS root
, idParentCategory AS idParentCategory0
, sumSubtotal AS sumSubtotal0
, sumQuantity AS sumQuantity0
FROM tenant_category_transaction_view AS t
UNION ALL
SELECT t.* , t0.root
, t0.idParentCategory0
, t0.sumSubtotal0
, t0.sumQuantity0
FROM cte AS t0
JOIN tenant_category_transaction_view AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(idParentCategory0) AS idParentCategory
, MIN(sumSubtotal0) AS sumSubtotal
, MIN(sumQuantity0) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
GROUP BY root
ORDER BY root
;
设置:
DROP TABLE IF EXISTS tenant_category_transaction_view;
CREATE TABLE tenant_category_transaction_view (
id int primary key
, idParentCategory int
, sumSubtotal int
, sumQuantity int
);
INSERT INTO tenant_category_transaction_view VALUES
(99, null, 0, 0)
, ( 1, 99, 9800, 98)
, ( 4, 99, 20, 1)
, ( 5, 4, 30, 1)
, ( 6, 5, 40, 1)
;
解决方法其实很简单:-)
- 在 CTE 中添加代表根节点的 hard-coded 行(其中 id 为 NULL)。请注意,我们可以在 CTE 中使用多个锚点查询(不引用 CTE 名称的查询)。
- 创建从所有节点到它们所有后代(包括它们自己)的所有路径。每条路径由一行表示,其中包含起始节点 ID、后代节点 ID 和后代节点的数量。请注意 null-safe 等于运算符 (<=>) 的使用
- 按起始节点 id 聚合,并为每个节点汇总其所有后代的数量
P.S.
“递归”查询没有任何递归。这是一个误导性的名称。这些实际上是迭代查询,而不是递归。
with recursive cte (root_id, id, sumSubtotal) as
(
select null
,null
,0
union all
select id
,id
,sumSubtotal
from tenant_category_transaction_view as tctv
union all
select cte.root_id
,tctv.id
,tctv.sumSubtotal
from cte
join tenant_category_transaction_view as tctv
on tctv.idParentCategory <=> cte.id
)
select root_id
,sum(sumSubtotal)
from cte
group by root_id
+---------+-------------+
| root_id | sumSubtotal |
+---------+-------------+
| null | 9890 |
| 1 | 9800 |
| 4 | 90 |
| 5 | 70 |
| 6 | 40 |
+---------+-------------+