2011-03-25 104 views
6

請考慮在SQL Server中的以下2個語句:SQL服務器 - OUTER APPLY與子查詢

這一個是使用嵌套子查詢:

WITH cte AS 
(
    SELECT TOP 100 PERCENT * 
    FROM Segments 
    ORDER BY InvoiceDetailID, SegmentID 
) 
SELECT *, ReturnDate = 
       (SELECT TOP 1 cte.DepartureInfo 
        FROM cte 
        WHERE seg.InvoiceDetailID = cte.InvoiceDetailID 
         AND cte.SegmentID > seg.SegmentID), 
      DepartureCityCode = 
       (SELECT TOP 1 cte.DepartureCityCode 
        FROM cte 
        WHERE seg.InvoiceDetailID = cte.InvoiceDetailID 
         AND cte.SegmentID > seg.SegmentID) 
FROM Segments seg 

而這種使用外部應用運算符:

WITH cte AS 
(
    SELECT TOP 100 PERCENT * 
    FROM Segments 
    ORDER BY InvoiceDetailID, SegmentID 
) 
SELECT seg.*, t.DepartureInfo AS ReturnDate, t.DepartureCityCode 
FROM Segments seg OUTER APPLY (
       SELECT TOP 1 cte.DepartureInfo, cte.DepartureCityCode 
       FROM cte 
       WHERE seg.InvoiceDetailID = cte.InvoiceDetailID 
         AND cte.SegmentID > seg.SegmentID 
      ) t 

考慮到兩個Segments表可能有數百萬行,這兩個表中的哪一個可能執行得更好?

我的直覺是OUTER APPLY會表現更好。

一對夫婦的更多的問題:

  1. 我幾乎敢肯定這一點,但還是想證實的是,在第一個方案中,CTE將有效地執行兩次(因爲其引用了兩次和熱膨脹係數像宏一樣內聯展開)。
  2. 當在OUTER APPLY運算符中使用時,CTE是否會針對每行執行一次?當在第一個語句的嵌套查詢中使用時,它也會爲每一行執行?
+8

運行,檢查查詢計劃 – 2011-03-25 15:53:00

+1

「TOP 100 PERCENT ... ORDER BY」已被優化,並且沒有任何效果。我同意第二個應該表現更好。你也可以看看'ROW_NUMBER'和'PARTITION BY'來獲得每個組的'TOP 1'。 – 2011-03-25 16:00:44

回答

4

首先,在CTE中擺脫Top 100 Percent。您在此處不使用TOP,如果您想要對結果進行排序,則應在整個聲明的末尾添加一個Order By。其次,爲了解決你關於性能的問題,如果被迫猜測,我的賭注只會在第二種形式,因爲它只有一個子查詢而不是兩個。第三,另一種形式,你可以嘗試將是:

With RankedSegments As 
    (
    Select S1.SegmentId, ... 
     , Row_Number() Over(Partition By S1.SegmentId Order By S2.SegmentId) As Num 
    From Segments As S1 
     Left Join Segments As S2 
      On S2.InvoiceDetailId = S1.InvoiceDetailId 
       And S2.SegmentId > S1.SegmentID 
    ) 
Select ... 
From RankedSegments 
Where Num = 1 

另一種可能性

With MinSegments As 
    (
    Select S1.SegmentId, Min(S2.SegmentId) As MinSegmentId 
    From Segments As S1 
     Join Segments As S2 
      On S2.InvoiceDetailId = S1.InvoiceDetailId 
       And S2.SegmentId > S1.SegmentID 
    Group By S1.SegmentId 
    ) 
Select ... 
From Segments As S1 
    Left Join (MinSegments As MS1 
     Join Segments As S2 
      On S2.SegmentId = MS1.MinSegmentId) 
     On MS1.SegmentId = S1.SegmentId 
+0

@Thomas:ORDER BY是因爲OUTER APPLY/Nested查詢需要針對排序的右表運行,您看到我需要TOP 1行,而且必須來自Sorted表,這就是爲什麼TOP 100 PERCENT與在那裏訂購。 嗯...我認爲ROW_NUMBER也是一個不錯的選擇,不知道我是如何錯過了自己:(我會檢查並得到回... – 2011-03-27 08:33:54

+0

@Thomas:第二個查詢的CTE缺少'GROUP BY'。 – 2011-03-27 11:17:51

+0

@Andriy M - Doah。Fixed。Thanks。 – Thomas 2011-03-27 16:22:57

1

也許我會用托馬斯的查詢的這種變化:

WITH cte AS 
(
SELECT *, Row_Number() Over(Partition By SegmentId Order By InvoiceDetailID, SegmentId) As Num 
FROM Segments) 
SELECT seg.*, t.DepartureInfo AS ReturnDate, t.DepartureCityCode 
FROM Segments seg LEFT JOIN cte t ON seg.InvoiceDetailID = t.InvoiceDetailID AND t.SegmentID > seg.SegmentID AND t.Num = 1 
+0

如果SegmentId是PK,則每行的num將爲1。 – Thomas 2011-03-27 16:26:34

+0

嗯..謝謝你指出這個... – 2011-04-07 15:28:21

+0

@Thomas:是SegmentId是PK。我認爲任何基於PARTITION BY或OVER子句的解決方案在這種情況下都不可行,包括您發佈的解決方案。 – 2011-04-07 15:38:01