2012-02-09 77 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