佳木斯湛栽影视文化发展公司

主頁 > 知識(shí)庫 > 關(guān)于SQL中CTE(公用表表達(dá)式)(Common Table Expression)的總結(jié)

關(guān)于SQL中CTE(公用表表達(dá)式)(Common Table Expression)的總結(jié)

熱門標(biāo)簽:地方門戶網(wǎng)站 網(wǎng)站排名優(yōu)化 百度競(jìng)價(jià)排名 AI電銷 呼叫中心市場(chǎng)需求 服務(wù)外包 鐵路電話系統(tǒng) Linux服務(wù)器
一.WITH AS的含義

WITH AS短語,也叫做子查詢部分(subquery factoring),可以讓你做很多事情,定義一個(gè)SQL片斷,該SQL片斷會(huì)被整個(gè)SQL語句所用到。有的時(shí)候,是為了讓SQL語句的可讀性更高些,也有可能是在UNION ALL的不同部分,作為提供數(shù)據(jù)的部分。
特別對(duì)于UNION ALL比較有用。因?yàn)閁NION ALL的每個(gè)部分可能相同,但是如果每個(gè)部分都去執(zhí)行一遍的話,則成本太高,所以可以使用WITH AS短語,則只要執(zhí)行一遍即可。如果WITH AS短語所定義的表名被調(diào)用兩次以上,則優(yōu)化器會(huì)自動(dòng)將WITH AS短語所獲取的數(shù)據(jù)放入一個(gè)TEMP表里,如果只是被調(diào)用一次,則不會(huì)。而提示materialize則是強(qiáng)制將WITH AS短語里的數(shù)據(jù)放入一個(gè)全局臨時(shí)表里。很多查詢通過這種方法都可以提高速度。

二.使用方法

先看下面一個(gè)嵌套的查詢語句:
復(fù)制代碼 代碼如下:

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

上面的查詢語句使用了一個(gè)子查詢。雖然這條SQL語句并不復(fù)雜,但如果嵌套的層次過多,會(huì)使SQL語句非常難以閱讀和維護(hù)。因此,也可以使用表變量的方式來解決這個(gè)問題,SQL語句如下:
復(fù)制代碼 代碼如下:

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語句要比第一種方式更復(fù)雜,但卻將子查詢放在了表變量@t中,這樣做將使SQL語句更容易維護(hù),但又會(huì)帶來另一個(gè)問題,就是性能的損失。由于表變量實(shí)際上使用了臨時(shí)表,從而增加了額外的I/O開銷,因此,表變量的方式并不太適合數(shù)據(jù)量大且頻繁查詢的情況。為此,在SQL Server 2005中提供了另外一種解決方案,這就是公用表表達(dá)式(CTE),使用CTE,可以使SQL語句的可維護(hù)性,同時(shí),CTE要比表變量的效率高得多。
下面是CTE的語法:
復(fù)制代碼 代碼如下:

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

現(xiàn)在使用CTE來解決上面的問題,SQL語句如下:
復(fù)制代碼 代碼如下:

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

其中cr是一個(gè)公用表表達(dá)式,該表達(dá)式在使用上與表變量類似,只是SQL Server 2005在處理公用表表達(dá)式的方式上有所不同。
在使用CTE時(shí)應(yīng)注意如下幾點(diǎn):
1. CTE后面必須直接跟使用CTE的SQL語句(如select、insert、update等),否則,CTE將失效。如下面的SQL語句將無法正常使用CTE:
復(fù)制代碼 代碼如下:

with
cr as
(
select CountryRegionCode from person.CountryRegion where Name like 'C%'
)
select * from person.CountryRegion -- 應(yīng)將這條SQL語句去掉
-- 使用CTE的SQL語句應(yīng)緊跟在相關(guān)的CTE后面 --
select * from person.StateProvince where CountryRegionCode in (select * from cr)

2. CTE后面也可以跟其他的CTE,但只能使用一個(gè)with,多個(gè)CTE中間用逗號(hào)(,)分隔,如下面的SQL語句所示:
復(fù)制代碼 代碼如下:

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的表達(dá)式名稱與某個(gè)數(shù)據(jù)表或視圖重名,則緊跟在該CTE后面的SQL語句使用的仍然是CTE,當(dāng)然,后面的SQL語句使用的就是數(shù)據(jù)表或視圖了,如下面的SQL語句所示:
復(fù)制代碼 代碼如下:

-- table1是一個(gè)實(shí)際存在的表
with
table1 as
(
select * from persons where age 30
)
select * from table1 -- 使用了名為table1的公共表表達(dá)式
select * from table1 -- 使用了名為table1的數(shù)據(jù)表

