SQL Server 查詢處理中的各個階段(SQL執行順序)

原文轉自:http://www.cnblogs.com/summer_adai/archive/2011/10/28/2227605.html

SQL 不同於與其他編程語言的最明顯特徵是處理代碼的順序。在大數編程語言中,代碼按編碼順序被處理,但是在SQL語言中,第一個被處理的子句是FROM子句,儘管SELECT語句第一個出現,但是幾乎總是最後被處理。

      每個步驟都會產生一個虛擬表,該虛擬表被用作下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只是最後一步生成的表纔會返回 給調用者。如果沒有在查詢中指定某一子句,將跳過相應的步驟。下面是對應用於SQL server 2000和SQL Server 2005的各個邏輯步驟的簡單描述。


複製代碼
(8)SELECT (9)DISTINCT  (11)<Top Num> <select list>
(
1)FROM [left_table]
(
3)<join_type> JOIN <right_table>
(
2)        ON <join_condition>
(
4)WHERE <where_condition>
(
5)GROUP BY <group_by_list>
(
6)WITH <CUBE | RollUP>
(
7)HAVING <having_condition>
(
10)ORDER BY <order_by_list>
複製代碼

邏輯查詢處理階段簡介

  1. FROM:對FROM子句中的前兩個表執行笛卡爾積(Cartesian product)(交叉聯接),生成虛擬表VT1
  2. ON:對VT1應用ON篩選器。只有那些使<join_condition>爲真的行才被插入VT2。
  3. OUTER(JOIN):如 果指定了OUTER JOIN(相對於CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部聯接把左表標記爲保留表,右外部聯接把右表標記爲保留表,完全外部聯接把兩個表都標記爲保留表)中未找到匹配的行將作爲外部行添加到 VT2,生成VT3.如果FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重複執行步驟1到步驟3,直到處理完所有的表爲止。
  4. WHERE:對VT3應用WHERE篩選器。只有使<where_condition>爲true的行才被插入VT4.
  5. GROUP BY:按GROUP BY子句中的列列表對VT4中的行分組,生成VT5.
  6. CUBE|ROLLUP:把超組(Suppergroups)插入VT5,生成VT6.
  7. HAVING:對VT6應用HAVING篩選器。只有使<having_condition>爲true的組纔會被插入VT7.
  8. SELECT:處理SELECT列表,產生VT8.
  9. DISTINCT:將重複的行從VT8中移除,產生VT9.
  10. ORDER BY:將VT9中的行按ORDER BY 子句中的列列表排序,生成遊標(VC10).
  11. TOP:從VC10的開始處選擇指定數量或比例的行,生成表VT11,並返回調用者。

注:步驟10,按ORDER BY子句中的列列表排序上步返回的行,返回遊標VC10.這一步是第一步也是唯一一步可以使用SELECT列表中的列別名的步驟。這一步不同於其它步驟的 是,它不返回有效的表,而是返回一個遊標。SQL是基於集合理論的。集合不會預先對它的行排序,它只是成員的邏輯集合,成員的順序無關緊要。對錶進行排序 的查詢可以返回一個對象,包含按特定物理順序組織的行。ANSI把這種對象稱爲遊標。理解這一步是正確理解SQL的基礎。

因爲這一步不返回表(而是返回遊標),使用了ORDER BY子句的查詢不能用作表表達式。表表達式包括:視圖、內聯表值函數、子查詢、派生表和共用表達式。它的結果必須返回給期望得到物理記錄的客戶端應用程序。例如,下面的派生表查詢無效,併產生一個錯誤:

select * 
from(select orderid,customerid from orders order by orderid)
as d

下面的視圖也會產生錯誤

create view my_view
as
select
*
from orders
order by orderid

      在SQL中,表表達式中不允許使用帶有ORDER BY子句的查詢,而在T—SQL中卻有一個例外(應用TOP選項)。

      所以要記住,不要爲表中的行假設任何特定的順序。換句話說,除非你確定要有序行,否則不要指定ORDER BY 子句。排序是需要成本的,SQL Server需要執行有序索引掃描或使用排序運行符。
      推薦一段SQL代碼:行列轉置
      


