SQL Server里ORDER BY的歧義性


在今天的文章里,我想談下SQL Server里非常有爭議和復雜的話題:ORDER BY子句的歧義性。

視圖與ORDER BY

我們用一個非常簡單的SELECT語句開始。

1 -- A very simple SELECT statement
2 SELECT * FROM Person.Person
3 ORDER BY LastName
4 GO

從剛才列出的代碼你可以看到,我們只想從Person.Person表以LastName列排序返回記錄。因為我們想能盡可能簡單的重用那個SQL語句,最后我們把它放到視圖里,如下:

1 -- This doesn't work
2 CREATE VIEW v_Persons
3 AS
4     SELECT * FROM Person.Person
5     ORDER BY LastName
6 GO

但是你會看到,SQL Server不能創建那個視圖,只返回一個錯誤信息:

這個錯誤信息告訴你,的那個你不使用TOP,OFFSET或FOR XML表達式時,在視圖里你不允許使用ORDER BY子句。基於那個錯誤信息,我們可以通過增加TOP 100 PERCENT子句到視圖里在輕松修正問題。

1 -- Let's make it work!
2 CREATE VIEW v_Persons
3 AS
4     SELECT TOP 100 PERCENT * FROM Person.Person
5     ORDER BY LastName
6 GO

現在視圖創建沒有任何問題!我們對視圖執行一個SELECT語句。

1 SELECT * FROM v_Persons
2 GO

SELECT語句本身可以執行,但當你看返回的數據時,瘋狂的事情發生了:返回的數據沒有按LastName列排序——SQL Server按BusinessEntityID——表上的聚集鍵列排序!

這是SQL Server里的BUG么?不,並不是——它是“故意的”!我們來解釋下為什么。首先你要知道ORDER BY子句在SQL(編程語言本身)里用2個不同的上下文:

  1. 使用ORDER BY子句你可以定義返回給你客戶端程序的排序
  2. 另外ORDER BY子句用來定義從TOP表達式哪些行返回

你必須知道的最重要的事情是,你用視圖定義了所謂的集合(Set),行內函數,派生表,子查詢和通用表表達式(common table expressions(CTE))。集合是數學上的概念,關系數據庫(例如SQL Server)上集合論(Set Theory)的組成。集合本身是沒有排序的。因此用視圖定義與ORDER BY組合是不允許的——如你剛才所見。如果你嘗試這樣做,SQL Server不允許你這樣做並給你一個錯誤信息。

當然你可以在與TOP表達式里組合使用ORDER BY。但基本上你在愚弄SQL Server和你自己,因為ORDER BY沒有告訴SQL Server要以怎樣的排序返回數據給客戶端程序。假設你使用TOP 10 PERCENT。表的前10%是什么?你需要確定性的方式里定義排序。

而且因為我們必須使用TOP 100 PERCENT與ORDER BY組合,查詢優化器實際上在執行計划里不會引入排序運算符。TOP 100 PERCENT意味着一切,因此如你在下圖所看到的,在執行計划里TOP運算符不需要排序輸入。

 

在這個例子里,我們的返回行以從內在數據結構讀取的排序。這由SQL Server的存儲引擎來決定返回行的排序。這里我們從聚集索引里讀取行。因此我們拿到的數據按BusinessEntityID排序,這是索引列里聚集鍵值。

現在我們修改下視圖定義,從Person.Person表值返回10%的行。我們還是指定了ORDER BY子句。

1 -- Alter the view
2 ALTER VIEW v_Persons
3 AS
4     SELECT TOP 10 PERCENT * FROM Person.Person
5     ORDER BY LastName
6 GO

當你現在看結果集時,你會看到返回的行按LastName列排序的。現在才對了,因為你在執行計划里看到了排序運算符(SQL Server 2014里沒有出現),因為TOP運算符最后能返回提供輸入行的前10%的數據。

 

當然你可以通過ORDER BY子句在你引用的視圖里按不同的排序返回10%的行給你的客戶端程序。

1 SELECT * FROM v_Persons
2 ORDER BY FirstName
3 GO

現在當你看執行計划時,你會在計划里看到2個(SQL Server 2014里只有1個)。

 

第1個(右邊)排序運算符為TOP運算符預排序(返回前10%)。第2個(左邊)排序運算符用來最后定義的排序,返回給客戶端程序。當你通過添加TOP 100 PERCENT來定義的視圖里強制ORDER BY——你基本上就在愚弄SQL Server……

沒有ORDER BY的TOP

另一個問題是沒有ORDER BY子句的TOP表達式不會提供你確定性的結果。我們可以用具體的例子演示下這個問題。假設有下列SELECT語句: 

1 SELECT TOP 1 LastName FROM Person.Person
2 GO

這個SQL語句用TOP 1表達式返回Person.Person表的第一行——沒有用ORDER BY子句定義排序。這個排序是基於執行計划里選擇的索引。在這個例子里SQL Server返回你“Abbas”給你作為結果,因為這是執行計划里查詢優化器選擇非聚集索引里第1條可用記錄。

 

因此從這個查詢返回的第1條記錄取決於執行計划里選擇的索引。如果現在我們把非聚集索引停用呢。

1 -- Let's deactivate this index
2 ALTER INDEX [IX_Person_LastName_FirstName_MiddleName] ON Person.Person
3 DISABLE
4 GO

然后當你再次執行剛才的SELECT語句,SQL Server返回你Sánchez值,意味只是在執行計划里現在選擇的聚集索引的第1條記錄。SQL Server從聚集索引里返回了用BusinessEntityID值為1的第1行。

因此你與非確定性記錄打交道時:你的結果取決與執行計划里選擇的索引!你可以通過增加ORDER BY子句來輕松實現查詢結果排序的明確性。在這個情況下ORDER BY子句為TOP表達式使記錄確定——這樣話在執行計划里你會有Sort(Top N Sort)的運算符。

1 SELECT TOP 1 LastName FROM Person.Person
2 ORDER BY LastName
3 GO

在執行計划里,SQL Server從哪個索引讀取行並不重要——Sort(Top N Sort)的運算符在執行計划里會物理預排序行,並從它返回第N行——很簡單,是不是? 

小結

在SQL(編程語言本身)里ORDER BY子句並不是一個最簡單的概念。如你在這篇文章里所學的,ORDER BY使用2個不同的上下文,因此你總要考慮下你要使用哪個上下文。永遠不要在視圖定義里增加TOP 100 PERCENT來愚弄SQL Server和你自己——它不會在最終的記錄集里體現排序。

感謝關注!

參考文章:

https://www.sqlpassion.at/archive/2015/05/25/the-ambiguity-of-the-order-by-in-sql-server/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM