SQL Server如何利用公用表表达式进行分组汇总_通过CTE结构实现

阿敏同学_7314

阿敏同学_7314

2026-06-16

434人浏览

原创

CTE在分组场景的核心价值是提前提炼分组逻辑供主查询复用,而非直接执行汇总;必须在CTE内完成全部聚合,主查询仅轻量关联,否则性能受损且成果被绕过。

sql server如何利用公用表表达式进行分组汇总_通过cte结构实现

CTE 本身不执行分组汇总,它只是把分组逻辑“提前提炼出来”,让主查询能干净地复用结果——这才是它在分组场景下真正不可替代的价值。

为什么不能直接在 CTE 里写 GROUP BY 后再 JOIN?

很多人试图这样写:

WITH sales_agg AS (
  SELECT SalesPersonID, COUNT(*) AS cnt
  FROM Sales.SalesOrderHeader
  GROUP BY SalesPersonID
),
order_detail_agg AS (
  SELECT SalesOrderID, SUM(OrderQty) AS total_qty
  FROM Sales.SalesOrderDetail
  GROUP BY SalesOrderID
)
SELECT s.SalesPersonID, s.cnt, d.total_qty
FROM sales_agg s
JOIN Sales.SalesOrderHeader h ON s.SalesPersonID = h.SalesPersonID
JOIN order_detail_agg d ON h.SalesOrderID = d.SalesOrderID;

问题在于:主查询又把 SalesOrderHeader 拉进来做 JOIN,等于绕过了 sales_agg 的聚合成果,中间结果集可能暴涨数倍。CTE 的价值被完全浪费。

正确做法是:所有聚合必须在 CTE 内完成,主查询只做轻量关联或过滤。

  • CTE 中的 GROUP BY 必须覆盖最终需要的所有分组维度(比如既要按人、又要按年,就得在 CTE 里一起分)
  • 如果主表小、明细表大(如 orders 表 10 万行,order_items 表 800 万行),先在 CTE 里对 order_items 按 order_id 聚合,再 JOIN orders,性能提升往往超过 5 倍
  • SQL Server 不会自动物化 CTE;若同一个 CTE 被引用多次(如既用于 JOIN 又用于 WHERE EXISTS),优化器可能重复执行其内部查询——这时应考虑用临时表替代

多个分组逻辑共存时怎么组织 CTE?

当你要同时计算「每个销售员的订单数」「每个销售员的退货数」「每个销售员的平均订单金额」,别堆在一个 CTE 里硬塞所有字段,容易失控。

推荐分层定义:

WITH sales_orders AS (
  SELECT SalesPersonID, COUNT(*) AS order_cnt, AVG(TotalDue) AS avg_order_amt
  FROM Sales.SalesOrderHeader
  WHERE Status = 5  -- 已发货
  GROUP BY SalesPersonID
),
sales_returns AS (
  SELECT SalesPersonID, COUNT(*) AS return_cnt
  FROM Sales.SalesOrderHeader h
  JOIN Sales.SalesOrderDetail d ON h.SalesOrderID = d.SalesOrderID
  WHERE d.ProductID IN (SELECT ProductID FROM Production.Product WHERE FinishedGoodsFlag = 0)
  GROUP BY SalesPersonID
)
SELECT 
  o.SalesPersonID,
  o.order_cnt,
  ISNULL(r.return_cnt, 0) AS return_cnt,
  o.avg_order_amt
FROM sales_orders o
LEFT JOIN sales_returns r ON o.SalesPersonID = r.SalesPersonID;
  • 每个 CTE 职责单一,调试和修改都只影响局部
  • sales_returns 里嵌套了子查询,但这是允许的;CTE 内部可以含任意合法 SELECT,只要不出现在递归成员中
  • 列名列表 (SalesPersonID, order_cnt, ...) 在多 CTE 场景下建议显式写出,避免因字段顺序错位导致静默错误

递归 CTE 里能做分组汇总吗?

不能。SQL Server 明确禁止在递归成员中使用 GROUP BY、SUM()、COUNT()、DISTINCT 等聚合操作。

Image Creator
Image Creator

一款集成到Photoshop工作流中的AI图像生成插件,支持文字生图、图像生成与局部编辑等能力,辅助完成视觉设计。

下载

典型错误写法:

WITH dept_tree AS (
  SELECT DepartmentID, ParentID, 1 AS Level
  FROM HumanResources.Department
  WHERE ParentID IS NULL
  UNION ALL
  SELECT d.DepartmentID, d.ParentID, t.Level + 1
  FROM HumanResources.Department d
  INNER JOIN dept_tree t ON d.ParentID = t.DepartmentID
  GROUP BY d.DepartmentID, d.ParentID  -- ❌ 报错:递归成员中不允许 GROUP BY
)

正确路径永远是两步分离:

  • 第一步:用递归 CTE 展开完整层级关系(例如生成 RootID → DescendantID 映射表)
  • 第二步:用这个结果 JOIN 员工表,再在外层 GROUP BY RootID 汇总人数或薪资
  • 锚点成员和递归成员之间必须用 UNION ALL,且递归成员的列数、类型、顺序必须与锚点严格一致

CTE 分组后怎么更新原始表?

CTE 可以作为 UPDATE、DELETE 的目标,但前提是 CTE 引用的必须是单个基表,且不能含聚合、DISTINCT、窗口函数等不可更新元素。

比如你想把每个部门的员工数写回 Department 表的 EmployeeCount 字段:

WITH dept_emp_count AS (
  SELECT d.DepartmentID, COUNT(e.BusinessEntityID) AS emp_cnt
  FROM HumanResources.Department d
  LEFT JOIN HumanResources.EmployeeDepartmentHistory e ON d.DepartmentID = e.DepartmentID
  GROUP BY d.DepartmentID
)
UPDATE d
SET d.EmployeeCount = c.emp_cnt
FROM HumanResources.Department d
INNER JOIN dept_emp_count c ON d.DepartmentID = c.DepartmentID;
  • 这里 dept_emp_count 是纯聚合 CTE,不能直接 UPDATE dept_emp_count —— 它不是可更新的视图
  • 必须通过 JOIN 回原始表 Department,再在 UPDATE 子句中指定目标
  • 若 CTE 包含 JOIN 多个表或聚合,就只能用于 SELECT,别指望它能当更新入口

最易被忽略的一点:CTE 的生命周期只限于紧随其后的那一条语句。写完 WITH ... AS (...) SELECT ... 之后,那个名字就彻底失效了——想复用,要么重写,要么转成临时表或视图。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

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

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

2023.10.12

4883

8

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

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

2023.10.27

991

4

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

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

2024.02.23

1289

5

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

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

2024.03.06

6921

10

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

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

2024.03.06

3603

4

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

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

2024.04.07

6920

11

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

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

2024.04.29

9641

6

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

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

2024.04.29

1310

5

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

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

2024.04.29

1152

5

热门下载

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

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.3万人学习