/*問題:假設有張學生成績表(tb)如下:
姓名 課程 分數
張三 語文 74
張三 數學 83
張三 物理 93
李四 語文 74
李四 數學 84
李四 物理 94

想變成(得到如下結果): 
姓名 語文 數學 物理 
---- ---- ---- ----
李四 74   84   94
張三 74   83   93
-------------------
*/
 

create table tb(姓名 varchar(10),課程 varchar(10),分數 int)
insert into tb values('張三' , '語文' , 74)
insert into tb values('張三' , '數學' , 83)
insert into tb values('張三' , '物理' , 93)
insert into tb values('李四' , '語文' , 74)
insert into tb values('李四' , '數學' , 84)
insert into tb values('李四' , '物理' , 94)
go 

--SQL SERVER 2000 靜態SQL,指課程只有語文、數學、物理這三門課程。(以下同)
select 姓名 as 姓名 ,
  
max(case 課程 when '語文' then 分數 else 0 end) 語文,
  
max(case 課程 when '數學' then 分數 else 0 end) 數學,
  
max(case 課程 when '物理' then 分數 else 0 end) 物理
from tb
group by 姓名
====================================================================================================
好像自已在書寫 SQL 語句時由於不清楚各個關鍵字的執行順序, 往往組織的 SQL 語句缺少很好的邏輯, 憑感覺 "拼湊" ( 不好意思, 如果您的 SQL 語句也經常 "拼湊", 那您是不是得好好反省一下呢?, 呵呵). 
這樣做確實是爽了自己, 可苦了機器, 服務器還需要在我們的雜亂無章的 SQL 語句中尋找它下一句需要執行的關鍵字在哪裏. 
效率嘛, 由於我們的感覺神經對秒以下的變化實在不敏感, 暫且就認爲自已寫的 SQL 順序無關緊要, "反正沒什麼變化!", 呵呵.其實服務器對每句 SQL 解析時間都會有詳細記錄的, 大家可以看一下自已按習慣寫的 SQL 和按標準順序寫的SQL解析時間差別有多大. 
因此, 建議大家在平時工作中 SQL 語句按標準順序寫, 一是專業, 二是實用, 呵呵, 不過我覺得最主要的是心裏感覺舒服. 
標準的 SQL 的解析順序爲: 
(1).FROM 子句, 組裝來自不同數據源的數據 
(2).WHERE 子句, 基於指定的條件對記錄進行篩選 
(3).GROUP BY 子句, 將數據劃分爲多個分組 
(4).使用聚合函數進行計算 
(5).使用 HAVING 子句篩選分組 
(6).計算所有的表達式 
(7).使用 ORDER BY 對結果集進行排序 
舉例說明: 在學生成績表中 (暫記爲 tb_Grade), 把 "考生姓名"內容不爲空的記錄按照 "考生姓名" 分組, 並且篩選分組結果, 選出 "總成績" 大於 600 分的. 
標準順序的 SQL 語句爲: 
select 考生姓名, max(總成績) as max總成績 
from tb_Grade 
where 考生姓名 is not null 
group by 考生姓名 
having max(總成績) > 600 
order by max總成績 
在上面的示例中 SQL 語句的執行順序如下: 
(1). 首先執行 FROM 子句, 從 tb_Grade 表組裝數據源的數據 
(2). 執行 WHERE 子句, 篩選 tb_Grade 表中所有數據不爲 NULL 的數據 
(3). 執行 GROUP BY 子句, 把 tb_Grade 表按 "學生姓名" 列進行分組 
(4). 計算 max() 聚集函數, 按 "總成績" 求出總成績中最大的一些數值 
(5). 執行 HAVING 子句, 篩選課程的總成績大於 600 分的. 
(7). 執行 ORDER BY 子句, 把最後的結果按 "Max 成績" 進行排序.
=====================================================================================================
SQLServer2005中查詢語句的執行順序
 

--1.from
--2.on
--3.outer(join)
--4.where
--5.group by
--6.cube|rollup
--7.having
--8.select
--9.distinct
--10.order by
--11.top


