.Orders;
该语句的执行结果如表9-7所示。前4行RunSubTotal列都计算为1000,这是为什么呢?我们在前面强调过,排序对于范围的限定非常重要,注意上面的ORDER BY子句限定了按EmpID和SalesYear列排序。由表9-7中可以看到,前4行的EmpID和SalesYear值是相同的,因此RANGE将范围限定为这4行,所以SUM对这4行的SubTotal列进行求和,结果为1000。这里需要注意的是,我们说的用于限定范围的相同值,是指的排序列的值相同,而不是被聚合列的值相同。

上面示例中的排序列存在重复值,所以在使用RANGE时存在对多行聚合现象,当排序列的值具有唯一性时,RANGE和ROWS的限定范围实际上是一样的。例如,下面的两条语句一条使用了ROWS,一条使用了RANGE,其他完全相同。因为排序列EmpID+SalesYear+SalesQuarter的值具有唯一性,所以这两条语句的返回结果是相同的,如表9-8所示。
SELECT EmpID, SalesYear, SalesQuarter, SubTotal,
SUM(SubTotal) OVER(PARTITION BY EmpID
ORDER BY EmpID, SalesYear, SalesQuarter
RANGE BETWEEN UNBOUNDED PRECEDING
ANDCURRENT ROW) AS RunSubTotal
FROM dbo.Orders;
SELECT EmpID, SalesYear, SalesQuarter, SubTotal,
SUM(SubTotal) OVER(PARTITION BY EmpID
ORDER BY EmpID, SalesYear, SalesQuarter
ROWS BETWEEN UNBOUNDED PRECEDING
ANDCURRENT ROW) AS RunSubTotal
FROM dbo.Orders;

9.2.3 开窗聚合函数的嵌套
前面我们讲过,窗口的分区与数据分组有类似之处,但是,在一条语句中指定分组列后,对于整个语句你只能按此列进行分组,而分区则可以在一条语句中指定多个不同的分区方式,这两种方式似乎不存在相交之处。实际上,两者也可以结合在一起使用。
仍旧以9.2.1节创建的Orders表为例,假设现在要计算雇员的年度销售额和企业整体的年度销售额,可以使用下面的语句。雇员的年度销售额按SalesYear+EmpID列分区,企业的年度销售额直接按SalesYear分区。查询结果如表9-9所示。
WITH CTE
AS(
SELECTEmpID, SalesYear,
SUM(SubTotal) OVER(PARTITION BY SalesYear, EmpID) AS EmpSubTotal,
SUM(SubTotal) OVER(PARTITION BY SalesYear) AS YearSubTotal
FROMdbo.Orders
)
SELECT DISTINCT * FROM CTE;
表9-9 计算雇员年度销售额和企业年度销售额
| EmpID |
SalesYear |
EmpSubTotal |
YearSubTotal |
| 1 |
2013 |
1000 |
2200 |
| 2 |
2013 |
1200 |
2200 |
| 1 |
2014 |
600 |
1400 |
| 2 |
2014 |
800 |
1400 |
由表9-9可以看出,企业2013年销售额是2200,2014年是1400。2013年雇员1和雇员2的销售额分别是1000和1200。2014年雇员1和雇员2的销售额分别是600和800。
再来看下面一条语句,它的执行结果与表9-5完全相同。
SELECT EmpID, SalesYear,
SUM(SubTotal) AS EmpSubTotal,
SUM(SUM(SubTotal)) OVER(PARTITION BYSalesYear) AS YearSubTotal
FROM dbo.Orders
GROUP BY EmpID, SalesYear
上面这条语句中使用GROUPBY按EmpID+SalesYear进行了分组计算,得到的是每位雇员的年度销售额SUM(SubTotal),而语句中的嵌套开窗函数SUM(SUM(SubTotal))(注意,最外层的SUM是开窗聚合函数,里面的SUM(SubTotal)是每位雇员的年度销售额)是对每位雇员的年度销售额进行求和,从而得到企业的年度销售额。当然,这里有一个问题,语句中第2行的SUM(SubTotal)是GROUP BY的计算结果,相对于第3行中的SUM(SUM(SubTotal))而言,它是一个中间结果,为什么这条语句能够正确执行呢?让我们再来回顾一下在5.10节中介绍的查询的逻辑处理顺序——SELECT子句是在GROUP BY后面执行的,因此SUM(SubTotal)这个中间结果对于开窗函数而言是可见的。不过,在这条语句中只能使用函数嵌套,而不能使用别名EmpSubTotal。我们在5.9节介绍过“同时操作”概念,这时候的别名EmpSubTotal对于后面的SUM函数并不可见。例如,下面语句将引发错误,提示“列名'EmpSubTotal'无效”。
SELECT EmpID, SalesYear,
SUM(SubTotal) AS EmpSubTotal,
SUM(EmpSubTotal) OVER(PARTITION BYSalesYear) AS YearSubTotal
FROM dbo.Orders
GROUP BY EmpID, SalesYear
从上面的分析可以看出,开窗聚合函数支持函数嵌套,但是对于语句可读性而言,增加了阅读难度。可以通过公用表表达式的方法进行简化,参考下面的语句。
WITH CTE AS (
SELECTEmpID, SalesYear, SUM(SubTotal) AS EmpSubTotal
FROMdbo.Orders
GROUP BYEmpID, SalesYear
)SELECT EmpID, SalesYear, EmpSubTotal,
SUM(EmpSubTotal) OVER(PARTITION BY SalesYear) AS YearSubTotal
FROM CTE;
9.2.4 分区聚合计算与联接的比较
前面我们讲过,分区可以使用子查询来代替,此外,也可以通过联结的方法实现类似功能,并且联结有时比子查询和分区更为高效。下面的示例使用在9.1节创建的Students表,现假设需要计算每名学生成绩与本班级平均成绩的差异。我们需要先计算每个班级的平均成绩,然后通过联接的方式将平均成绩关联到相应的学生成绩行,再计算差异。语句如下:
SELECT S1.ClassID,
S1.StudentName,
S1.Achievement,
S2.AvgAch ,
S1.Achievement - S2.AvgAch AS Diff
FROM Students AS S1
LEFT OUTERJOIN (SELE