Sql學習第三天——SQL 關於使用CTE(公用表表達式)的遞歸查詢


關於使用CTE(公用表表達式)的遞歸查詢----SQL Server 2005及以上版本

  公用表表達式 (CTE) 具有一個重要的優點,那就是能夠引用其自身,從而創建遞歸 CTE。遞歸 CTE 是一個重復執行初始 CTE 以返回數據子集直到獲取完整結果集的公用表表達式。

  當某個查詢引用遞歸 CTE 時,它即被稱為遞歸查詢。遞歸查詢通常用於返回分層數據,例如:顯示某個組織圖中的雇員或物料清單方案(其中父級產品有一個或多個組件,而那些組件可能還有子組件,或者是其他父級產品的組件)中的數據。

  遞歸 CTE 可以極大地簡化在 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 語句中運行遞歸查詢所需的代碼。在 SQL Server 的早期版本中,遞歸查詢通常需要使用臨時表、游標和邏輯來控制遞歸步驟流。 

CTE 的基本語法結構如下:

    WITH expression_name [ ( column_name [,...n] ) ]

    AS

    ( CTE_query_definition )

    --只有在查詢定義中為所有結果列都提供了不同的名稱時,列名稱列表才是可選的。

    --運行 CTE 的語句為:

    SELECT <column_list> FROM expression_name;

在使用CTE時應注意如下幾點:

CTE后面必須直接跟使用CTE的SQL語句(如select、insert、update等),否則,CTE將失效。如下面的SQL語句將無法正常使用CTE:

 

  with
  cr as
  (
      select * from 表名 where 條件
   )
  --select * from person.CountryRegion  --如果加上這句話后面用到cr將報錯 
  select * from cr

2. CTE后面也可以跟其他的CTE,但只能使用一個with,多個CTE中間用逗號(,)分隔,如下面的SQL語句所示:

with
cte1 as
(
    select * from table1 where name like '測試%'
),
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語句使用的就是數據表或視圖。

4. CTE 可以引用自身,也可以引用在同一 WITH 子句中預先定義的 CTE。

5. 不能在 CTE_query_definition 中使用以下子句:

  •  COMPUTE 或 COMPUTE BY
  •  ORDER BY(除非指定了 TOP 子句)
  •  INTO
  •  帶有查詢提示的 OPTION 子句
  •  FOR XML
  •  FOR BROWSE

6. 如果將 CTE 用在屬於批處理的一部分的語句中,那么在它之前的語句必須以分號結尾,如下面的SQL所示:

declare @s nvarchar(3)
set @s = '測試%';  -- 必須加分號
with
t_tree as
(
    select * fromwhere 字段 like @s
)
select * from  t_tree

------------------------------------操作------------------------------------

上面可能對with as說的有點兒啰嗦了,下面進入正題:

老規矩先建表(Co_ItemNameSet):

CREATE TABLE [dbo].[Co_ItemNameSet](
    [ItemId] [int] NULL,
    [ParentItemId] [int] NULL,
    [ItemName] [nchar](10) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]

插入數據:

--給表插入數據
 insert into dbo.Co_ItemNameSet values(2,0,'管理費用')
 insert into dbo.Co_ItemNameSet values(3,0,'銷售費用')
 insert into dbo.Co_ItemNameSet values(4,0,'財務費用')
 insert into dbo.Co_ItemNameSet values(5,0,'生產成本')
 insert into dbo.Co_ItemNameSet values(35,5,'材料')
 insert into dbo.Co_ItemNameSet values(36,5,'人工')
 insert into dbo.Co_ItemNameSet values(37,5,'制造費用')
 insert into dbo.Co_ItemNameSet values(38,35,'原材料')
 insert into dbo.Co_ItemNameSet values(39,35,'主要材料')
 insert into dbo.Co_ItemNameSet values(40,35,'間輔材料')
 insert into dbo.Co_ItemNameSet values(41,36,'工資')
 insert into dbo.Co_ItemNameSet values(42,36,'福利')
 insert into dbo.Co_ItemNameSet values(43,2,'管理費用子項')
 insert into dbo.Co_ItemNameSet values(113,43,'管理費用子項的子項')

查詢插入的數據:

--查詢數據
 select * from Co_ItemNameSet

結果圖:

題目需求是:查詢ItemId=2及子節點,也就是管理費用和其下屬所有節點的信息

