t-sql GROUP BY使用COUNT,然后从COUNT包含MAX

时间:2012-02-09 19:34:35

标签: sql sql-server-2008 tsql

假设你有一张包含数十万行的“汽车”表, 你想做一个GROUP BY:

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到局部变量。 但我想知道最好的方法是什么。


更新

Common Table Expression似乎是最优雅的写作, 但与@EBarr相似,我的有限测试表明性能明显下降。 所以我不会参加CTE。

由于@EBarr对COMPUTE选项的链接表示该功能 不推荐使用,这似乎也不是最好的路线。

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

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


更新2

@ EBarr使用窗口函数很好而且简短。注意自我: 如果使用RIGHT JOIN到外部参考表,COUNT() 函数应该从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

就速度而言,它似乎很好,但你必须在什么时候 担心读取次数?

3 个答案:

答案 0 :(得分:13)

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

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

DISFAVORED (读取已删除)第四种方式是使用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子句。

然而,如果不了解更多关于您的设计,那么任何人都很难告诉您什么是最好的。您将从索引视图中获得快速查询。不过,这种表现是有代价的。价格是维护费用。如果基础表是大量插入/更新/删除操作的目标,则索引视图的维护将使其他区域的性能陷入停滞。

如果您分享有关您的用例和数据访问模式的更多信息,那么人们将能够分享更多洞察力。


MICRO PERFORMANCE TEST

所以我生成了一个小数据脚本,并查看了CTE性能与窗口函数的sql profiler编号。这是一个微测试,所以在实际负载下的你的系统中尝试一些实数。

数据生成:

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;

运行上述查询后,我在上面的“just group by”查询中创建了一个索引视图。然后我对执行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

显然,这是一个微观基准,只是温和的指导,所以把它当作它的价值。

答案 1 :(得分: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;

答案 2 :(得分:0)

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

GROUP BY CarID, CarName WITH ROLLUP