關於SQL中CTE(公用表表達式)(Common Table Expression)的總結

一.WITH AS的含義 

WITH AS短語,也叫做子查詢部分(subquery factoring),可以讓你做很多事情,定義一個SQL片斷,該SQL片斷會被整個SQL語句所用到。有的時候,是爲了讓SQL語句的可讀性更高些,也有可能是在UNION ALL的不同部分,作爲提供數據的部分。 
特別對於UNION ALL比較有用。因爲UNION ALL的每個部分可能相同,但是如果每個部分都去執行一遍的話,則成本太高,所以可以使用WITH AS短語,則只要執行一遍即可。如果WITH AS短語所定義的表名被調用兩次以上,則優化器會自動將WITH AS短語所獲取的數據放入一個TEMP表裏,如果只是被調用一次,則不會。而提示materialize則是強制將WITH AS短語裏的數據放入一個全局臨時表裏。很多查詢通過這種方法都可以提高速度。 

二.使用方法 

先看下面一個嵌套的查詢語句: 
複製代碼代碼如下:

select * from person.StateProvince where CountryRegionCode in 
(select CountryRegionCode from person.CountryRegion where Name like 'C%') 

上面的查詢語句使用了一個子查詢。雖然這條SQL語句並不複雜,但如果嵌套的層次過多,會使SQL語句非常難以閱讀和維護。因此,也可以使用表變量的方式來解決這個問題,SQL語句如下: 
複製代碼代碼如下:

declare @t table(CountryRegionCode nvarchar(3)) 
insert into @t(CountryRegionCode) (select CountryRegionCode from person.CountryRegion where Name like 'C%') 
select * from person.StateProvince where CountryRegionCode 
in (select * from @t) 

雖然上面的SQL語句要比第一種方式更復雜,但卻將子查詢放在了表變量@t中,這樣做將使SQL語句更容易維護,但又會帶來另一個問題,就是性能的損失。由於表變量實際上使用了臨時表,從而增加了額外的I/O開銷,因此,表變量的方式並不太適合數據量大且頻繁查詢的情況。爲此,在SQL Server 2005中提供了另外一種解決方案,這就是公用表表達式(CTE),使用CTE,可以使SQL語句的可維護性,同時,CTE要比表變量的效率高得多。 
下面是CTE的語法: 
複製代碼代碼如下:

[ WITH <common_table_expression> [ ,n ] ] 
<common_table_expression>::= 
expression_name [ ( column_name [ ,n ] ) ] 
AS 
( CTE_query_definition ) 

現在使用CTE來解決上面的問題,SQL語句如下: 
複製代碼代碼如下:

with 
cr as 

select CountryRegionCode from person.CountryRegion where Name like 'C%' 

select * from person.StateProvince where CountryRegionCode in (select * from cr) 

其中cr是一個公用表表達式,該表達式在使用上與表變量類似,只是SQL Server 2005在處理公用表表達式的方式上有所不同。 
在使用CTE時應注意如下幾點: 
1. CTE後面必須直接跟使用CTE的SQL語句(如select、insert、update等),否則,CTE將失效。如下面的SQL語句將無法正常使用CTE: 
複製代碼代碼如下:

with 
cr as 

select CountryRegionCode from person.CountryRegion where Name like 'C%' 

select * from person.CountryRegion -- 應將這條SQL語句去掉 
-- 使用CTE的SQL語句應緊跟在相關的CTE後面 -- 
select * from person.StateProvince where CountryRegionCode in (select * from cr) 

2. CTE後面也可以跟其他的CTE,但只能使用一個with,多個CTE中間用逗號(,)分隔,如下面的SQL語句所示: 
複製代碼代碼如下:

with 
cte1 as 

select * from table1 where name like 'abc%' 
), 
cte2 as 

select * from table2 where id > 20 
), 
cte3 as 

select * from table3 where price < 100 

select a.* from cte1 a, cte2 b, cte3 c where a.id = b.id and a.id = c.id 

3. 如果CTE的表達式名稱與某個數據表或視圖重名,則緊跟在該CTE後面的SQL語句使用的仍然是CTE,當然,後面的SQL語句使用的就是數據表或視圖了,如下面的SQL語句所示: 
複製代碼代碼如下:

-- table1是一個實際存在的表 
with 
table1 as 

select * from persons where age < 30 

select * from table1 -- 使用了名爲table1的公共表表達式 
select * from table1 -- 使用了名爲table1的數據表 