操作1:先看看不用CTE遞歸操作的sql語句如下(需要真是的建兩個表進行數據的存放和判斷,非常麻煩):

 declare @i int                        
 select @i=2;

 create table #tem(                    
 [ItemId] [INT] NOT NULL,
 [level] INT
 );

 create table #list(                
 [ItemId] [INT] NOT NULL,
 [ParentItemId] [INT] NOT NULL default ((0)),
 [ItemName] [nvarchar](100) NOT NULL default (''),
 [level] int
 );

 insert INTO #tem([ItemId],[level])    
    select ItemId,1
    from Co_ItemNameSet
    where itemid=@i

 insert into #list([ItemId],[ParentItemId],[ItemName],[level])
    select ItemId,ParentItemId,ItemName,1
    from Co_ItemNameSet
    where itemid=@i

declare @level int
select @level=1
declare @current INT
select @current=0

 while(@level>0)
 begin
 select @current=ItemId
 from #tem
 where [level]=@level
 if @@ROWCOUNT>0
 begin


 delete from #tem
 where [level]=@level and ItemId=@current

 insert into #tem([ItemId],[level])
 select [ItemId],@level+1
 from Co_ItemNameSet
 where ParentItemId=@current

 insert into #list([ItemId],[ParentItemId],[ItemName],[level])
    select [ItemId],[ParentItemId],[ItemName],@level+1
    from Co_ItemNameSet
    where ParentItemId=@current
    if @@rowcount>0
    begin
    select @level=@level+1
    end
 end
 else
 begin
 select @level=@level-1
 end
 end

 select * from #list
 drop table #tem
 drop table #list

結果圖:

操作2:用CTE遞歸操作的sql語句如下:

DECLARE @i INT
SELECT @i=2;
WITH Co_ItemNameSet_CTE(ItemId,ParentItemId,ItemName,[Level])
AS
(
    SELECT ItemId,ParentItemId,ItemName,1 AS [Level]
    FROM Co_ItemNameSet
    WHERE itemid=@i
    UNION ALL
    SELECT c.ItemId,c.ParentItemId,c.ItemName,[Level] + 1
    FROM Co_ItemNameSet c INNER JOIN Co_ItemNameSet_CTE ct
    ON c.ParentItemId=ct.ItemId
)
SELECT * FROM Co_ItemNameSet_CTE

結果圖:

-----------------------------分析(查看MSDN的分析)----------------------------

主要分析一下用CTE的遞歸操作:

遞歸 CTE 由下列三個元素組成:

  1. 例程的調用。

    遞歸 CTE 的第一個調用包括一個或多個由 UNION ALL、UNION、EXCEPT 或 INTERSECT 運算符聯接的 CTE_query_definitions。由於這些查詢定義形成了 CTE 結構的基准結果集,所以它們被稱為“定位點成員”。

    CTE_query_definitions 被視為定位點成員,除非它們引用了 CTE 本身。所有定位點成員查詢定義必須放置在第一個遞歸成員定義之前,而且必須使用 UNION ALL 運算符聯接最后一個定位點成員和第一個遞歸成員。

  2. 例程的遞歸調用。

    遞歸調用包括一個或多個由引用 CTE 本身的 UNION ALL 運算符聯接的 CTE_query_definitions(就是as里的語句塊)。這些查詢定義被稱為“遞歸成員”。

  3. 終止檢查。

    終止檢查是隱式的;當上一個調用中未返回行時,遞歸將停止。

遞歸 CTE 結構必須至少包含一個定位點成員一個遞歸成員。以下偽代碼顯示了包含一個定位點成員和一個遞歸成員的簡單遞歸 CTE 的組件。

WITH cte_name ( column_name [,...n] )
AS
(
CTE_query_definition --定位點成員
UNION ALL
CTE_query_definition --遞歸成員.

現在讓我們看一下遞歸執行過程:

  1. 將 CTE 表達式拆分為定位點成員和遞歸成員。

  2. 運行定位點成員,創建第一個調用或基准結果集 (T0)。

  3. 運行遞歸成員,將 Ti 作為輸入,將 Ti+1 作為輸出。

  4. 重復步驟 3,直到返回空集。

  5. 返回結果集。這是對 T0 到 Tn 執行 UNION ALL 的結果。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM