在一个选择查询中计算多个聚合函数时,SQL Server如何迭代行

时间:2014-02-13 09:50:58

标签: sql-server

我有一个SQL Server 2012

并查询

SELECT ManagerId,
       SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as badSoldDays,
       SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc

FROM SomeTable
GROUP BY ProductId

一些表定义

ManagerId | SoldInDay | Category

    1         50          PC
    1         20          Laptop
    2         30          PC
    3         40          Laptop

所以,问题是: 这是否意味着Sql会遍历所有行两次?那么,每个聚合函数是否在表中所有行的单独循环中执行?或者它更聪明?

这个查询无关紧要,这是我的梦想。

1 个答案:

答案 0 :(得分:1)

(看来你的问题是通过评论解决的,但为了完整起见,我在这里提供了正式答案。)

首先,您的示例SQL不会运行。您在字段列表中包含ManagerId,但不包括GROUP BY。您将收到类似于此的错误:

  

消息8120,级别16,状态1,行9列'@ SomeTable.ManagerID'是   在选择列表中无效,因为它不包含在任何一个中   聚合函数或GROUP BY子句。

假设您在字段列表中使用“ManagerId”而不是“ProductId”,我会重现您的情况并审核执行计划。它只显示了一个“Stream Aggregate”运算符。您可以通过将聚合分成两个不同的公用表表达式(CTE)并将它们重新连接在一起来强制它在表上运行两次。在这种情况下,您会看到两个Stream Aggregate运算符(每个运行一个运行表)。

Query Plan Showing Treatment of Multiple Aggregates

以下是生成执行计划的代码:

DECLARE @SomeTable TABLE
(
    ManagerId int,
    SoldInDay int,
    Category varchar(50)
);

INSERT INTO @SomeTable (ManagerId, SoldInDay, Category) VALUES (1, 50, 'PC');
INSERT INTO @SomeTable (ManagerId, SoldInDay, Category) VALUES (1, 20, 'Laptop');
INSERT INTO @SomeTable (ManagerId, SoldInDay, Category) VALUES (2, 30, 'PC');
INSERT INTO @SomeTable (ManagerId, SoldInDay, Category) VALUES (3, 40, 'Laptop');

/*

This produces an error:

Msg 8120, Level 16, State 1, Line 9
Column '@SomeTable.ManagerID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

SELECT
    ManagerId,
    SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as badSoldDays,
    SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
FROM        @SomeTable
GROUP BY    ProductId;

*/

SELECT
    ManagerId,
    SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as BadSoldDays,
    SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
FROM        @SomeTable
GROUP BY    ManagerId;

WITH DaysWithSoldPcTable AS
(
    SELECT
        ManagerId,
        SUM(CASE WHEN Category = 'PC' THEN 1 ELSE 0 END) as DaysWithSoldPc
    FROM        @SomeTable
    GROUP BY    ManagerId
), BadSoldDaysTable AS
(
    SELECT
        ManagerId,
        SUM(CASE WHEN SoldInDay < 30 THEN 1 ELSE 0 END) as BadSoldDays
    FROM        @SomeTable
    GROUP BY    ManagerId
)
SELECT
    DaysWithSoldPcTable.ManagerId,
    DaysWithSoldPcTable.DaysWithSoldPc,
    BadSoldDaysTable.BadSoldDays
FROM        DaysWithSoldPcTable
JOIN        BadSoldDaysTable
ON          DaysWithSoldPcTable.ManagerId = BadSoldDaysTable.ManagerId;