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 与 HAVINGHAVING 子句的作用有点类似于 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 ROLLUPROLLUP 是对 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);