如何通过SQL窗口函数实现包含父级分类汇总的层级结构报表?

轻静酱_4525

轻静酱_4525

2026-06-06

471人浏览

原创

SUM() OVER() 不能直接跨层级求父级汇总,因其窗口函数仅按 PARTITION BY 和 ORDER BY 定义的逻辑帧计算,不理解树形层级;需先用递归CTE或自连接补全祖先路径(如 root_id),再基于该字段 PARTITION BY 实现正确汇总。

如何通过sql窗口函数实现包含父级分类汇总的层级结构报表?

为什么 SUM() OVER() 不能直接跨层级求父级汇总

窗口函数默认按 ORDER BY 和 PARTITION BY 定义的逻辑帧计算,它天然不理解树形层级关系。比如你有三级分类(一级→二级→三级),想在每行显示“所属一级分类的总销售额”,SUM(sales) OVER(PARTITION BY level1_id) 看似可行,但前提是数据里必须显式存有 level1_id 字段——而真实业务表往往只存直接父ID(parent_id),没有预先展开的祖先路径。

常见错误现象:NULL 值大量出现、汇总值重复累加、某级分类总和对不上手工核对结果。

  • 必须先通过递归 CTE 或自连接把每条记录的顶层分类 ID 补全,再用 SUM() OVER(PARTITION BY top_level_id)
  • MySQL 8.0+、PostgreSQL、SQL Server 2017+ 支持递归 CTE;旧版 MySQL 需用自连接模拟(最多支持固定深度)
  • 避免在窗口函数中嵌套子查询来查父级汇总——这会退化为相关子查询,性能极差,N 行 × N 次 IO

用递归 CTE 构建完整祖先路径(以 PostgreSQL 为例)

核心是把原始的 category_id → parent_id 关系,逐层向上追溯,直到 parent_id IS NULL,并把根 ID 记为 root_id。

WITH RECURSIVE category_tree AS (
  -- 基础:所有叶子/中间节点
  SELECT id, name, parent_id, id AS root_id, 0 AS depth
  FROM categories
  WHERE parent_id IS NULL
  UNION ALL
  -- 递归:找每个节点的子节点,并继承其 root_id
  SELECT c.id, c.name, c.parent_id, ct.root_id, ct.depth + 1
  FROM categories c
  INNER JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT 
  c.id,
  c.name,
  c.root_id,
  s.sales_amount,
  SUM(s.sales_amount) OVER(PARTITION BY c.root_id) AS root_total_sales
FROM category_tree c
JOIN sales_summary s ON c.id = s.category_id;

注意:UNION ALL 不去重,确保父子链完整;若存在环(如 A→B→A),需加 depth 限制防死循环。

GROUPING SETS 和窗口函数混用时的层级对齐陷阱

当报表既要明细行,又要小计行(如每级分类合计),有人会倾向用 GROUPING SETS + 窗口函数。但要注意:窗口函数在 GROUP BY 后执行,若分组粒度比目标父级更细(例如按三级分类分组,却想算一级汇总),SUM() OVER(PARTITION BY level1_id) 仍有效;但若用了 ROLLUP,生成的 NULL 分组线会让 PARTITION BY 行为异常——它会把所有 NULL 归为同一组。

PostgreSQL 18.4 ubuntu
PostgreSQL 18.4 ubuntu

PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。

下载
  • 安全做法:先用 CTE 展平层级(补 root_id、level1_id 等字段),再在最终 SELECT 中用窗口函数
  • 避免在 GROUPING SETS 的主查询里直接 PARTITION BY GROUPING_ID()——语义不清,各数据库行为不一致
  • Oracle 用户注意:CONNECT BY 的 LEVEL 伪列不能直接用于 OVER() 的 PARTITION BY,需先转成普通列

性能敏感点:为什么索引要覆盖 parent_id 和 id

递归 CTE 的性能瓶颈几乎全在 JOIN 效率上。每次递归都需根据子节点的 parent_id 查父节点的 id,若没索引,就是全表扫描 × 递归深度。

实操建议:

  • 在 categories(parent_id, id) 上建联合索引(顺序不能反,因为查询条件是 WHERE parent_id = ?)
  • PostgreSQL 还可考虑 INDEX CONCURRENTLY 避免锁表;SQL Server 注意统计信息是否及时更新
  • 如果层级深(>10 层)且查询频繁,不如物化祖先路径到宽表(如加字段 ancestors_path TEXT CHECK (ancestors_path ~ '^(\d+,)*\d+$')),用字符串函数提取根 ID

最易被忽略的是:递归结果集默认无序,若后续依赖顺序(比如只取每层第一个节点),必须显式加 ORDER BY —— 而这个 ORDER BY 无法下推到递归内部,可能触发额外排序。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

2023.10.12

4583

8

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.27

951

4

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

2024.02.23

1209

5

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

6561

10

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

3323

4

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

2024.04.07

6520

11

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

2024.04.29

8961

6

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1210

5

sql中删除一列的命令是什么
sql中删除一列的命令是什么

在sql中,使用alter table语句可以删除一列,语法为:alter table table_name drop column column_name。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1072

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL vs MySQL
PostgreSQL vs MySQL

共1课时 | 192人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 20.7万人学习

PostgreSQL 手册
PostgreSQL 手册

共0课时 | 1万人学习