1. 邏輯查詢處理步驟序號
(8)SELECT (9)DISTINCT (11)<TOP_specification> <select_list>
(1)FROM <left_table>
(3) <join_type> JOIN <right_table>
(2)    ON <join_condition>
(4)WHERE <where_condition>
(5)GROUP BY <group_by_list>
(6)WITH {CUBE | ROLLUP}
(7)HAVING <having_condition>
(10)ORDER BY <order_by_list>

 

每個步驟產生一個虛擬表,該虛擬表被用作下一個步驟的輸入。
只有最後一步生成的表返回給調用者。
如果沒有某一子句,則跳過相應的步驟。
1. FROM:
   對FROM子句中的前兩個表執行笛卡爾積,生成虛擬表VT1。
2. ON:
   對VT1應用ON篩選器。只有那些使<join_condition>爲真的行才被插入VT2。
3. OUTER(JOIN):
   如果指定了OUTER JOIN,保留表中未找到匹配的行將作爲外部行添加到VT2,生成VT3。
   如果FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重複執行步驟1到步驟3,直到處理完所有的表爲止。
4. 對VT3應用WHERE篩選器。只有使<where_condition>爲TRUE的行才被插入VT4。
5. GROUP BY:
   按GROUP BY 子句中的列列表對VT4中的行分組,生成VT5。
6. CUBE|ROLLUP:
   把超組插入VT5,生成VT6。
7. HAVING:
   對VT6應用HAVING篩選器。
   只有使<having_condition>爲TRUE的組纔會被插入VT7。

   注:having不能單獨使用,having子句是對分組後的記錄的篩選,所以有having必須要有group by
8. SELECT:
   處理SELECT列表,產生VT8。
9. DISTINCT:
   將重複的行從VT8中移除,產生VT9。
10. ORDER BY:
   將VT9中的行按ORDER BY子句中的列列表排序,生成一個有表(VC10)。
11. TOP:從VC10的開始處選擇指定數量或比例的行,生成表VT11,並返回給調用者。

   注:top n可以實現分頁

        select top 20 * from 僱員                                                                 ------第一頁

 

        select top 20 * from 僱員

        where 身份證號碼 not in (select top 20 身份證號碼 from 僱員)            ------第二頁

 

2. 準備數據

SET NOCOUNT ON;
USE tempdb;
GO
 
IF OBJECT_ID('dbo.Orders') IS NOT NULL
 DROP TABLE dbo.Orders;
GO
IF OBJECT_ID('dbo.Customers') IS NOT NULL
 DROP TABLE dbo.Customers;
GO
 
CREATE TABLE dbo.Customers
(
 customerid CHAR(5)    NOT NULL PRIMARY KEY,
 city       VARCHAR(10) NOT NULL
);
 
INSERT INTO dbo.Customers(customerid,city) VALUES('FISSA', 'Madrid');
INSERT INTO dbo.Customers(customerid,city) VALUES('FRNDO', 'Madrid');
INSERT INTO dbo.Customers(customerid,city) VALUES('KRLOS', 'Madrid');
INSERT INTO dbo.Customers(customerid,city) VALUES('MRPHS', 'Zion');
 
CREATE TABLE dbo.Orders
(
 orderid    INT     NOT NULL PRIMARY KEY,
 customerid CHAR(5) NULL     REFERENCES Customers(customerid)
);
 
