SQL Server 2005/2008中的CTE應(yīng)用之遞歸查詢
SQL Server 2005/2008中的CTE應(yīng)用之遞歸查詢是本文我們主要要介紹的內(nèi)容,接下來我們首先介紹一下MSDN中關(guān)于CTE的相關(guān)知識,然后通過實際的例子進行說明,希望能夠?qū)δ兴鶐椭?/p>
按照MSDN介紹
1、公用表表達式 (CTE) 可以認(rèn)為是在單個 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句的執(zhí)行范圍內(nèi)定義的臨時結(jié)果集。CTE 與派生表類似,具體表現(xiàn)在不存儲為對象,并且只在查詢期間有效。與派生表的不同之處在于,CTE 可自引用,還可在同一查詢中引用多次。
CTE 可用于:
- 創(chuàng)建遞歸查詢。有關(guān)詳細(xì)信息,請參閱使用公用表表達式的遞歸查詢。
- 在不需要常規(guī)使用視圖時替換視圖,也就是說,不必將定義存儲在元數(shù)據(jù)中。
- 啟用按從標(biāo)量嵌套 select 語句派生的列進行分組,或者按不確定性函數(shù)或有外部訪問的函數(shù)進行分組。
- 在同一語句中多次引用生成的表。
使用CTE 可以獲得提高可讀性和輕松維護復(fù)雜查詢的優(yōu)點。查詢可以分為單獨塊、簡單塊、邏輯生成塊。之后,這些簡單塊可用于生成更復(fù)雜的臨時 CTE,直到生成最終結(jié)果集??梢栽谟脩舳x的例程(如函數(shù)、存儲過程、觸發(fā)器或視圖)中定義 CTE。
2、公用表表達式 (CTE) 具有一個重要的優(yōu)點,那就是能夠引用其自身,從而創(chuàng)建遞歸 CTE。遞歸 CTE 是一個重復(fù)執(zhí)行初始 CTE 以返回數(shù)據(jù)子集直到獲取完整結(jié)果集的公用表表達式。當(dāng)某個查詢引用遞歸 CTE 時,它即被稱為遞歸查詢。遞歸查詢通常用于返回分層數(shù)據(jù),例如:顯示某個組織圖中的雇員或物料清單方案(其中父級產(chǎn)品有一個或多個組件,而那些組件可能還有子組件,或者是其他父級產(chǎn)品的組件)中的數(shù)據(jù)。
遞歸CTE可以極大地簡化在 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句中運行遞歸查詢所需的代碼。在 SQL Server 的早期版本中,遞歸查詢通常需要使用臨時表、游標(biāo)和邏輯來控制遞歸步驟流。有關(guān)公用表表達式的詳細(xì)信息,請參閱使用公用表表達式。
為了描述方便,邀月特地列舉了一個常見的自關(guān)聯(lián)Table。
表結(jié)構(gòu)如下:
表結(jié)構(gòu):
- 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
再插入一些測試數(shù)據(jù):
- 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,'邀月備注'
一個典型的應(yīng)用場景是:在這個自關(guān)聯(lián)的表中,查詢以PKID為2的分類包含所有子分類。也許很多情況下,我們不得不用臨時表\表變量\游標(biāo)等?,F(xiàn)在我們有了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
查詢結(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后加一個選項 Option(MAXRECURSION 5),注意5表示到第5層就不往下找了。如果只想找第二層,但實際結(jié)果有三層,此時會出錯: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
查詢結(jié)果:
- C_Name C_ParentName C_ParentCode
- 分類5 分類2 2
- 分類8 分類5 2/5
當(dāng)然,我們不是說CTE就是***的。通過好的表設(shè)計也可以某種程度上解決特定的問題。下面用常規(guī)的SQL實現(xiàn)上面這個需求。注意:上面表中有一個字段很重要,就是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
查詢結(jié)果同上,略去。這里我們看出,有時候,好的表結(jié)構(gòu)設(shè)計相當(dāng)重要。
關(guān)于SQL Server 2005/2008中的CTE應(yīng)用之遞歸查詢的相關(guān)知識就介紹到這里了,希望本次的介紹能夠?qū)δ兴斋@!
【編輯推薦】
- SQL Server多表查詢優(yōu)化方案總結(jié)
- SQL Server數(shù)據(jù)庫ISNULL函數(shù)的應(yīng)用實例
- SQL Server數(shù)據(jù)庫DATEPART的語法及使用實例
- SQL Server根據(jù)子節(jié)點查詢所有父節(jié)點的代碼示例
- SQL Server臟讀方式數(shù)據(jù)提取之NOLOCK和READPAST