4. CTE 可以引用自身,也可以引用在同一 WITH 子句中預(yù)先定義的 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 用在屬于批處理的一部分的語句中,那么在它之前的語句必須以分號(hào)結(jié)尾,如下面的SQL所示:
復(fù)制代碼 代碼如下:

declare @s nvarchar(3)
set @s = 'C%'
; -- 必須加分號(hào)
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除了可以簡(jiǎn)化嵌套SQL語句外,還可以進(jìn)行遞歸調(diào)用。

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

按照MSDN介紹

1、公用表表達(dá)式 (CTE) 可以認(rèn)為是在單個(gè) SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執(zhí)行范圍內(nèi)定義的臨時(shí)結(jié)果集。CTE 與派生表類似,具體表現(xiàn)在不存儲(chǔ)為對(duì)象,并且只在查詢期間有效。與派生表的不同之處在于,CTE 可自引用,還可在同一查詢中引用多次。
CTE 可用于:
創(chuàng)建遞歸查詢。有關(guān)詳細(xì)信息,請(qǐng)參閱使用公用表表達(dá)式的遞歸查詢。

在不需要常規(guī)使用視圖時(shí)替換視圖,也就是說,不必將定義存儲(chǔ)在元數(shù)據(jù)中。

啟用按從標(biāo)量嵌套 select 語句派生的列進(jìn)行分組,或者按不確定性函數(shù)或有外部訪問的函數(shù)進(jìn)行分組。


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

使用 CTE 可以獲得提高可讀性和輕松維護(hù)復(fù)雜查詢的優(yōu)點(diǎn)。查詢可以分為單獨(dú)塊、簡(jiǎn)單塊、邏輯生成塊。之后,這些簡(jiǎn)單塊可用于生成更復(fù)雜的臨時(shí) CTE,直到生成最終結(jié)果集??梢栽谟脩舳x的例程(如函數(shù)、存儲(chǔ)過程、觸發(fā)器或視圖)中定義 CTE。
2、公用表表達(dá)式 (CTE) 具有一個(gè)重要的優(yōu)點(diǎn),那就是能夠引用其自身,從而創(chuàng)建遞歸 CTE。遞歸 CTE 是一個(gè)重復(fù)執(zhí)行初始 CTE 以返回?cái)?shù)據(jù)子集直到獲取完整結(jié)果集的公用表表達(dá)式。當(dāng)某個(gè)查詢引用遞歸 CTE 時(shí),它即被稱為遞歸查詢。遞歸查詢通常用于返回分層數(shù)據(jù),例如:顯示某個(gè)組織圖中的雇員或物料清單方案(其中父級(jí)產(chǎn)品有一個(gè)或多個(gè)組件,而那些組件可能還有子組件,或者是其他父級(jí)產(chǎn)品的組件)中的數(shù)據(jù)。
遞 歸 CTE 可以極大地簡(jiǎn)化在 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句中運(yùn)行遞歸查詢所需的代碼。在 SQL Server 的早期版本中,遞歸查詢通常需要使用臨時(shí)表、游標(biāo)和邏輯來控制遞歸步驟流。有關(guān)公用表表達(dá)式的詳細(xì)信息,請(qǐng)參閱使用公用表表達(dá)式。

這里舉例說明如下:

為了描述方便,邀月特地列舉了一個(gè)常見的自關(guān)聯(lián)Table
表結(jié)構(gòu)如下:
表結(jié)構(gòu)
復(fù)制代碼 代碼如下:

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

再插入一些測(cè)試數(shù)據(jù)
復(fù)制代碼 代碼如下:

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,'邀月備注'

一個(gè)典型的應(yīng)用場(chǎng)景是:在這個(gè)自關(guān)聯(lián)的表中,查詢以PKID為2的分類包含所有子分類。也許很多情況下,我們不得不用臨時(shí)表/表變量/游標(biāo)等?,F(xiàn)在我們有了CTE,就簡(jiǎn)單多了
復(fù)制代碼 代碼如下:

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

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

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

查詢結(jié)果:
C_Name C_ParentName C_ParentCode
分類5 分類2 2
分類8 分類5 2/5
當(dāng)然,我們不是說CTE就是萬能的。通過好的表設(shè)計(jì)也可以某種程度上解決特定的問題。下面用常規(guī)的SQL實(shí)現(xiàn)上面這個(gè)需求。
注意:上面表中有一個(gè)字段很重要,就是C_Code,編碼 ,格式如"1/2",“2/5/8"表示該分類的上級(jí)分類是1/2,2/5/8
這樣,我們查詢就簡(jiǎn)單多,查詢以PKID為2的分類包含所有子分類:
復(fù)制代碼 代碼如下:

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的分類包含所有子分類,且級(jí)別不大于3
復(fù)制代碼 代碼如下:

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

