2014-10-09 81 views
4

我是CROSS APPLY的新手,試圖理解它的工作機制。特別是,在做一些測試時,我發現在CROSS APPLY語句中包含一個GROUP BY子句會大大提高聚合的性能,但這似乎有點違反直覺。我想讓我感到困惑的是操作的確切順序。T-SQL與GROUP BY交叉應用

這裏是我的測試:

declare @cust table (CUSTID int, NAME varchar(30), MaxOrder decimal, TotalAmountSpent decimal, OrderCount int) 
declare @order table (OID int, CUSTID int, AMOUNT decimal) 

insert into @cust values (01, 'Fred', 0, 0, 0) 
insert into @cust values (02, 'Mary', 0, 0, 0) 
insert into @cust values (03, 'Karl', 0, 0, 0) 

insert into @order values (20, 01, 6.00) 
insert into @order values (21, 03, 10.00) 
insert into @order values (22, 03, 20.00) 

update @cust 
    set MaxOrder = app.MaxOrder, TotalAmountSpent = app.TotalAmountSpent, OrderCount = app.OrderCount 
    from @cust c 

cross apply (
       select MAX(AMOUNT) MaxOrder, SUM(AMOUNT) TotalAmountSpent, COUNT(OID) OrderCount 
       from @order o 
       where c.CUSTID = o.CUSTID 
       group by o.CUSTID 
      ) app 

select * from @cust 

這將產生正確的結果:

CUSTID NAME MaxOrder TotalAmountSpent OrderCount 
1  Fred   6     6    1 
2  Mary   0     0    0 
3  Karl   20     30    2 

註釋掉GROUP BY導致瑪麗的價值觀爲NULL被覆蓋:

CUSTID NAME MaxOrder TotalAmountSpent OrderCount 
1  Fred   6     6    1 
2  Mary  NULL    NULL    0 
3  Karl   20     30    2 

所以雖然兩個結果集都可以被認爲是「正確的」,但第一種方法隻影響那些行實際上相關。在一個更大的數據集上,這似乎有點提高了性能。

以下是我對此感到困惑的內容:一般來說,我相信在任何SQL語句中,WHERE子句都將在GROUP BY子句之前處理,不是嗎?在這種情況下,SQL Server查詢優化器是否知道在應用左右表之間的WHERE子句之前首先執行GR​​OUP BY?令我感到驚訝的是,以這種方式編寫這個文件導致了正確的結果和更好的表現對引擎蓋下發生了什麼的解釋將非常感謝。

謝謝!

回答

3

這不是關於where子句和group by子句是異步執行的,而是查詢優化器認爲是最有效的路徑。使用group by查看查詢計劃,在流聚合和合並連接之前,引入了兩種排序,每個表對應一個表。排序後的列表在聚合時會比未排序的列表更快 - 需要的比較少,檢查/ IO要求少 - 分組表達式更改的每個時間間隔,它會設置一個新組並繼續流式傳輸數值in。

另一方面,如果沒有它,那麼使用where子句應用查詢就足以返回1行,因此不會中斷結果集,因爲它是所有聚合函數。如果沒有分組,則不需要跟蹤任何表達式中的更改,只需要將where子句條件匹配的任何聚合匹配。

結果是否一樣?不完全是,但是簡單地合併爲零比在另一個查詢計劃中的兩種類型相關的處理成本更簡單。

+0

我還不能投票,但這是有幫助的,謝謝。 – SQLDM 2014-10-10 14:01:05

2

有趣的行爲。嚴格地說,你的查詢是不正確的 - 如果你沒有瑪麗的訂單,但仍然想更新她的記錄,你應該使用outer apply而不是cross。此外,在set部分中使用isnull()包裝處理此「無記錄」情況可能會更好。

現在瑪麗行的值不會被零寫 - 它們是保持不變,因爲apply不會爲她返回任何東西。你可以通過改變你的表初始化如下看到:

insert into @cust values (01, 'Fred', -1, -1, -1) 
insert into @cust values (02, 'Mary', -1, -1, -1) 
insert into @cust values (03, 'Karl', -1, -1, -1) 

有了group by,瑪麗的行沒有得到零,但它仍然擁有所有這些-1的。當您嘗試使用不返回行的查詢將值分配給標量變量時,它的行爲完全相同 - 該變量在此之後仍然保持其先前的值。它是一個記錄和知名的功能。儘管如此,至少對我來說還是很有意思的,爲什麼註釋掉group by這麼劇烈地改變了行爲。我們可以通過查看apply子查詢的結果,這樣它縮小:

select MAX(AMOUNT) MaxOrder, SUM(AMOUNT) TotalAmountSpent, COUNT(OID) OrderCount 
from @order o 
where o.CUSTID = 2; 

select MAX(AMOUNT) MaxOrder, SUM(AMOUNT) TotalAmountSpent, COUNT(OID) OrderCount 
from @order o 
where o.CUSTID = 2 
group by o.CUSTID; 

當它出現時,指定分組條件可以作爲額外的過濾器。這可能是在SQL Server中實現聚合的方式。

編輯:經過一番搜索,我發現Oracle的工作方式完全一樣。所以這似乎是一種標準行爲。另外,在此討論這種效應:Count Returning blank instead of 0

簡而言之,group by會過濾掉不存在的組,因此當您指定沒有銷售的客戶時,您什麼都沒有。然而,如果沒有分組,則不存在這樣的過濾階段,因此您收到整個表格的集合 - null s爲maxsum,零爲count。在你的特定例子中,group by實際上是不必要的,因爲所有返回的列都是聚合(這是非常罕見的)。

+1

在我最初的測試中,我確實將OUTER APPLY作爲主要查詢的一部分(沒有更新語句),因爲正如你所提到的那樣,這將是自然而然的正確方法。不過,那是我第一次注意到更大數據集的性能問題。所以,這導致我嘗試更新語句,以及帶有GROUP BY的CROSS。對我而言,它看起來並不直觀,但它可以一次又一次地檢查結果,而且它看起來能夠產生正確的聚合,而且正如你所描述的那樣,它可以作爲一個額外的過濾器來提高性能。 (沒有足夠的代表upvote呢,但是謝謝!) – SQLDM 2014-10-10 13:56:57

+0

此外,我正在使用SQL Server版本2008 R2。 – SQLDM 2014-10-10 14:02:00

+1

@SQLDM,更新了答案。 – 2014-10-10 15:54:00