INSERT INTO dbo.Orders(orderid, customerid) VALUES(1,'FRNDO');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(2,'FRNDO');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(3,'KRLOS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(4,'KRLOS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(5,'KRLOS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(6,'MRPHS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(7, NULL);

  執行結果:

TSQL查詢內幕: (1)邏輯查詢處理

TSQL查詢內幕: (1)邏輯查詢處理

3. 查詢語句

USE tempdb;
GO
SELECT C.customerid, COUNT(O.orderid) AS numorders
FROM dbo.Customers AS C
 LEFT OUTER JOIN dbo.Orders AS O
    ON C.customerid = O.customerid
WHERE C.city = 'Madrid'
GROUP BY C.customerid
HAVING COUNT(O.orderid) < 3
ORDER BY numorders;

  執行結果:

TSQL查詢內幕: (1)邏輯查詢處理

4. 邏輯查詢處理步驟詳解

 

1. 執行笛卡爾乘積,形成VT1。如果左表包含n行,右表包含m行,VT1將包含n×m行。

    執行結果VT1:

TSQL查詢內幕: (1)邏輯查詢處理

 

2. 應用ON 篩選器,只有<join_condition>爲TRUE的那些行纔會包含在VT2中。

ON C.customerid = O.customerid

  三值邏輯:

      TRUE、FALSE、UNKNOWN爲SQL中邏輯表達式的可能值。

  UNKNOWN值通常出現在含NULL值的邏輯表達式中,如NULL > 42; NULL = NULL; X + NULL > Y。

  NOT TRUE 等於 FALSE

  NOT FALSE 等於TRUE

  NOT UNKNOWN 等於 UNKNOWN

  所有的查詢篩選器,如ON、WHERE、HAVING把UNKNOWN看作爲FALSE處理。

  CHECK約束中的UNKNOWN值被當作TRUE對待。如果表中含有一個CHECK約束,要求salary列的值必須大於0,則插入salary爲NULL的行時可以被接受。

  UNIQUE約束、排序操作、分組操作認爲兩個NULL值是相等的。如,表中有一列定義了UNIQUE約束,則無法向表中插入該列值爲NULL的兩行。GROUP BY子句把所有NULL值分在一組。ORDERB BY子句把所有NULL值排列在一起。

  對VT1增加ON篩選器的結果VT2:

TSQL查詢內幕: (1)邏輯查詢處理

  

3. 添加外部行,通過指定LEFT、RIGHT、FULL中的一種OUTER JOIN,可以把左表、右表、所有表標記爲保留表。把一個表設爲保留表表示返回該表的所有行,即使<join_condition>已經執行過篩選。保留表中的這些行被稱爲外部行,外部行中非保留表的屬性被賦予NULL,最後生成VT3:

TSQL查詢內幕: (1)邏輯查詢處理

  

4. 應用WHERE篩選器,只有符合<where_condition>的行纔會成爲VT4的一部分。因爲數據還沒有被分組,所以不能使用聚合篩選器,例如WHERE orderdate = MAX(orderdate)。也不能飲用SELECT列表中的別名,因爲SELECT列表這時還沒有被處理,例如SELECT YEAR(orderdate) AS orderyear WHERE orderyear > 2000。

  對於包含OUTER JOIN子句的查詢,如何判斷到底是在ON篩選器還是在WHERE篩選器中指定邏輯表達式:ON在添加外部行前被應用,WHERE在外部行添加之後被應用。ON篩選器對保留表中部分行的一處不是最終的,因爲還要執行添加外部行的步驟,而WHERE篩選器對這些行的移除是最終的。

  只有在使用外部聯接時,ON和WHERE子句纔會存在這種邏輯限制,當使用內部聯接時,在那裏指定邏輯表達式都無所謂,因爲沒有上面的步驟3。

WHERE C.city = 'Madrid'

  生成虛擬表VT4:

TSQL查詢內幕: (1)邏輯查詢處理

  

5. 分組。GROUP BY子句中列列表的每個唯一的值組合成爲一組,生成VT5:

 

 

  Groups

 

 

  Raw

 

 

  C.customerid

 

 

TSQL查詢內幕: (1)邏輯查詢處理

 

 

  FISSA

 

 

  FRNDO

 

 

  KRLOS

 

 

  VT5由兩部分組成:Group Section和Raw Section。

  如果在查詢中指定了GROUP BY子句,則後面的所有步驟(如:HAVING、SELECT)只能指定可以爲成組得到的標量值的表達式。也就是說,表達式的結果是GROUP BY列表中的列/表達式(如:C.customer)或聚合函數(如:COUNT(O.orderid))。該限制是因爲最終的結果集中最多隻爲每一個組包含一行。

  這一階段認爲兩個NULL是相等的。所有的NULL值會被分配到一組。

  如果指定GROUP BY ALL,則在WHERE篩選中被移除的組將被添加到VT5中,且原始部分爲空集合。在後面的步驟中,對該組應用COUNT聚合函數的結果將爲0,應用其他聚合函數的結果爲NULL。最好不要使用GROUP BY ALL。

  

6. 使用CUBE或ROLLUP選項,將創建超組並把它添加到上一步返回的虛擬表中,生成VT6。

  

7. 應用HAVING 篩選器 ,只有符合<having_condition>的組纔會成爲VT7的一部分。HAVING是唯一的應用到已分組數據的篩選器。

HAVING COUNT(O.orderid) < 3

  在這裏使用了COUNT(O.orderid),而不是COUNT(*),所以外部行因爲O.orderid爲NULL,於是不計入COUNT中。如FISSA這組的COUNT(O.orderid)爲0.

  紅色部分爲被HAVING篩選掉的分組。

 

 

  Groups

 

 

  Raw

 

 

  C.customerid

 

 

TSQL查詢內幕: (1)邏輯查詢處理

 

 

  FISSA

 

 

  FRNDO

 

 

KRLOS

 

 

  

8. 處理SELECT列表,爲不是基列的表達式應用別名,使其在結果表中有一個名稱。在SELECT列表中創建的別名不能再前面的步驟中使用,甚至不能再SELECT列表中使用,只能在ORDER BY中使用。

SELECT C.customerid, COUNT(O.orderid) AS numorders

  生成VT8:

TSQL查詢內幕: (1)邏輯查詢處理

  邏輯上,應當假設所有操作同時發生。

  

9. 應用DISTINCT子句,如果查詢中指定了DISTINCT子句,將從上一步返回的虛擬表中移除重複行,並生成虛擬表VT9。使用GROUP BY,再使用DISTINCT是多餘的。

  

10. 應用ORDER BY子句,按照ORDER BY子句中的列列表排序上一步返回的行,返回遊標VC10。只有這一步可以使用SELECT別名。如果指定了DISTINCT,ORDER BY子句中的表達式只能訪問上一步返回的虛擬表,只能按已經SELECT的列排序。

  ANSI SQL 1999中增強了ORDER BY的支持,允許訪問SELECT階段的輸入虛擬表和輸出虛擬表。就是說如果未指定DISTINCT,可以在ORDER BY子句中指定任何可以在SELECT子句中使用的表達式,可以按最後結果集中不存在的表達式排序。

ORDER BY numorders;

  也可以在ORDER BY子句中指定SELECT列表中結果列的序號:

ORDER BY 2, 1;

  但是儘量不要這樣去做,因爲可能改變了SELECT列表卻忘記了修改ORDER BY列表,而且當SELECT列表很長時,查序號不是一個好方法。

  因爲這一步不是返回表,而是返回遊標,使用了ORDER BY子句的查詢不能用作表表達式。表表達式包括:視圖、內聯表值函數、子查詢、派生表和共用表表達式(CTE)。

  不要爲表中的行假定順序,除非確實需要有序行,否則不要指定ORDER BY子句。排序是需要成本的,SQL Server需要執行有序索引掃描或使用排序運算符。

  ORDER BY這一步認爲兩個NULL是相等的,所有的NULL會被排列在一起,ANSI並沒有規定NULL比已知值高還是低,而是把這個問題留給了具體實現,在T-SQL中NULL排位比已知值低。

ORDER BY numorders

  返回的遊標VC10:

TSQL查詢內幕: (1)邏輯查詢處理

 

11. 應用TOP選項,從遊標的最前面選擇指定的行數,生成表VT11並返回給調用者。在SQLServer 2000中,TOP的輸入必須爲常量,而在2005中可以是任何獨立的表達式。

  如果沒有ORDER BY子句或WITH TIES選項,返回的行正好是物理上最先訪問的行,可能會產生不同的結果。

  只有指定了TOP選項,纔可以在表表達式中使用帶有ORDER BY子句的查詢:

SELECT *
FROM (SELECT TOP 100 PERCENT orderid, customerid
        FROM dbo.Orders
        ORDER BY orderid) AS D;


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章