2012-02-09 74 views
11

假设你有“汽车总动员”的表几十万行, 和你想要做一个GROUP BY:T-SQL GROUP BY和COUNT,然后包括MAX从COUNT

SELECT CarID 
     , CarName 
     , COUNT(*) AS Total 
FROM  dbo.tbl_Cars 
GROUP BY CarID 
     , CarName 

该分组会给你带来类似于:

CarID  CarName Total 
1872  Olds  202,121 
547841  BMW  175,298 
9877  Ford  10,241 

一切都很好。 我的问题,但是,是什么东西拿到 总计和MAX总到一个表,在性能和 清洁编码方面的最佳方式,让你有这样的结果:

CarID  CarName Total  Max Total 
1872  Olds  202,121 202,121 
547841  BMW  175,298 202,121 
9877  Ford  10,241 202,121 

一种方法将GROUP结果放入临时表 ,然后将临时表中的MAX读入局部变量。 但我想知道做到这一点的最佳方法是什么。


UPDATE

的公共表表达式似乎是最优雅的编写, 但类似@EBarr,我有限的测试表明一个显著降低性能。 所以我不会和CTE一起去。

由于@EBarr对COMPUTE选项的链接表示功能 已被弃用,它似乎也不是最佳路线。

MAX值的局部变量选项和使用 临时表可能是我下降的路线,因为我不是 意识到它的性能问题。

有关我的用例的更多细节:它可能最终会成为其他SO问题的一系列问题。但足以说,我正在将一大部分数据加载到临时表中(因此tbl_Cars的一个子集是 进入#tbl_Cars,甚至#tbl_Cars可能会被进一步过滤 并对其执行聚合),因为我必须在单个存储的proc 内对其执行多个筛选 和聚合查询,该查询返回多个结果集。


更新2

@ EBarr的使用窗口函数的是好的和短。自我注释: 如果将RIGHT JOIN用于外部参照表,则函数应从tbl_Cars中选择一列,而不是从'*'中选择一列。

SELECT  M.MachineID 
      , M.MachineType 
      , COUNT(C.CarID) AS Total 
      , MAX(COUNT(C.CarID)) OVER() as MaxTotal 
FROM   dbo.tbl_Cars C 
RIGHT JOIN dbo.tbl_Machines M 
     ON  C.CarID = M.CarID 
GROUP BY  M.MachineID 
      , M.MachineType 

在速度方面,看起来很好,但在什么时候,你必须要 担心的读取次数?

回答

13

机械地有几种方法可以做到这一点。你可以使用临时表/表变量。另一种方法是用@Aaron_Bertrand显示的嵌套查询和/或CTE。第三种方法是使用WINDOWED FUNCTIONS,例如...

SELECT CarName, 
      COUNT(*) as theCount, 
      MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxPerGroup 
FROM  dbo.tbl_Cars 
GROUP BY CarName 

不受欢迎(读depricated)第四种方法是使用COMPUTE关键字作为这样...

SELECT CarID, CarName, Count(*) 
FROM  dbo.tbl_Cars 
GROUP BY CarID, CarName 
COMPUTE MAX(Count(*)) 

COMPUTE关键字生成在的端部显示为附加摘要列总计结果集(see this)。在上面的查询中,您实际上会看到两个记录集。

最快

现在,下一个问题是什么是 “最好的/最快/最简单的。”我立即想到indexed view。正如@Aaron轻轻提醒我的那样,索引视图有各种各样的限制。但是,上述策略允许您在SELECT ... FROM..GROUP BY上创建索引视图。然后从索引视图中选择应用WINDOWED FUNCTION子句。

然而,不知道更多关于你的设计,任何人都会告诉你什么是最好的。您将从索引视图中获得照明快速查询。尽管如此,这种表现还是值得的。价格是维护成本。如果基础表是大量插入/更新/删除操作的目标,那么索引视图的维护会使其他领域的性能下降。

如果您对使用案例和数据访问模式有更多的了解,人们将能够分享更多见解。


科技性能测试

于是我产生了一点数据脚本,看着SQL事件探查器号码为CTE性能VS窗口的功能。这是一个微型测试,所以请尝试您的系统下的实际负载中的一些实数。

数据生成:

Create table Cars (CarID int identity (1,1) primary key, 
        CarName varchar(20), 
        value int) 
GO 
insert into Cars (CarName, value) 
values ('Buick', 100), 
     ('Ford', 10), 
     ('Buick', 300),  
     ('Buick', 100), 
     ('Pontiac', 300),  
     ('Bmw', 100), 
     ('Mecedes', 300),  
     ('Chevy', 300),  
     ('Buick', 100), 
     ('Ford', 200); 
GO 1000 

这个脚本生成10,000行。然后我跑了四个以下查询多次:

--just group by 
select CarName,COUNT(*) countThis 
FROM Cars 
GROUP BY CarName   

--group by with compute (BAD BAD DEVELOPER!) 
select CarName,COUNT(*) countThis 
FROM Cars 
GROUP BY CarName   
COMPUTE MAX(Count(*)); 

-- windowed aggregates... 
SELECT CarName, 
     COUNT(*) as theCount, 
     MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxInAnyGroup 
FROM Cars 
GROUP BY CarName   

--CTE version 
;WITH x AS (
    SELECT CarName, 
      COUNT(*) AS Total 
    FROM  Cars 
    GROUP BY CarName 
) 
SELECT x.CarName, x.Total, x2.[Max Total] 
FROM x CROSS JOIN (
    SELECT [Max Total] = MAX(Total) FROM x 
) AS x2; 

运行上面的查询后,我创建的索引视图“只是按”上面的查询。然后我在执行MAX(Count(*)) OVER(PARTITION BY 'foo'的索引视图上运行查询。

平均结果

Query      CPU  Reads  Duration 
-------------------------------------------------------- 
Group By     15  31  7 ms 
Group & Compute   15  31  7 ms 
Windowed Functions   14  56  8 ms 
Common Table Exp.   16  62  15 ms 
Windowed on Indexed View 0  24  0 ms 

显然,这是一个微型基准,仅轻度启发,所以你要为它的价值。

+0

你不能在索引视图中使用'MAX'(我一直要求5年 - http://connect.microsoft.com/SQLServer/feedback/details/267516/expand-aggregate-support-in-indexed-views-min-max)。 'theFieldBeingSearchedForMax'不在表中,它是输出的一部分(这是最高的计数)。 – 2012-02-09 19:41:33

+0

只需重新阅读问题。我错读了它。更新SQL。 – EBarr 2012-02-09 19:42:58

+0

- 抱歉,我未能在第一个查询中添加GROUP BY;我的错。 – mg1075 2012-02-09 19:52:20

8

这里有一种方法:

;WITH x AS 
(
    SELECT CarID 
     , CarName 
     , COUNT(*) AS Total 
    FROM  dbo.tbl_Cars 
    GROUP BY CarID, CarName 
) 
SELECT x.CarID, x.CarName, x.Total, x2.[Max Total] 
FROM x CROSS JOIN 
(
    SELECT [Max Total] = MAX(Total) FROM x 
) AS x2; 
0

SQL Server 2008 R2和更新版本,您可以使用:

GROUP BY CarID, CarName WITH ROLLUP