1、GROUP BY 与聚合函数

GROUP BY 是一种能将查询结果划分为多个行组的查询语句的子句,其目的通常是为了在每个组上执行一个或多个聚合运算,所以 GROUP BY 通常会与聚合函数一块儿出现在查询语句中。

GROUP BY 的标准分组方式是按所有分组字段的值依次来分组。假如字段 A 的值有 3 种,字段 B 的值有 2 种;如果是GROUP BY A,那么就会被分为 3 组;而如果是GROUP BY A,B,那么就会先被 A 分为 3 组,然后这 3 组又会被 B 再各自分为 2 组,最终会被分为 3×2 等于 6 组。

显然,GROUP BY B,A最终也会被分为 6 组,换而言之,标准分组时的字段的顺序不会对分组结果产生影响。但分组字段的顺序会影响查询结果的排序,如果想要改变结果集的排序,可以通过 ORDER BY 子句来实现。

示例一、查询统计学生 1、2、3 的第 1 次考试成绩,且按各科总分降序排列:

SELECT t.StudentId,COUNT(1) 科目数,
SUM(t.Scores) 总分,MAX(t.Scores) 最高分,MIN(t.Scores) 最低分,AVG(t.Scores) 平均分
FROM T_ExamResults t
WHERE t.Counts = 1 AND t.StudentId IN(1,2,3)
GROUP BY t.StudentId
ORDER BY 总分 DESC;

示例二、查询统计学生 1、2、3 的第 1 次考试成绩,且按班级名称和学生名称来升序排列:

SELECT t1.Code,t1.Name,t3.Name,COUNT(1) 科目数,
SUM(t2.Scores) 总分,MAX(t2.Scores) 最高分,MIN(t2.Scores) 最低分,AVG(t2.Scores) 平均分
FROM T_Students t1
JOIN T_ExamResults t2 ON t1.Id = t2.StudentId AND t2.Counts = 1
JOIN T_Classes t3 ON t1.ClassId = t3.Id
WHERE t1.Id IN(1,2,3)
GROUP BY t1.Code,t1.Name,t3.Name
ORDER BY t3.Name,t1.Name DESC;

注意:在含有 GROUP BY 子句的查询语句中,每组只会返回一行数据,且查询选择列表中的列只能是 GROUP BY 中的字段或聚合函数表达式。

2、GROUP BY 与 HAVING

HAVING 子句的作用有点类似于 WHERE 子句,说到底它们都是过滤数据用的,但不同的是,WHERE 子句过滤的最小单位是数据行,而 HAVING 子句过滤的最小单位是行组。相较于 WHERE 子句,HAVING 子句最大的优势就是支持聚合函数。

HAVING 子句只能在查询语句中使用,且通常与 GROUP BY 子句一起使用。如果查询语句中没有 GROUP BY 子句,那么就会有隐式的单一行组,但这通常是没有意义的。例如要查询统计平均分达到 80 分的学生第 1 次考试成绩,且按总分倒序排列,示例如下:

SELECT t.StudentId,COUNT(1) 科目数,
SUM(t.Scores) 总分,MAX(t.Scores) 最高分,MIN(t.Scores) 最低分,AVG(t.Scores) 平均分
FROM T_ExamResults t
WHERE t.Counts = 1
GROUP BY t.StudentId
HAVING AVG(t.Scores) >= 80
ORDER BY SUM(t.Scores) DESC;

3、GROUP BY 扩展分组

在实际的开发工作中,尤其是开发数据报表,往往需要统计多维度的小计和合计。大多数情况下用 UNION 也能达到类似效果,但实现起来比较繁琐,灵活性较差,性能往往也比较低。针对这类需求,SQL Server 提供了几个实用的扩展分组,以便能更好的实现这些需求。

3.1、GROUP BY ROLLUP

ROLLUP 是对 GROUP BY 子句的一种扩展,它允许计算标准分组及部分维度的小计及合计。ROLLUP 的计算结果与分组字段的顺序有关,因为它的分组过程具有方向性,先计算标准分组,然后从右到左递减计算更高一级的小计,直到所有字段被计算完,最后计算合计。

对于GROUP BY ROLLUP(a,b,c),结果具有 (a,b,c)、(a,b,NULL)、(a,NULL,NULL)、(NULL,NULL,NULL) 唯一值的组。换而言之,GROUP BY ROLLUP(a,b,c)的结果集就等价于GROUP BY a,b,c的结果集,加上GROUP BY a,b的结果集,再加上GROUP BY a的结果集,最后加上不带GROUP BY的总计结果集。

示例一、查询统计 1、2、3 班的学生个数及年龄:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId
FROM T_Students t
WHERE t.ClassId IN(1,2,3)
)
SELECT t.ClassId,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY ROLLUP(t.ClassId);

