假设你有一张包含数十万行的“汽车”表, 你想做一个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
就速度而言,它似乎很好,但你必须在什么时候 担心读取次数?
答案 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