4. CTE 可以引用自身,也可以引用在同一 WITH 子句中預先定義的 CTE。不允許前向引用。 
5. 不能在 CTE_query_definition 中使用以下子句: 
(1)COMPUTE 或 COMPUTE BY 
(2)ORDER BY(除非指定了 TOP 子句) 
(3)INTO 
(4)帶有查詢提示的 OPTION 子句 
(5)FOR XML 
(6)FOR BROWSE 
6. 如果將 CTE 用在屬於批處理的一部分的語句中,那麼在它之前的語句必須以分號結尾,如下面的SQL所示: 
複製代碼代碼如下:

declare @s nvarchar(3) 
set @s = 'C%' 
; -- 必須加分號 
with 
t_tree as 

select CountryRegionCode from person.CountryRegion where Name like @s 

select * from person.StateProvince where CountryRegionCode in (select * from t_tree) 

CTE除了可以簡化嵌套SQL語句外,還可以進行遞歸調用。 

微軟從SQl2005起引入了CTE(Common Table Expression)以強化T-SQL。這是一個類似於非持久視圖的好東東。 

按照MSDN介紹 

1、公用表表達式 (CTE) 可以認爲是在單個 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執行範圍內定義的臨時結果集。CTE 與派生表類似,具體表現在不存儲爲對象,並且只在查詢期間有效。與派生表的不同之處在於,CTE 可自引用,還可在同一查詢中引用多次。 
CTE 可用於: 
創建遞歸查詢。有關詳細信息,請參閱使用公用表表達式的遞歸查詢。 

在不需要常規使用視圖時替換視圖,也就是說,不必將定義存儲在元數據中。 

啓用按從標量嵌套 select 語句派生的列進行分組,或者按不確定性函數或有外部訪問的函數進行分組。 


在同一語句中多次引用生成的表。 

使用 CTE 可以獲得提高可讀性和輕鬆維護複雜查詢的優點。查詢可以分爲單獨塊、簡單塊、邏輯生成塊。之後,這些簡單塊可用於生成更復雜的臨時 CTE,直到生成最終結果集。可以在用戶定義的例程(如函數、存儲過程、觸發器或視圖)中定義 CTE。 
2、公用表表達式 (CTE) 具有一個重要的優點,那就是能夠引用其自身,從而創建遞歸 CTE。遞歸 CTE 是一個重複執行初始 CTE 以返回數據子集直到獲取完整結果集的公用表表達式。當某個查詢引用遞歸 CTE 時,它即被稱爲遞歸查詢。遞歸查詢通常用於返回分層數據,例如:顯示某個組織圖中的僱員或物料清單方案(其中父級產品有一個或多個組件,而那些組件可能還有子組件,或者是其他父級產品的組件)中的數據。 
遞 歸 CTE 可以極大地簡化在 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句中運行遞歸查詢所需的代碼。在 SQL Server 的早期版本中,遞歸查詢通常需要使用臨時表、遊標和邏輯來控制遞歸步驟流。有關公用表表達式的詳細信息,請參閱使用公用表表達式。 

這裏舉例說明如下: 

爲了描述方便,邀月特地列舉了一個常見的自關聯Table 
表結構如下: 
表結構 
複製代碼代碼如下:

