With As 子句,可應用於遞歸

一.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 的遞歸成員中引用了遠程服務器,那麼將爲每個遠程表創建一個假脫機,這樣就可以在本地反覆訪問這些表。

 

遞歸實例:

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

 

 

一.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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章