查詢結(jié)果同上,略去。這里我們看出,有時(shí)候,好的表結(jié)構(gòu)設(shè)計(jì)相當(dāng)重要。
邀月于2009.10.23 1:36 完成分享。
有人很關(guān)心性能問題。目前沒有測(cè)試過。稍后會(huì)附上百萬級(jí)測(cè)試報(bào)告。不過,有兩點(diǎn)理解邀月忘了補(bǔ)充:
一、CTE其實(shí)是面向?qū)ο蟮模\(yùn)行的基礎(chǔ)是CLR。一個(gè)很好的說明是With查詢語句中是區(qū)分字段的大小寫的。即"C_Code"和"c_Code"是不一樣的,后者會(huì)報(bào)錯(cuò)。這與普通的SQL語句不同。
二、 這個(gè)應(yīng)用示例重在簡(jiǎn)化業(yè)務(wù)邏輯,即便是性能不佳,但對(duì)臨時(shí)表/表變量/游標(biāo)等傳統(tǒng)處理方式是一種業(yè)務(wù)層次上的簡(jiǎn)化或者說是優(yōu)化。

公用表表達(dá)式 (CTE) 可以認(rèn)為是在單個(gè) SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執(zhí)行范圍內(nèi)定義的臨時(shí)結(jié)果集。CTE 與派生表類似,具體表現(xiàn)在不存儲(chǔ)為對(duì)象,并且只在查詢期間有效。與派生表的不同之處在于,CTE 可自引用,還可在同一查詢中引用多次。
CTE 可用于:
創(chuàng)建遞歸查詢。
在不需要常規(guī)使用視圖時(shí)替換視圖,也就是說,不必將定義存儲(chǔ)在元數(shù)據(jù)中。
啟用按從標(biāo)量嵌套 select 語句派生的列進(jìn)行分組,或者按不確定性函數(shù)或有外部訪問的函數(shù)進(jìn)行分組。
在同一語句中多次引用生成的表。
使用 CTE 可以獲得提高可讀性和輕松維護(hù)復(fù)雜查詢的優(yōu)點(diǎn)。查詢可以分為單獨(dú)塊、簡(jiǎn)單塊、邏輯生成塊。之后,這些簡(jiǎn)單塊可用于生成更復(fù)雜的臨時(shí) CTE,直到生成最終結(jié)果集。
可以在用戶定義的例程(如函數(shù)、存儲(chǔ)過程、觸發(fā)器或視圖)中定義 CTE。
CTE 的結(jié)構(gòu)
CTE 由表示 CTE 的表達(dá)式名稱、可選列列表和定義 CTE 的查詢組成。定義 CTE 后,可以在 SELECT、INSERT、UPDATE 或 DELETE 語句中對(duì)其進(jìn)行引用,就像引用表或視圖一樣。CTE 也可用于 CREATE VIEW 語句,作為定義 SELECT 語句的一部分。
CTE 的基本語法結(jié)構(gòu)如下:
WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )
只有在查詢定義中為所有結(jié)果列都提供了不同的名稱時(shí),列名稱列表才是可選的。
運(yùn)行 CTE 的語句為:
SELECT column_list>
FROM expression_name
示例:
復(fù)制代碼 代碼如下:

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時(shí)應(yīng)注意如下幾點(diǎn):
1. CTE后面必須直接跟使用CTE的SQL語句(如select、insert、update等),否則,CTE將失效。
2. CTE后面也可以跟其他的CTE,但只能使用一個(gè)with,多個(gè)CTE中間用逗號(hào)(,)分隔。
3. 如果CTE的表達(dá)式名稱與某個(gè)數(shù)據(jù)表或視圖重名,則緊跟在該CTE后面的SQL語句使用的仍然是CTE,當(dāng)然,后面的SQL語句使用的就是數(shù)據(jù)表或視圖了。
4. CTE 可以引用自身(遞歸查詢),也可以引用在同一 WITH 子句中預(yù)先定義的 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 用在屬于批處理的一部分的語句中,那么在它之前的語句必須以分號(hào)結(jié)尾。

標(biāo)簽:蘭州 仙桃 銅川 湖南 湘潭 崇左 黃山 衡水

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《關(guān)于SQL中CTE(公用表表達(dá)式)(Common Table Expression)的總結(jié)》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    夹江县| 宁都县| 景东| 吴旗县| 临夏县| 紫金县| 大冶市| 罗源县| 白山市| 平顶山市| 星子县| 安新县| 双流县| 广水市| 永吉县| 郁南县| 正蓝旗| 威信县| 红桥区| 扬中市| 合江县| 阿图什市| 彭水| 泾阳县| 玛纳斯县| 萝北县| 定陶县| 吴川市| 廉江市| 恩平市| 宜兴市| 塔河县| 逊克县| 平定县| 务川| 揭西县| 莱西市| 邯郸县| 南岸区| 清苑县| 如皋市|