1. 程式人生 > >SQL中WITH--AS的用法

SQL中WITH--AS的用法

一.WITH AS的含義

WITH AS短語,也叫做子查詢部分(subquery factoring),可以定義一個SQL片斷,該SQL片斷會被整個SQL語句用到。可以使SQL語句的可讀性更高,也可以在UNION ALL的不同部分,作為提供資料的部分。

對於UNION ALL,使用WITH AS定義了一個UNION ALL語句,當該片斷被呼叫2次以上,優化器會自動將該WITH AS短語所獲取的資料放入一個Temp表中。而提示meterialize則是強制將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。不允許前向引用。

--使用遞迴公用表表達式顯示遞迴的多個級別
WITH DirectReports(ManagerID, EmployeeID, EmployeeLevel) AS 
(
    SELECT ManagerID, EmployeeID, 0 AS EmployeeLevel
    FROM HumanResources.Employee
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.ManagerID, e.EmployeeID, EmployeeLevel + 1
    FROM HumanResources.Employee e
        INNER JOIN DirectReports d
        ON e.ManagerID = d.EmployeeID 
)
SELECT ManagerID, EmployeeID, EmployeeLevel 
FROM DirectReports ;

--使用遞迴公用表表達式顯示遞迴的兩個級別
WITH DirectReports(ManagerID, EmployeeID, EmployeeLevel) AS 
(
    SELECT ManagerID, EmployeeID, 0 AS EmployeeLevel
    FROM HumanResources.Employee
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.ManagerID, e.EmployeeID, EmployeeLevel + 1
    FROM HumanResources.Employee e
        INNER JOIN DirectReports d
        ON e.ManagerID = d.EmployeeID 
)
SELECT ManagerID, EmployeeID, EmployeeLevel 
FROM DirectReports 
WHERE EmployeeLevel <= 2 

--使用遞迴公用表表達式顯示層次列表
WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort)
AS (SELECT CONVERT(varchar(255), c.FirstName + ' ' + c.LastName),
        e.Title,
        e.EmployeeID,
        1,
        CONVERT(varchar(255), c.FirstName + ' ' + c.LastName)
    FROM HumanResources.Employee AS e
    JOIN Person.Contact AS c ON e.ContactID = c.ContactID 
    WHERE e.ManagerID IS NULL
    UNION ALL
    SELECT CONVERT(varchar(255), REPLICATE ('| ' , EmployeeLevel) +
        c.FirstName + ' ' + c.LastName),
        e.Title,
        e.EmployeeID,
        EmployeeLevel + 1,
        CONVERT (varchar(255), RTRIM(Sort) + '| ' + FirstName + ' ' + 
                 LastName)
    FROM HumanResources.Employee as e
    JOIN Person.Contact AS c ON e.ContactID = c.ContactID
    JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
    )
SELECT EmployeeID, Name, Title, EmployeeLevel
FROM DirectReports 
ORDER BY Sort

--使用 MAXRECURSION 取消一條語句
--可以使用 MAXRECURSION 來防止不合理的遞迴 CTE 進入無限迴圈。以下示例特意建立了一個無限迴圈,然後使用 MAXRECURSION 提示將遞迴級別限制為兩級
WITH cte (EmployeeID, ManagerID, Title) as
(
    SELECT EmployeeID, ManagerID, Title
    FROM HumanResources.Employee
    WHERE ManagerID IS NOT NULL
  UNION ALL
    SELECT cte.EmployeeID, cte.ManagerID, cte.Title
    FROM cte 
    JOIN  HumanResources.Employee AS e 
        ON cte.ManagerID = e.EmployeeID
)
--Uses MAXRECURSION to limit the recursive levels to 2
SELECT EmployeeID, ManagerID, Title
FROM cte
OPTION (MAXRECURSION 2)
--在更正程式碼錯誤之後,就不再需要 MAXRECURSION。以下示例顯示了更正後的程式碼
WITH cte (EmployeeID, ManagerID, Title)
AS
(
    SELECT EmployeeID, ManagerID, Title
    FROM HumanResources.Employee
    WHERE ManagerID IS NOT NULL
  UNION ALL
    SELECT  e.EmployeeID, e.ManagerID, e.Title
    FROM HumanResources.Employee AS e
    JOIN cte ON e.ManagerID = cte.EmployeeID
)
SELECT EmployeeID, ManagerID, Title
FROM 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)

注意事項

  • 子查詢可重用相同或者前一個with查詢塊,通過select呼叫(with子句也只能被select呼叫)
  • with子句的查詢輸出儲存到使用者臨時表空間,一次查詢,到處使用
  • 同級select前有多個查詢定義,第一個用with,後面的不用with,並且用逗號分割
  • 最後一個with查詢塊與下面的select呼叫之間不能用逗號分割,只通過右括號分離,with子句的查詢必須括號括起
  • 如果定義了with子句,而在查詢中不使用,則會報ora-32035錯誤,只要後面有引用的即可,不一定在select呼叫,在後with查詢塊引用也是可以的
  • 前面的with子句定義的查詢在後面的with子句中可以使用,但是一個with子句內部不能巢狀with子句
  • with查詢的結果列有別名,引用時候必須使用別名或者*
     

WITH--AS  語句總結:

(1)  SQL可讀性增強。比如對於特定with子查詢取個有意義的名字等。

(2)  with子查詢只執行一次,將結果儲存在使用者臨時表空間中,可以引用多次,增強效能。

舉例:在進行匯入EXCEL的過程中,有時候,需要將資料儲存在臨時表中,當下一次在進行匯入的時候,進行清除臨時表的資料,但是這時候,有時候發生併發問題的話,兩個使用者可能會分別操作對方的資料,所以,可能造成混亂,但是可以使用WITH函式和UNION語句拼接一個SQL語句,儲存在SESSION中,當需要匯出錯誤資訊的時候,可以使用該語句構造資料