查询结果如下:

ClassId     学生个数        最大年龄        最小年龄
----------- ----------- ----------- -----------
1 10 20 15
2 9 20 16
3 9 21 15
NULL 28 21 15

示例二、查询统计 1、2 班的学生个数及年龄:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY ROLLUP(t.ClassId,t.Gender);

查询结果如下:

ClassId     Gender      学生个数        最大年龄        最小年龄
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
1 1 4 20 18
1 NULL 10 20 15
2 0 4 20 17
2 1 5 20 16
2 NULL 9 20 16
NULL NULL 19 20 15

示例三、查询统计 1、2 班的学生个数及年龄:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY t.ClassId,ROLLUP(t.Gender);

查询结果如下:

ClassId     Gender      学生个数        最大年龄        最小年龄
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
1 1 4 20 18
1 NULL 10 20 15
2 0 4 20 17
2 1 5 20 16
2 NULL 9 20 16

3.2、GROUP BY CUBE

CUBE 是对 GROUP BY 子句的一种扩展,它允许计算标准分组及所有维度的小计及合计。CUBE 会对所有可能的分组进行统计,从而生成交叉报表。CUBE 比 ROLLUP 的分组更多,完全包含了 ROLLUP 的统计结果,且计算结果与分组字段的顺序无关,但如果字段顺序不同,默认的结果集排序会有不同。

对于GROUP BY CUBE(a,b),结果具有 (a,b)、(a,NULL)、(NULL,b)、(NULL,NULL) 唯一值的组。换而言之,GROUP BY CUBE(a,b)的结果集就等价于GROUP BY a,b的结果集,加上GROUP BY a的结果集,再加上GROUP BY b的结果集,最后加上不带GROUP BY的总计结果集。

示例一、查询统计 1、2、3 班的学生个数及年龄:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId
FROM T_Students t
WHERE t.ClassId IN(1,2,3)
)
SELECT t.ClassId,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY CUBE(t.ClassId);

查询结果如下:

ClassId     学生个数        最大年龄        最小年龄
----------- ----------- ----------- -----------
1 10 20 15
2 9 20 16
3 9 21 15
NULL 28 21 15

示例二、查询统计 1、2 班的学生个数及年龄:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY CUBE(t.ClassId,t.Gender);

查询结果如下:

ClassId     Gender      学生个数        最大年龄        最小年龄
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
2 0 4 20 17
NULL 0 10 20 15
1 1 4 20 18
2 1 5 20 16
NULL 1 9 20 16
NULL NULL 19 20 15
1 NULL 10 20 15
2 NULL 9 20 16

示例三、查询统计 1、2 班的学生个数及年龄:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY t.ClassId,CUBE(t.Gender);

查询结果如下:

ClassId     Gender      学生个数        最大年龄        最小年龄
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
1 1 4 20 18
1 NULL 10 20 15
2 0 4 20 17
2 1 5 20 16
2 NULL 9 20 16

3.3、GROUP BY GROUPING SETS

GROUPING SETS 是对 GROUP BY 子句的一种扩展,它允许一次计算多个标准分组的小计。GROUPING SETS 的功能相当于将多个 GROUP BY 子句组合到一个 GROUP BY 子句中,类似于用 UNION ALL 合并多个 GROUP BY 的结果集,所以它的计算结果与排序字段的顺序无关,而且不会合并重复组。

例如GROUP BY GROUPING SETS(ROLLUP(A))GROUP BY ROLLUP(A)的结果集相同,GROUP BY GROUPING SETS(A,B)GROUP BY AGROUP BY B的结果集相同。示例如下:

WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,COUNT(1) 学生个数,MAX(t.Age) 最大年龄,MIN(t.Age) 最小年龄
FROM t
GROUP BY GROUPING SETS(ROLLUP(t.ClassId),CUBE(t.ClassId));

查询结果如下:

ClassId     学生个数        最大年龄        最小年龄
----------- ----------- ----------- -----------
1 10 20 15
2 9 20 16
NULL 19 20 15
1 10 20 15
2 9 20 16
NULL 19 20 15

GROUPING SETS 中还支持GROUP BY (),用于指定生成总计的空组。例如要查询统计浙江地区各级别行政区个数,及总计个数,示例如下:

SELECT t.Level 级别,COUNT(1) 个数
FROM T_Districts t
WHERE SUBSTRING(t.Code,1,2) = '33'
GROUP BY GROUPING SETS(t.Level,());

查询结果如下:

级别          个数
----------- -----------
1 1
2 6
3 10
NULL 17

