在SQL Server里,你有沒有想進行跨越多個列/緯度的聚集操作,不使用SSAS許可(SQL Server分析服務)。我不是說在生產里使用開發版,也不是說安裝盜版SQL Server。
不可能的任務?未必,因為通過SQL Server里所謂的Grouping Sets就可以。在這篇文章里我會給你概括介紹下Grouping Sets,使用它們可以實現哪類查詢,什么是它們的性能優勢。
使用Grouping Sets的聚合
假設你有個訂單表,你想進行跨多個分組的T-SQL聚集查詢。在AdventureWorks2012數據庫的Sales.SalesOrderHeader表的環境里,這些分組可以類似如下:
- 在每列分組
- GROUP BY SalesPersonID, YEAR(OrderDate)
- GROUP BY CustomerID, YEAR(OrderDate)
- GROUP BY CustomerID, SalesPersonID, YEAR(OrderDate)
當你想用傳統T-SQL查詢進行這些各自分組時,你需要多個語句,對各個記錄集進行UNION ALL。我們來看這樣的查詢:
1 SELECT * FROM 2 ( 3 -- 1st Grouping Set 4 SELECT 5 NULL AS 'CustomerID', 6 NULL AS 'SalesPersonID', 7 NULL AS 'OrderYear', 8 SUM(TotalDue) AS 'TotalDue' 9 FROM Sales.SalesOrderHeader 10 WHERE SalesPersonID IS NOT NULL 11 12 UNION ALL 13 14 -- 2nd Grouping Set 15 SELECT 16 NULL AS 'CustomerID', 17 SalesPersonID, 18 YEAR(OrderDate) AS 'OrderYear', 19 SUM(TotalDue) AS 'TotalDue' 20 FROM Sales.SalesOrderHeader 21 WHERE SalesPersonID IS NOT NULL 22 GROUP BY SalesPersonID, YEAR(OrderDate) 23 24 UNION ALL 25 26 -- 3rd Grouping Set 27 SELECT 28 CustomerID, 29 NULL AS 'SalesPersonID', 30 YEAR(OrderDate) AS 'OrderYear', 31 SUM(TotalDue) AS 'TotalDue' 32 FROM Sales.SalesOrderHeader 33 WHERE SalesPersonID IS NOT NULL 34 GROUP BY CustomerID, YEAR(OrderDate) 35 36 UNION ALL 37 38 -- 4th Grouping Set 39 SELECT 40 CustomerID, 41 SalesPersonID, 42 YEAR(OrderDate) AS 'OrderYear', 43 SUM(TotalDue) AS 'TotalDue' 44 FROM Sales.SalesOrderHeader 45 WHERE SalesPersonID IS NOT NULL 46 GROUP BY CustomerID, SalesPersonID, YEAR(OrderDate) 47 ) AS t 48 ORDER BY CustomerID, SalesPersonID, OrderYear 49 GO
用這個T-SQL語句方法有多個缺點:
- T-SQL語句本身很龐大,因為每個單獨分組都是一個不同查詢。
- 每查詢1次,Sales.SalesOrderHeader表需要訪問4次。
- 每查詢1次,你在執行計划里會看到SQL Server進行了4次的索引查找(非聚集)(Index Seek (NonClustered) )。
如果你使用自SQL Server 2008以后引入的grouping sets功能,就可以大大簡化你需要的T-SQL代碼。下面代碼展示你同樣的查詢,但這次用grouping sets實現。
1 SELECT 2 CustomerID, 3 SalesPersonID, 4 YEAR(OrderDate) AS 'OrderYear', 5 SUM(TotalDue) AS 'TotalDue' 6 FROM Sales.SalesOrderHeader 7 WHERE SalesPersonID IS NOT NULL 8 GROUP BY GROUPING SETS 9 ( 10 -- Our 4 different grouping sets 11 (CustomerID, SalesPersonID, YEAR(OrderDate)), 12 (CustomerID, YEAR(OrderDate)), 13 (SalesPersonID, YEAR(OrderDate)), 14 () 15 ) 16 GO
從代碼本身可以看到,你只在GROUP BY GROUPING SETS子句里指定需要的分組集——其它的一切都由SQL Server搞定。指定的空括號是所謂的Empty Grouping Set,是跨整個表的聚集。當你看STATISTICS IO輸出時,你會發現Sales.SalesOrderHeader只被訪問了1次!這是和剛才手工實現的巨大區別。
在執行計划里,SQL Server使用了Table Spool運算符,它把獲得的數據臨時存儲在TempDb里。來自臨時表里創建的Worktable的數據在執行計划的第2個分支被使用。因此對來自表的每個分組數據沒有重新掃描,這就給整個執行計划的帶來了更好的性能。
我們再來看下執行計划,你會發現查詢計划包含了3個Stream Aggregate運算符(紅色,藍色,綠色高亮顯示)。這3個運算符計算各個分組集:
- 藍色高亮的運算符計算CustomerID, SalesPersonID, YEAR(OrderDate的分組集。
- 紅色高亮的運算符計算SalesPersonID, YEAR(OrderDate)的分組集。另外也計算每1列的分組集。
- 綠色高亮的運算符計算CustomerID, YEAR(OrderDate)的分組集。
2個連續的Stream Aggregate運算符的背后想法是計算所謂的Super Aggregates——聚集的聚集。
小結
在今天的文章里我給你介紹了grouping sets,在SQL Server 2008后引入的增強T-SQL。如你所見grouping sets有2個大優點:簡化你的代碼,只訪問一次數據提高查詢性能。
我希望現在你已經能夠很好理解grouping sets,如果你能在你的數據庫里使用這個功能可以在此留言,非常感謝!
感謝關注!
參考文章:
https://www.sqlpassion.at/archive/2014/09/15/the-power-of-grouping-sets-in-sql-server/