【轉】with as

with as使用--Sql語句循環訪問指定節點的全部子節點

一.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語句外,還能夠進行遞歸調用,關於這一部分的內容將在下一篇文章中介紹。

先看以下一個數據表(t_tree):

   上 圖顯示了一個表中的數據,這個表有三個字段:id、node_name、parent_id。實際上,這個表中保存了一個樹型結構,分三層:省、市、區。 其中id表示當前省、市或區的id號、node_name表示名稱、parent_id表示節點的父節點的id。
   如今有一個需求,要查詢出某個省下面的全部市和區(查詢結果包含省)。若是隻使用SQL語句來實現,須要使用到遊標、臨時表等技術。但在SQL Server2005中還可使用CTE來實現。

   從這個需求來看屬於遞歸調用,也就是說先查出知足調價的省的記錄,在本例子中的要查「遼寧省」的記錄,以下:

id  node_name  parent_id

1    遼寧省       0

   而後再查全部parent_id字段值爲1的記錄,以下:

id  node_name  parent_id

2     瀋陽市      1

3     大連市      1

   最後再查parent_id字段值爲2或3的記錄,以下:

id   node_name   parent_id

4      大東區       2

5      瀋河區       2

6      鐵西區       2

   將上面三個結果集合並起來就是最終結果集。

   上述的查詢過程也能夠按遞歸的過程進行理解,即先查指定的省的記錄(遼寧省),獲得這條記錄後,就有了相應的id值,而後就進入了的遞歸過程,以下圖所示。



   從上面能夠看出,遞歸的過程就是使用union all合併查詢結果集的過程,也就是至關於下面的遞歸公式:

   resultset(n) = resultset(n-1) union all current_resultset

   其 中resultset(n)表示最終的結果集,resultset(n - 1)表示倒數第二個結果集,current_resultset表示當前查出 來的結果集,而最開始查詢出「遼寧省」的記錄集至關於遞歸的初始條件。而遞歸的結束條件是current_resultset爲空。下面是這個遞歸過程的 僞代碼:


public resultset getResultSet(resultset)
{
   if(resultset is null)
    {
        current_resultset =第一個結果集(包含省的記錄集)
        將結果集的id保存在集合中
        getResultSet(current_resultset)
    }
    current_resultset = 根據id集合中的id值查出當前結果集
   if(current_result is null) return resultset
    將當前結果集的id保存在集合中
   return  getResultSet(resultset union all current_resultset)
}

// 得到最終結果集
resultset = getResultSet(null)


   從上面的過程能夠看出,這一遞歸過程實現起來比較複雜,然而CTE爲咱們提供了簡單的語法來簡化這一過程。
   實現遞歸的CTE語法以下:



[ WITH <common_table_expression> [ ,n ] ]
<common_table_expression>::=
        expression_name [ ( column_name [ ,n ] ) ]
   AS (
       CTE_query_definition1 --  定位點成員(也就是初始值或第一個結果集)
      union all
       CTE_query_definition2 --  遞歸成員
    )

   



with
district as
(
   --  得到第一個結果集,並更新最終結果集
   select * from t_tree where node_name= N'遼寧省'
   union all
   --  下面的select語句首先會根據從上一個查詢結果集中得到的id值來查詢parent_id        
   --  字段的值,而後district就會變當前的查詢結果集,並繼續執行下面的select 語句
   --  若是結果集不爲null,則與最終的查詢結果合併,同時用合併的結果更新最終的查
   --  詢結果;不然中止執行。最後district的結果集就是最終結果集。
   select a.* from t_tree a, district b
              where a.parent_id = b.id
)
select * from district






with
district as
(
   select * from t_tree where node_name= N'遼寧省'
   union all
   select a.* from t_tree a, district b
              where a.parent_id = b.id
),
district1 as
(
   select a.* from district a where a.id in (select parent_id from district)   
)
select * from district1


  



   注:只有「遼寧省」和「瀋陽市」有下子節點。

   在定義和使用遞歸CTE時應注意以下幾點:

1. 遞歸 CTE 定義至少必須包含兩個 CTE 查詢定義,一個定位點成員和一個遞歸成員。能夠定義多個定位點成員和遞歸成員;但必須將全部定位點成員查詢定義置於第一個遞歸成員定義以前。全部 CTE 查詢定義都是定位點成員,但它們引用 CTE 自己時除外。
2. 定位點成員必須與如下集合運算符之一結合使用:UNION ALL、UNION、INTERSECT 或 EXCEPT。在最後一個定位點成員和第一個遞歸成員之間,以及組合多個遞歸成員時,只能使用 UNION ALL 集合運算符。
3. 定位點成員和遞歸成員中的列數必須一致。
4. 遞歸成員中列的數據類型必須與定位點成員中相應列的數據類型一致。
5. 遞歸成員的 FROM 子句只能引用一次 CTE expression_name。
6. 在遞歸成員的 CTE_query_definition 中不容許出現下列項:

(1)SELECT DISTINCT

(2)GROUP BY

(3)HAVING

(4)標量聚合

(5)TOP

(6)LEFT、RIGHT、OUTER JOIN(容許出現 INNER JOIN)

(7)子查詢

(8)應用於對 CTE_query_definition 中的 CTE 的遞歸引用的提示。

7. 不管參與的 SELECT 語句返回的列的爲空性如何,遞歸 CTE 返回的所有列均可覺得空。
8. 如 果遞歸 CTE 組合不正確,可能會致使無限循環。例如,若是遞歸成員查詢定義對父列和子列返回相同的值,則會形成無限循環。可使 用 MAXRECURSION 提示以及在 INSERT、UPDATE、DELETE 或 SELECT 語句的 OPTION 子句中的一 個 0 到 32,767 之間的值,來限制特定語句所容許的遞歸級數,以防止出現無限循環。這樣就可以在解決產生循環的代碼問題以前控制語句的執行。服 務器範圍內的默認值是 100。若是指定 0,則沒有限制。每個語句只能指定一個 MAXRECURSION 值。
9. 不能使用包含遞歸公用表表達式的視圖來更新數據。
10. 可使用 CTE 在查詢上定義遊標。遞歸 CTE 只容許使用快速只進遊標和靜態(快照)遊標。若是在遞歸 CTE 中指定了其餘遊標類型,則該類型將轉換爲靜態遊標類型。
11. 能夠在 CTE 中引用遠程服務器中的表。若是在 CTE 的遞歸成員中引用了遠程服務器,那麼將爲每一個遠程表建立一個假脫機,這樣就能夠在本地反覆訪問這些表。node

 

遞歸實例:express

CREATE TABLE Dept(
   id int PRIMARY KEY,
   parent_id int,
   name nvarchar(20))
INSERT Dept
SELECT 0, 0, N'<所有>' UNION ALL
SELECT 1, 0, N'財務部' UNION ALL
SELECT 2, 0, N'行政部' UNION ALL
SELECT 3, 0, N'業務部' UNION ALL
SELECT 4, 0, N'業務部' UNION ALL
SELECT 5, 4, N'銷售部' UNION ALL
SELECT 6, 4, N'MIS' UNION ALL
SELECT 7, 6, N'UI' UNION ALL
SELECT 8, 6, N'軟件開發' UNION ALL
SELECT 9, 8, N'內部開發'
GO服務器


-- 查詢指定部門下面的全部部門
DECLARE @Dept_name nvarchar(20)
SET @Dept_name = N'MIS'
;WITH
DEPTS AS(
   -- 定位點成員
   SELECT * FROM Dept
   WHERE name = @Dept_name
   UNION ALL
   -- 遞歸成員, 經過引用CTE自身與Dept基表JOIN實現遞歸
   SELECT A.*
   FROM Dept A, DEPTS B
   WHERE A.parent_id = B.id
)
SELECT * FROM DEPTS
GO性能

-- 刪除演示環境
--DROP TABLE Dept優化

相關文章
相關標籤/搜索