4、GROUP BY 扩展函数

4.1、GROUPING 函数

GROUPING 函数用于指示当前行是否为聚合行,如果它返回 1 则表示聚合,相反,返回 0 则表示未聚合。仅当指定了 GROUP BY 时,GROUPING 才能在 SELECT 子句、HAVING 或 ORDER BY 子句中使用。

通常将一个分组字段作为该函数的参数,然后通过判断它的返回值来区分聚集行与常规行,从而进一步对结果集美化或过滤。示例如下:

SELECT GROUPING(t.Level) 标志,t.Level 级别,COUNT(1) 个数
FROM T_Districts t
WHERE SUBSTRING(t.Code,1,2) = '33'
GROUP BY GROUPING SETS(t.Level,());

查询结果如下:

标志   级别          个数
---- ----------- -----------
0 1 1
0 2 6
0 3 10
1 NULL 17

4.2、GROUPING_ID 函数

GROUPING 函数用于计算分组级别,它将返回与行相关联的 GROUPING 位向量对应的数值。GROUPING\_ID 按从左到右的顺序计算,如果是分组字段,则为 0,如果是小计或合计则为 1,然后按字段的顺序将计算结果组成二进制序列(位向量),最后将位向量转化为十进制数。仅当指定了 GROUP BY 时,GROUPING 才能在 SELECT 子句、HAVING 或 ORDER BY 子句中使用。

GROUPING_ID 函数在功能上等效于多个 GROUPING 函数,当查询结果有多个聚合级别时,使用该函数会更容易表达行过滤条件。示例如下:

SELECT t.CourseId,t.StudentId,t.Counts,MAX(t.Scores) Scores,
GROUPING_ID(t.CourseId) gc,GROUPING_ID(t.StudentId) gs,GROUPING_ID(t.Counts) gt,
GROUPING_ID(t.CourseId,t.StudentId) gcs,
GROUPING_ID(t.StudentId,t.Counts) gst,
GROUPING_ID(t.CourseId,t.Counts) gct,
GROUPING_ID(t.CourseId,t.StudentId,t.Counts) gcst
FROM T_ExamResults t
WHERE t.StudentId = 1 AND t.Counts = 2
GROUP BY ROLLUP(t.CourseId,t.StudentId,t.Counts);

查询结果如下:

CourseId   StudentId  Counts    Scores    gc      gs      gt      gcs     gst     gct     gcst
---------- ---------- --------- --------- ------- ------- ------- ------- ------- ------- -------
1 1 2 63.0 0 0 0 0 0 0 0
1 1 NULL 63.0 0 0 1 0 1 1 1
1 NULL NULL 63.0 0 1 1 1 3 1 3
2 1 2 98.0 0 0 0 0 0 0 0
2 1 NULL 98.0 0 0 1 0 1 1 1
2 NULL NULL 98.0 0 1 1 1 3 1 3
3 1 2 73.0 0 0 0 0 0 0 0
3 1 NULL 73.0 0 0 1 0 1 1 1
3 NULL NULL 73.0 0 1 1 1 3 1 3
NULL NULL NULL 98.0 1 1 1 3 3 3 7

5、本文小结

本文主要讲述了 SQL Server 中分组查询的相关知识点,包括 GROUP BY 与聚合函数、HAVING 联合使用及 GROUP BY 的标准分组、扩展分组、扩展函数等。

另外,不知道会不会有读者感到疑惑,为什么扩展分组返回的结果集中经常会出现 NULL 值?其实这是 NULL 的一个特殊应用,它在 ROLLUP、CUBE 或 GROUPING SETS 操作的结果集内作为字段的占位符,表示全体(数据)。

本文参考链接:

去导航目录篇下载创建本系列博文通用库表及数据的 SQL 语句

本文链接http://www.cnblogs.com/hanzongze/p/tsql-groupby.html

版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!个人博客,能力有限,若有不当之处,敬请批评指正,谢谢!

