公用表表达式(CTE)

在编写T-SQL代码时,往往需要 临时存储某些结果集,下面给出三种方法:


3种方法比较

(一)、临时表:

需要在临时数据库TempDB中通过I/O操作来创建表结构,一旦用户退出SQL Server环境则自动被删除。

优点:能够长久存储数据,可以建立索引,和普通的物理表一样,能存储大量数据
缺点:不方便使用,使用完之后要手动的drop,不然就会一直存在(此次连接关闭后就没了)

  • # 表示本地临时表,仅在当前会话中可见;
  • ## 表示全局临时表,在所有会话中都可见;
--方法一:
SELECT 
into #filterList  
from dbo.table
--方法二:
CREATE table #tegeb ( nm int , amount decimal, sta varchar ) 

insert into #tegeb 
SELECT nm, amount,sta
FROM dbo.order 
with(NOLOCK)
-- 处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAST 
-- with(NOLOCK) 可能把没有提交事务的数据也显示出来 
-- with(READPAST) 会把被锁住的行不显示出来 

SELECT * FROM #tegeb --临时表一直存在,直到链接关闭
Truncate table #tegeb --清空数据
DROP TABLE dbo.#tegeb --必须手动drop

(二)、表变量:

在内存中以表结构的形式存在,其定义与变量一致,其使用与表类似,不需要产生I/O。

DECLARE @temp table (  nm int , amount decimal, sta varchar ) 

insert into @temp 
SELECT nm, amount,sta  
FROM dbo.order

SELECT * FROM @temp    

--单独运行最后一句,@temp已经不存在了

(三)、公用表表达式:Common Table Expression:

定义在内存中保存的临时存储结果集对象,不产生I/O,不需要按照表变量这样定义,使用方法和表类似。可以自己引用,也可以再查询中被多次引用。

  • WITH AS-做子查询部分(subquery factoring)。
  • 如果WITH AS所以定的表名被调用两次以上,则优化器会自动将WITH AS所获取的数据放入临时表里,如果只是被调用一次,则不会。
  • WITH AS可以被紧跟着的一条SQL语句所使用多次,但不能被紧跟着的多条SQL语句使用。
  • 如果将 CTE 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾

CTE的定义语法如下,主要包括3个部分:

  • Expression_name:CTE表达式的名称。
  • Column_name:列名列表。
  • CTE_query_definition:定义CTE结果集的Select查询语句
WITH expression_name [(column_name [,...n] )]
AS
( 
         cte_query_definition 
)

根据微软对CTE好处的描述,可以归结为四点:

  • 可以定义递归公用表表达式(CTE)
  • 当不需要将结果集作为视图被多个地方引用时,CTE可以使其更加简洁
  • GROUP BY语句可以直接作用于子查询所得的标量列
  • 可以在一个语句中多次引用公用表表达式(CTE)

可以将公用表(CTE)表达式分为 递归 公用表表达式和 非递归 公用表表达式

非递归公用表表达式(CTE):
  • 仅仅一次性返回一个结果集用于外部查询调用
  • 并不在其定义的语句中调用其自身的CTE

非递归公用表表达式(CTE)的使用方式和视图以及子查询一致。

WITH CTE_Test
AS
(
    SELECT * FROM Person_1
)
SELECT * FROM CTE_Test
可以在接下来一条语句中多次引用
WITH CTE_Test
AS
(
    SELECT * FROM Person_1
)

SELECT * 
    FROM CTE_Test AS a                            --第一次引用
    INNER JOIN  CTE_Test AS b ON a.Id = b.Id      --第二次引用
如果多条语句引用,会报错
WITH CTE_Test
AS
(
    SELECT * FROM Person_1
)
SELECT * FROM CTE_Test 
SELECT * FROM CTE_Test    --这条语句会报错
当需要接下来的一条语句中引用多个CTE时,可以定义多个,中间用逗号分隔
WITH CTE_Test1
AS
(
SELECT * FROM Person_1
),
CTE_Test2
AS
(
SELECT * FROM Person_2
)

SELECT * FROM CTE_Test1
UNION
SELECT * FROM CTE_Test2
递归公用表表达式(CTE):

对于递归公用表达式来说,只需要在语句中定义两部分:

  • 基本语句(定位点成员)
  • 递归语句(递归成员)

递归 CTE 定义至少必须包含两个 CTE 查询定义,一个定位点成员和一个递归成员。 可以定义多个定位点成员和递归成员;但必须将所有定位点成员查询定义置于第一个递归成员定义之前。 所有 CTE 查询定义都是定位点成员,但它们引用 CTE 本身时除外。

•定位点成员必须与以下集合运算符之一结合使用:UNION ALL、UNION、INTERSECT 或 EXCEPT。 在最后一个定位点成员和第一个递归成员之间,以及组合多个递归成员时,只能使用 UNION ALL 集合运算符。

先建一张表栏目表如下,栏目Id,栏目名称,栏目的父栏目。



现在使用CTE查询其每个栏目是第几层栏目的代码如下:

WITH COL_CTE(Id,Name,ParentId,tLevel )
AS
(
    --基本语句
    SELECT Id,Name,ParentId,0 AS tLevel 
    FROM Col
    WHERE ParentId = 0
    UNION ALL
    --递归语句
    SELECT c.Id,c.Name,c.ParentId,ce.tLevel+1 AS tLevel 
    FROM COL as c
        INNER JOIN COL_CTE AS ce ON c.ParentId = ce.Id --递归调用
    )

SELECT * FROM COL_CTE

结果:


0表示顶级栏目,1就是1级栏目,语法非常优雅,就一个SELECT * FRON COL_CTE。
但是,这要有约束,否则如果无限制递归可以会消耗掉非常多的系统资源。下面来看看如何 限制递归的最大次数

WITH COL_CTE(Id,Name,ParentId,tLevel )
AS
(
    --基本语句
    SELECT Id,Name,ParentId,0 AS tLevel 
    FROM Col
    WHERE ParentId = 0
    UNION ALL
    --递归语句
    SELECT c.Id,c.Name,c.ParentId,ce.tLevel+1 AS tLevel 
    FROM COL as c
        INNER JOIN COL_CTE AS ce ON c.ParentId = ce.Id --递归调用
    )

SELECT * FROM COL_CTE
OPTION(MAXRECURSION 2)  --指定最大递归次数为2

【参考资料】
WITH common_table_expression (Transact-SQL)
T-SQL 公用表表达式(CTE)

你可能感兴趣的:(公用表表达式(CTE))