2010-03-30 69 views
3

我注意到SQL Server(在這種情況下,SQL Server 2008)如何處理select語句內的相關子查詢時有點意外。我的假設是查詢計劃不應該受到在select語句的投影子句中寫入子查詢(或列,就此而言)的順序的影響。但是,這似乎並非如此。投影中相關子查詢排序的影響

考慮以下兩個查詢,這是除子查詢的CTE內的順序相同:

--query 1: subquery for Color is second 
WITH vw AS 
(
SELECT p.[ID], 
    (SELECT TOP(1) [FirstName] FROM [Preference] WHERE p.ID = ID AND [FirstName] IS NOT NULL ORDER BY [LastModified] DESC) [FirstName], 
    (SELECT TOP(1) [Color] FROM [Preference] WHERE p.ID = ID AND [Color] IS NOT NULL ORDER BY [LastModified] DESC) [Color] 
FROM Person p 
) 
SELECT ID, Color, FirstName 
FROM vw 
WHERE Color = 'Gray'; 


--query 2: subquery for Color is first 
WITH vw AS 
(
SELECT p.[ID], 
    (SELECT TOP(1) [Color] FROM [Preference] WHERE p.ID = ID AND [Color] IS NOT NULL ORDER BY [LastModified] DESC) [Color], 
    (SELECT TOP(1) [FirstName] FROM [Preference] WHERE p.ID = ID AND [FirstName] IS NOT NULL ORDER BY [LastModified] DESC) [FirstName] 
FROM Person p 
) 
SELECT ID, Color, FirstName 
FROM vw 
WHERE Color = 'Gray'; 

如果你看看這兩個查詢計劃,你會看到一個外連接使用對於每個子查詢,並且聯接的順序與子查詢寫入的順序相同。有一個過濾器應用於顏色的外部連接的結果,以濾除顏色不是「灰色」的行。 (對我來說,SQL會使用顏色子查詢的外部聯接,因爲我對顏色子查詢的結果有一個非空限制,但確定。)

大部分行都被顏色過濾器。結果是查詢2比查詢1便宜得多,因爲第二次連接涉及的行較少。拋開這種陳述的所有原因是否是預期的行爲?無論SQL查詢計劃中SQL Server是否選擇儘早移動篩選器,而不管子查詢的寫入順序如何?

編輯:只是爲了澄清,我有一個正在探索這種情況的正當理由。我可能需要創建一個涉及類似構造的子查詢的視圖,現在很明顯,基於從視圖中投影出來的這些列的任何過濾都會因爲列的排序而在性能上發生變化!

+0

和你爲什麼要使用相關子查詢在所有?爲什麼不使用連接? – HLGEM 2010-03-30 21:26:24

+0

如果您爲每個子查詢使用兩個CTE表而不是一個子查詢,那麼查詢計劃的外觀如何。 – Thomas 2010-03-30 21:33:04

+0

@HLGEM我通常會使用一個連接,但在這個例子中,我只關心另一個表中最高的1(或0)值,其中可能有很多。 – 2010-03-30 21:36:21

回答

1

下面是可能執行的替代版本更好:

With Colors As 
    (
    Select Id, [Color] 
     , ROW_NUMBER() OVER (PARTITION BY ID ORDER BY [LastModified] DESC) As Num 
    From Preference 
    Where [Color] Is Not Null 
    ) 
    , Names As 
    (
    Select Id, [FirstName] 
     , ROW_NUMBER() OVER (PARTITION BY ID ORDER BY [LastModified] DESC) As Num 
    From Preference 
    Where [FirstName] Is Not Null 
    ) 
Select 
From Person As P 
    Join Colors As C 
     On C.Id = P.Id 
      And C.Num = 1 
    Left Join Names As N 
     On N.Id = P.Id 
      And N.Num = 1 
Where C.[Color]= 'Grey' 

另一種解決方案,它更簡潔,但可能會或可能不會進行,以及:

With RankedItems 
    (
    Select Id, [Color], [FirstName] 
     , ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Case When [Color] Is Not Null 1 Else 0 End DESC, [LastModified] DESC) As ColorRank 
     , ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Case When [FirstName] Is Not Null 1 Else 0 End DESC, [LastModified] DESC) As NameRank 
    From Preference 
    ) 
Select 
From Person As P 
    Join RankedItems As RI 
     On RI.Id = P.Id 
      And RI.ColorRank = 1 
    Left Join RankedItems As RI2 
     On RI2.Id = P.Id 
      And RI2.NameRank = 1 
Where RI.[Color]= 'Grey' 
+0

謝謝托馬斯,我正在檢查你的第一個查詢,它看起來不錯。到目前爲止,似乎生成的計劃都是獨立於順序的,並且過濾的連接總是首先執行並作爲內部連接執行。 – 2010-03-31 14:03:43

+0

由於SQL掃描後續連接而不是使用重複索引查找,因此您的第一個查詢在IO性能方面似乎也比我的原始查詢擴展得更好。第二個查詢更簡潔(雖然不太清楚),但不幸的是,它需要對case語句的結果進行昂貴的排序。通過在ID,LastModified DESC上聚類,我可以輕鬆避免第一個查詢中需要的排序。 – 2010-03-31 14:45:43

2

隨着TOP運算符在這裏發揮作用,查詢優化器對統計數據非常瞎猜,所以它會查找其他線索,如如何最好地處理它,例如首先實例化CTE的相關部分。

而且它是外連接,因爲如果沒有返回任何內容,子查詢將被用作NULL,並且系統首先實例化它。如果你使用聚合而不是TOP,你可能會得到一個稍微不同但更一致的計劃。

+0

您可能更願意使用row_number來獲得您的結果,因爲它可以成爲TOP的有效替代品。 – 2010-03-30 21:34:51

+0

是的,我理解你對統計數據的觀點,但是優化器不應該假設任何最差的過濾都會留下所有行,但最多隻會刪除許多行?另外,如果使用外連接構造更典型的查詢,然後在連接的列上放置非空約束,則優化器足夠智能以使用內連接。爲什麼不在這種情況下? – 2010-03-30 21:41:02

+0

另外,在這種情況下,我不能輕鬆使用聚合,因爲SQL中沒有空合併聚合。 – 2010-03-31 02:22:14