CREATE TABLE [dbo].[CategorySelf]( 
[PKID] [int] IDENTITY(1,1) NOT NULL, 
[C_Name] [nvarchar](50) NOT NULL, 
[C_Level] [int] NOT NULL, 
[C_Code] [nvarchar](255) NULL, 
[C_Parent] [int] NOT NULL, 
[InsertTime] [datetime] NOT NULL, 
[InsertUser] [nvarchar](50) NULL, 
[UpdateTime] [datetime] NOT NULL, 
[UpdateUser] [nvarchar](50) NULL, 
[SortLevel] [int] NOT NULL, 
[CurrState] [smallint] NOT NULL, 
[F1] [int] NOT NULL, 
[F2] [nvarchar](255) NULL 

CONSTRAINT [PK_OBJECTCATEGORYSELF] PRIMARY KEY CLUSTERED 

[PKID] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

GO 

再插入一些測試數據 
複製代碼代碼如下:

Insert 
INSERT INTO [CategorySelf]([C_Name],[C_Level] ,[C_Code],[C_Parent] ,[InsertTime] ,[InsertUser] ,[UpdateTime] ,[UpdateUser] ,[SortLevel] ,[CurrState] ,[F1] ,[F2]) 
select '分類1',1,'0',0,GETDATE(),'testUser',DATEADD(dd,1,getdate()),'CrackUser',13,0,1,'邀月備註' union all 
select '分類2',1,'0',0,GETDATE(),'testUser',DATEADD(dd,78,getdate()),'CrackUser',12,0,1,'邀月備註' union all 
select '分類3',1,'0',0,GETDATE(),'testUser',DATEADD(dd,6,getdate()),'CrackUser',10,0,1,'邀月備註' union all 
select '分類4',2,'1',1,GETDATE(),'testUser',DATEADD(dd,75,getdate()),'CrackUser',19,0,1,'邀月備註' union all 
select '分類5',2,'2',2,GETDATE(),'testUser',DATEADD(dd,3,getdate()),'CrackUser',17,0,1,'邀月備註' union all 
select '分類6',3,'1/4',4,GETDATE(),'testUser',DATEADD(dd,4,getdate()),'CrackUser',16,0,1,'邀月備註' union all 
select '分類7',3,'1/4',4,GETDATE(),'testUser',DATEADD(dd,5,getdate()),'CrackUser',4,0,1,'邀月備註' union all 
select '分類8',3,'2/5',5,GETDATE(),'testUser',DATEADD(dd,6,getdate()),'CrackUser',3,0,1,'邀月備註' union all 
select '分類9',4,'1/4/6',6,GETDATE(),'testUser',DATEADD(dd,7,getdate()),'CrackUser',5,0,1,'邀月備註' union all 
select '分類10',4,'1/4/6',6,GETDATE(),'testUser',DATEADD(dd,7,getdate()),'CrackUser',63,0,1,'邀月備註' union all 
select '分類11',4,'1/4/6',6,GETDATE(),'testUser',DATEADD(dd,8,getdate()),'CrackUser',83,0,1,'邀月備註' union all 
select '分類12',4,'2/5/8',8,GETDATE(),'testUser',DATEADD(dd,10,getdate()),'CrackUser',3,0,1,'邀月備註' union all 
select '分類13',4,'2/5/8',8,GETDATE(),'testUser',DATEADD(dd,15,getdate()),'CrackUser',1,0,1,'邀月備註'

一個典型的應用場景是:在這個自關聯的表中,查詢以PKID爲2的分類包含所有子分類。也許很多情況下,我們不得不用臨時表/表變量/遊標等。現在我們有了CTE,就簡單多了 
複製代碼代碼如下:

CTEDemo1 
WITH SimpleRecursive(C_Name, PKID, C_Code,C_Parent) 
AS 
(SELECT C_Name, PKID, C_Code,C_Parent FROM CategorySelf WHERE PKID = 2 
UNION ALL 
SELECT p.C_Name, p.PKID, p.C_Code,p.C_parent 
FROM CategorySelf P INNER JOIN 
SimpleRecursive A ON A.PKID = P.C_Parent 

SELECT sr.C_Name as C_Name, c.C_Name as C_ParentName,sr.C_Code as C_ParentCode 
FROM SimpleRecursive sr inner join CategorySelf c 
on sr.C_Parent=c.PKID 

查詢結果如下:C_Name C_ParentName C_ParentCode 
分類5 分類2 2 
分類8 分類5 2/5 
分類12 分類8 2/5/8 
分類13 分類8 2/5/8 
感覺怎麼樣?如果我只想查詢第二層,而不是默認的無限查詢下去, 
可以在上面的SQL後加一個選項 Option(MAXRECURSION 5),注意5表示到第5層就不往下找了。如果只想找第二層,但實際結果有三層,此時會出錯, 
Msg 530, Level 16, State 1, Line 1 
The statement terminated. The maximum recursion 1 has been exhausted before statement completion. 
此時可以通過where條件來解決,而保證不出錯,看如下SQL語句: 
CTEDemo2 
複製代碼代碼如下:

WITH SimpleRecursive(C_Name, PKID, C_Code,C_Parent,Sublevel) 
AS 
(SELECT C_Name, PKID, C_Code,C_Parent,0 FROM CategorySelf WHERE PKID = 2 
UNION ALL 
SELECT p.C_Name, p.PKID, p.C_Code,p.C_parent,Sublevel+1 
FROM CategorySelf P INNER JOIN 
SimpleRecursive A ON A.PKID = P.C_Parent 

SELECT sr.C_Name as C_Name, c.C_Name as C_ParentName,sr.C_Code as C_ParentCode 
FROM SimpleRecursive sr inner join CategorySelf c 
on sr.C_Parent=c.PKID 
where SubLevel<=2 

查詢結果: 
C_Name C_ParentName C_ParentCode 
分類5 分類2 2 
分類8 分類5 2/5 
當然,我們不是說CTE就是萬能的。通過好的表設計也可以某種程度上解決特定的問題。下面用常規的SQL實現上面這個需求。 
注意:上面表中有一個字段很重要,就是C_Code,編碼 ,格式如"1/2",“2/5/8"表示該分類的上級分類是1/2,2/5/8 
這樣,我們查詢就簡單多,查詢以PKID爲2的分類包含所有子分類: 
複製代碼代碼如下:

SELECT C_Name as C_Name, (Select top 1 C_Name from CategorySelf s where c.C_Parent=s.PKID) as C_ParentName,C_Code as C_ParentCode 
from CategorySelf c where C_Code like '2/%' 

查詢以PKID爲2的分類包含所有子分類,且級別不大於3 
複製代碼代碼如下:

SELECT C_Name as C_Name, (Select top 1 C_Name from CategorySelf s where c.C_Parent=s.PKID) as C_ParentName,C_Code as C_ParentCode 
from CategorySelf c where C_Code like '2/%' and C_Level<=3 

查詢結果同上,略去。這裏我們看出,有時候,好的表結構設計相當重要。 
邀月於2009.10.23 1:36 完成分享。 
有人很關心性能問題。目前沒有測試過。稍後會附上百萬級測試報告。不過,有兩點理解邀月忘了補充: 
一、CTE其實是面向對象的,運行的基礎是CLR。一個很好的說明是With查詢語句中是區分字段的大小寫的。即"C_Code"和"c_Code"是不一樣的,後者會報錯。這與普通的SQL語句不同。 
二、 這個應用示例重在簡化業務邏輯,即便是性能不佳,但對臨時表/表變量/遊標等傳統處理方式是一種業務層次上的簡化或者說是優化。 

公用表表達式 (CTE) 可以認爲是在單個 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執行範圍內定義的臨時結果集。CTE 與派生表類似,具體表現在不存儲爲對象,並且只在查詢期間有效。與派生表的不同之處在於,CTE 可自引用,還可在同一查詢中引用多次。 
CTE 可用於: 
創建遞歸查詢。 
在不需要常規使用視圖時替換視圖,也就是說,不必將定義存儲在元數據中。 
啓用按從標量嵌套 select 語句派生的列進行分組,或者按不確定性函數或有外部訪問的函數進行分組。 
在同一語句中多次引用生成的表。 
使用 CTE 可以獲得提高可讀性和輕鬆維護複雜查詢的優點。查詢可以分爲單獨塊、簡單塊、邏輯生成塊。之後,這些簡單塊可用於生成更復雜的臨時 CTE,直到生成最終結果集。 
可以在用戶定義的例程(如函數、存儲過程、觸發器或視圖)中定義 CTE。 
CTE 的結構 
CTE 由表示 CTE 的表達式名稱、可選列列表和定義 CTE 的查詢組成。定義 CTE 後,可以在 SELECT、INSERT、UPDATE 或 DELETE 語句中對其進行引用,就像引用表或視圖一樣。CTE 也可用於 CREATE VIEW 語句,作爲定義 SELECT 語句的一部分。 
CTE 的基本語法結構如下: 
WITH expression_name [ ( column_name [,...n] ) ] 
AS 
( CTE_query_definition ) 
只有在查詢定義中爲所有結果列都提供了不同的名稱時,列名稱列表纔是可選的。 
運行 CTE 的語句爲: 
SELECT <column_list> 
FROM expression_name 
示例: 
複製代碼代碼如下:

with s_name as 

select s.name, sc.c,sc.grade from SQL Server student AS s,sc 
where s.s#=sc.s# 

select * from s_name 

在使用CTE時應注意如下幾點: 
1. CTE後面必須直接跟使用CTE的SQL語句(如select、insert、update等),否則,CTE將失效。 
2. CTE後面也可以跟其他的CTE,但只能使用一個with,多個CTE中間用逗號(,)分隔。 
3. 如果CTE的表達式名稱與某個數據表或視圖重名,則緊跟在該CTE後面的SQL語句使用的仍然是CTE,當然,後面的SQL語句使用的就是數據表或視圖了。 
4. CTE 可以引用自身(遞歸查詢),也可以引用在同一 WITH 子句中預先定義的 CTE。不允許前向引用。 
5. 不能在 CTE_query_definition 中使用以下子句: 
(1)COMPUTE 或 COMPUTE BY 
(2)ORDER BY(除非指定了 TOP 子句) 
(3)INTO 
(4)帶有查詢提示的 OPTION 子句 
(5)FOR XML 
(6)FOR BROWSE 

6. 如果將 CTE 用在屬於批處理的一部分的語句中,那麼在它之前的語句必須以分號結尾。



轉:http://www.jb51.net/article/30976.htm

發佈了33 篇原創文章 · 獲贊 10 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章