SQL Server温故系列(5):SQL 查询之分组查询 GROUP BY的更多相关文章

  1. SQL Server温故系列(0):导航目录

    创建本系列博文通用库表及数据的 SQL 语句:下载 SQL Server温故系列(0):导航目录 SQL Server温故系列(1):SQL 数据操作 CRUD 之增删改合 SQL Server温故系 ...

  2. SQL Server温故系列(2):SQL 数据操作 CRUD 之简单查询

    1.查询语句 SELECT 1.1.查询语句的 SELECT 子句 1.2.查询语句的 FROM 子句 1.2.1.内连接查询 INNER JOIN 1.2.2.外连接查询 OUTER JOIN 1. ...

  3. SQL Server温故系列(3):SQL 子查询 & 公用表表达式 CTE

    1.子查询 Subqueries 1.1.单行子查询 1.2.多行子查询 1.3.相关子查询 1.4.嵌套子查询 1.5.子查询小结及性能问题 2.公用表表达式 CTE 2.1.普通公用表表达式 2. ...

  4. SQL Server温故系列(1):SQL 数据操作 CRUD 之增删改合

    1.插入语句 INSERT INTO 1.1.用 INSERT 插入单行数据 1.2.用 INSERT 插入多行数据 1.3.用 INSERT 插入子查询结果行 1.4.INSERT 小结及特殊字段插 ...

  5. SQL Server温故系列(4):SQL 查询之集合运算 & 聚合函数

    1.集合运算 1.1.并集运算 UNION 1.2.差集运算 EXCEPT 1.3.交集运算 INTERSECT 1.4.集合运算小结 2.聚合函数 2.1.求行数函数 COUNT 2.2.求和函数 ...

  6. SQL Server Reporting Service(SSRS) 第二篇 SSRS数据分组Parent Group

    SQL Server Reporting Service(SSRS) 第一篇 我的第一个SSRS例子默认使用Table进行简单的数据显示,有时为了进行更加直观的数据显示,我们需要按照某个字段对列表进行 ...

  7. SQL Server调优系列基础篇(子查询运算总结)

    前言 前面我们的几篇文章介绍了一系列关于运算符的介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符.有兴 ...

  8. SQL Server调优系列进阶篇(查询语句运行几个指标值监测)

    前言 上一篇我们分析了查询优化器的工作方式,其中包括:查询优化器的详细运行步骤.筛选条件分析.索引项优化等信息. 本篇我们分析在我们运行的过程中几个关键指标值的检测. 通过这些指标值来分析语句的运行问 ...

  9. SQL Server调优系列玩转篇(如何利用查询提示(Hint)引导语句运行)

    前言 前面几篇我们分析了关于SQL Server关于性能调优的一系列内容,我把它分为两个模块. 第一个模块注重基础内容的掌握,共分7篇文章完成,内容涵盖一系列基础运算算法,详细分析了如何查看执行计划. ...

随机推荐

  1. 两个同名controller导致调用崩溃

    之前遇到一个很诡异的bug,大概情况如下: 生成成功,运行正常,调试正常 但是调用目标controller的目标方法,运行自动中断,调试自动中断 没有任何明确的错误提示,包括调试都没有弹窗报错 调用其 ...

  2. ASP .NET Controller返回类型

    返回类型 return View(model); 即返回htmlreturn Json("String"); 返回Json格式的数据return File(new byte[] { ...

  3. NoSQL Manager for MongoDB 4.6.0.3 带key

    NoSQL Manager for MongoDB 4.6.0.3 是一个Windows平台的MongoDB高级管理工具.请低调使用. 博客园文件一次最大不超过10M. 官方安装包: mongodbm ...

  4. JAVASCRIPT高程笔记-------第五章 引用类型

    一.Object类型 1.1创建方式 ①new关键字 : var person = new Oject(); ②给定直接量: var person = { name : "zhangsan& ...

  5. jquery模拟飞秋

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  6. ASP.NET CORE系列【六】Entity Framework Core 之数据迁移

    原文:ASP.NET CORE系列[六]Entity Framework Core 之数据迁移 前言 最近打算用.NET Core写一份简单的后台系统,来练练手 然后又用到了Entity Framew ...

  7. [原译]实现IEnumerable接口&理解yield关键字

    原文:[原译]实现IEnumerable接口&理解yield关键字 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享.请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢 ...

  8. 使用IntelliJ IDEA开发SpringMVC网站(三)数据库配置

    原文:使用IntelliJ IDEA开发SpringMVC网站(三)数据库配置 摘要 讲解在IntelliJ IDEA中,如何进行Mysql数据库的配置 目录[-] 文章已针对IDEA 15做了一定的 ...

  9. UWP项目生成错误: 未能使用“CompileXaml”任务的输入参数初始化该任务。“CompileXaml”任务不支持“PlatformXmlDir”参数。请确认该参数存在于此任务中,并且是可设置的公共实例属性。

    UWP项目生成错误: 未能使用“CompileXaml”任务的输入参数初始化该任务.“CompileXaml”任务不支持“PlatformXmlDir”参数.请确认该参数存在于此任务中,并且是可设置的 ...

  10. WCF研究-后篇

    最后就对之前的资料进行整理以及在其他博客园的朋友那看到的资料稍微分享一下,这样有助于学习和使用WCF的朋友更好的学习和理解WCF 后期要是看到合适的资料也会再次编辑这个后篇,让我共同进步! 后篇 1. ...