詳解SQL Server 2008中CTE遞歸查詢的實(shí)現(xiàn)
今天基本搞清楚SQL Server中遞歸查詢的實(shí)現(xiàn)方式,So,先記錄下來(lái)。不過(guò)呢,個(gè)人覺(jué)得SQL Server的遞歸查詢相對(duì)于Oracle中的遞歸查詢更加難以理解。
從SQL Server 2005開(kāi)始,我們可以直接通過(guò)CTE來(lái)支持遞歸查詢,這對(duì)查詢樹(shù)形或?qū)哟谓Y(jié)構(gòu)的數(shù)據(jù)很有用。CTE即公用表表達(dá)式,雖然不恰當(dāng),但你可以將它看做成一個(gè)臨時(shí)命名的結(jié)果集合。
我們先建立一個(gè)示例表,名稱為MENU,表示菜單的層次結(jié)構(gòu):
- CREATE TABLE MENU
- (
- name nvarchar(50) NOT NULL PRIMARY KEY,
- senior nvarchar(50) NULL
- );
- INSERT INTO MENU values
- ('文件',NULL),
- ('新建','文件'),
- ('項(xiàng)目','新建'),
- ('使用當(dāng)前連接查詢','新建');
表示的菜單層次如下:
- 文件
- 新建
- 項(xiàng)目
- 使用當(dāng)前連接查詢
- OK,先看CTE的語(yǔ)法:
- WITH CTE名稱(字段列表)
- AS
- (
- 查詢語(yǔ)句
- )
例如:
- WITH lmenu(name,senior)
- as
- (
- SELECT name,senior from menu
- )
我們定義了一個(gè)名稱為lmenu的CTE,這樣我們后續(xù)即可直接使用lmenu來(lái)查詢,如:
- SELECT * FROM lmenu
如果我們?cè)诙xCTE的查詢語(yǔ)句中直接引用CTE表本身,則會(huì)形成遞歸查詢,當(dāng)然遞歸查詢具有自己的特殊結(jié)構(gòu),下面的SQL通過(guò)遞歸查詢獲取每個(gè)菜單的層次深度:
- WITH lmenu(name,senior,level) as
- (
- SELECT NAME,SENIOR,0 level FROM MENU WHERE SENIOR IS NULL
- UNION ALL
- SELECT A.NAME,A.SENIOR,b.level+1 FROM MENU A,lmenu b
- where a.senior = b.name
- )
- SELECT * from lmenu
結(jié)果:
- name senior level
- ----------------------------------- -----------
- 文件 NULL 0
- 新建 文件 1
- 使用當(dāng)前連接查詢 新建 2
- 項(xiàng)目 新建 2
注意查詢定義語(yǔ)句,它由兩條查詢語(yǔ)句構(gòu)成,其中
- SELECT NAME,SENIOR,0 level FROM MENU WHERE SENIOR IS NULL
稱為定位成員,SQL Server通過(guò)此語(yǔ)句來(lái)判斷是否繼續(xù)進(jìn)行遞歸。
- SELECT A.NAME,A.SENIOR,b.level+1 FROM MENU A,lmenu b
- where a.senior = b.name
稱之為遞歸成員,其特征為from子句中引用了CTE對(duì)象自身。
遞歸CTE具有一些限制條件(引自MSDN):
至少有一個(gè)定位點(diǎn)成員和一個(gè)遞歸成員,當(dāng)然,你可以定義多個(gè)定位點(diǎn)成員和遞歸成員,但所有定位點(diǎn)成員必須在遞歸成員的前面
定位點(diǎn)成員之間必須使用UNION ALL、UNION、INTERSECT、EXCEPT集合運(yùn)算符,最后一個(gè)定位點(diǎn)成員與遞歸成員之間必須使用UNION ALL,遞歸成員之間也必須使用UNION ALL連接
定位點(diǎn)成員和遞歸成員中的字段數(shù)量和類型必須完全一致
遞歸成員的FROM子句只能引用一次CTE對(duì)象
遞歸成員中不允許出現(xiàn)下列項(xiàng)
- SELECT DISTINCT
- GROUP BY
- HAVING
- 標(biāo)量聚合
- TOP
- LEFT、RIGHT、OUTER JOIN(允許出現(xiàn) INNER JOIN)
- 子查詢
CTE遞歸查詢的執(zhí)行方式:
遞歸的終止依賴于定位點(diǎn)成員的,如果理解了這一點(diǎn),也就理解了遞歸查詢的執(zhí)行方式。
我們來(lái)看上例的執(zhí)行執(zhí)行過(guò)程:
- SELECT * FROM lmenu
這條語(yǔ)句進(jìn)入遞歸查詢
- SELECT A.NAME,A.SENIOR,b.level+1 FROM MENU A,lmenu b
- where a.senior = b.name
作為最外層的語(yǔ)句,顯然遞歸的第一層應(yīng)該根據(jù)MENU表的記錄來(lái)循環(huán)(如果查詢執(zhí)行計(jì)劃,這表示一個(gè)嵌套循環(huán)),假設(shè)menu表中查詢出的記錄順序如下:
- name senior
- --- --------------------------------------------------
- 文件 NULL
- 新建 文件
- 使用當(dāng)前連接查詢 新建
- 項(xiàng)目 新建
第一條記錄:
首先判斷是否進(jìn)入遞歸,由于 文件包含在定位點(diǎn)成員結(jié)果集中,不符合遞歸條件,所以不進(jìn)入遞歸,直接返回從定位點(diǎn)成員集合中返回記錄:
- select name,senior,0 level from menu where senior is null and name='文件'
- name senior level
- --------------- ------------------------ -----------
- 文件 NULL 0
第二條記錄:
即NAME = '新建', 定位點(diǎn)成員結(jié)果集中沒(méi)有該記錄,將進(jìn)入遞歸:
將當(dāng)前行的值帶入遞歸成員:
- SELECT A.NAME,A.SENIOR,b.level+1 level FROM MENU A,lmenu b
- where a.senior = b.name
- AND a.senior = '文件'
- AND a.name='新建'
由于遞歸的關(guān)聯(lián)條件是a.senior = b.name,所以b.name='文件',以此條件進(jìn)入下級(jí)遞歸,這實(shí)際上就是第一條記錄的情況,由于name='文件'符合定位點(diǎn)條件,所以將終止遞歸,如果我們用子查詢來(lái)替換掉lmenu遞歸成員,第二條記錄的查詢語(yǔ)句實(shí)際為:
- SELECT a.name,a.senior,b.level+1 from menu a, (
- select name,senior,0 level from menu
- where senior is null and name='文件'
- ) b
- where a.senior=b.name
- and a.senior = '文件'
- and a.name='新建'
name senior level
--------------------- --------------------------
新建 文件 1
第三條記錄:
NAME='使用當(dāng)前連接查詢',同樣不符合定位點(diǎn)條件,將進(jìn)入遞歸:
- SELECT A.NAME,A.SENIOR,b.level+1 FROM MENU A,lmenu b
- where a.senior = b.name
- AND a.senior = '新建'
- AND a.name = '使用當(dāng)前連接查詢'
同樣,代入當(dāng)前記錄條件,下級(jí)遞歸b.name='新建',由于'新建'還不符合定位點(diǎn)條件,所以還將繼續(xù)遞歸,及l(fā)menu b表示子查詢:
- select c.name,c.senior,d.level+1 level from menu c,lmenu d
- where c.senior = d.name
- and c.name = '新建'
- and c.senior = '文件'
替換成上述語(yǔ)句后,d.name='文件',將再次判斷是否需要繼續(xù)遞歸,由于'文件'符合終止遞歸條件,所以將終止遞歸。
我們用子查詢表示第三條記錄的遞歸過(guò)程如下:
- SELECT a.name,a.senior,b.level+1 level FROM menu A,(
- select c.name,c.senior,d.level+1 level from menu c,(
- select name,senior,0 level from menu where senior is null and name='文件'
- ) d
- where c.senior = d.name
- and c.name = '新建'
- and c.senior = '文件'
- ) b
- where a.senior = b.name
- and a.senior = '新建'
- and a.name = '使用當(dāng)前連接查詢'
name senior level
--------------------------------------------------------- -----------
使用當(dāng)前連接查詢 新建 2
第四條記錄與第三條記錄的遞歸層次完全一樣。
原文標(biāo)題:SQL Server 2008中的CTE遞歸查詢
鏈接:http://www.cnblogs.com/xfrog/archive/2010/10/10/1847462.html
【編輯推薦】
- SQL Server 2000刪除實(shí)戰(zhàn)演習(xí)
- SQL Server存儲(chǔ)過(guò)程的命名標(biāo)準(zhǔn)如何進(jìn)行?
- 卸載SQL Server 2005組件的正確順序
- 對(duì)SQL Server字符串?dāng)?shù)據(jù)類型的具體描述
- SQL Server數(shù)據(jù)類型的種類與應(yīng)用