(1.1)DML增强功能-CTE

关键字:SQL树查询(参考:树遍历

0.什么是CTE?(WITH common_table_expression)

  指定临时命名的结果集,这些结果集称为公用表表达式 (CTE)。 这派生自简单的查询,并在单个 SELECT、INSERT、UPDATE、DELETE 或 MERGE 语句的执行范围内定义。 

  该子句也可用在 CREATE VIEW 语句中,作为该语句的 SELECT 定义语句的一部分。 公用表表达式可以包括对自身的引用。 这种表达式称为递归公用表表达式。

1.CTE的通用形式

  WITH temp_name as 

  (

    CTE查询结果集

  )

  释义:

    (1)with/as :关键字

    (2)temp_name:为CTE临时使用名称,可以看初学者做是一个临时表

    (3)():查询结果集主体

 

2.CTE的递归查询

 介绍:其实CTE最强大的地方就是在于其递归查询

举例1:使用CTE递归获取某年的1-12月份

   

 --获取2018年的1-12月份(可以用来做外连接和分组)
 ;with date_test as
 (
     select cast('20180101 00:00:00' as datetime) as 'date'
     union all
     select dateadd(mm,1,cast([date] as datetime)) as 'date' from date_test
     where [date]  < cast('20181201' as datetime)
 )
 select date date from date_test
 --select convert(char(7),date,120) date from date_test 获取其年号与月号

  执行结果如图: 

    

举例2:递归快速构建1~100(再大也可以用这个办法快速构建)

with t
as
(select 1 as dt
 union all
 select dt+1 from t
where dt+1<=100
 )
 select dt from t  -- option(maxrecursion 0)
 ;

  结果如图:篇幅太长看下面的行数即可

    

举例3:经典树形结构查询

;WITH t1(id ,area ,pid) AS 
(
    
    select 1,N'河北省',0 union all  
    select 2,N'陕西省',0 union all  
    select 3,N'石家庄市',1 union all  
    select 4,N'桥东区',3 union all  
    select 5,N'唐山市',1 UNION all  
    select 6,N'西安市',2 UNION all  
    select 7,N'雁塔区',6 
) 
,t2 AS 
(
    SELECT *,1 AS level FROM t1 WHERE pid=0
    UNION ALL 
    SELECT t1.*,level+1 FROM t1 JOIN t2 ON t1.pid=t2.id
)
SELECT * FROM t2

结果如图:

  

 

 

举例4:CTE实现instr功能(即指定字符在字符串中出现了几次)

if object_id('tempdb..#T') is not null
  drop table #T
create table #T
(
source_string nvarchar(4000)
)
insert into #T values (N'我们我们')
insert into #T values (N'我我哦我')
declare @sub_string nvarchar(1024)
declare @nth    int
set @sub_string = N'我们'
set @nth = 2
;with T(source_string, starts, pos, nth) 
as (
  select source_string, 1, charindex(@sub_string, source_string), 1 from #t
  union all
  select source_string, pos + 1, charindex(@sub_string, source_string, pos + 1), nth+1 from T
  where pos > 0
)
select
  source_string, pos, nth
from T
where pos <> 0
 and nth = @nth
order by source_string, starts
--source_string  pos  nth
--我们我们  3  2

检索字符改成我,试试。

3.CTE的CUR(R上面1-2已经描述过了,这里不再赘述)

案例数据:

create table test106(id int,name varchar(100),parent int,description varchar(100))
insert into test106 values
(1,'A',0,'总经理'),
(2,'B',1,'开发部经理'),
(3,'C',2,'JAVA小组长'),
(4,'guo',3,'苦逼的开发'),
(5,'li',3,'苦逼的开发'),
(6,'zhao',3,'安静的测试'),
(7,'wang',3,'暴躁的测试'),
(8,'D',2,'JAVA测试小组长')

(1)delete示例

select * from test106
--用CTE筛选出id<=3的数据
;with temp_delete as 
(
    select * from test106 
    where id <=3
)
delete  from temp_delete
select * from test106;

  结果如下:

    

 

(2)update

    与(1)一样没什么区别

    做好with temp_name as ()之后,直接就update语句from temp_name 即可,这里不再演示。

(3)insert

    同(1)、(2),直接insert into select temp_name即可

 

4.CTE的总结与分析

(1)递归CTE的执行过程

  

(2)CTE的注意事项

    【1】在cte使用之前如果有有语句运行,必须加';'分号,如3中的delete示例中一样。否则可以不加。

        

 

    【2】在cte中可以定义多个temp_name,但不可重复,只能后者调用先定义的

               

    【3】CTE只在定以后第一条语句使用,并且可以重复在第一条语句使用其temp_name表

        

    【4】CTE定义好后,使用的时候temp_name如果和现有表同名,则CTE下第一条语句使用的是temp_name表

        

    【5】同一个CTE定义过程中不可有同名的temp_name

        

    【6】CTE中不可嵌套CTE,这个不演示了,有意者可以自己去。

5.CTE层级查询递归最佳实践

【5.1】向上/向下递归

with t as
(select 1 as dt
 union all
 select dt+1 from t
 where dt+1<=100
)
select dt from t  -- option(maxrecursion 0)
 

太长就不一一列举完毕了; 

   

【5.2】树形层级递归(给层级分好层,做出树形界面)

create table testtable(id int,name varchar(20),parentid int)
insert into testtable values(1,'银河系',0)
insert into testtable values(2,'太阳系',1)
insert into testtable values(3,'地球',2)
insert into testtable values(4,'亚洲',3)
insert into testtable values(5,'中国',4)
insert into testtable values(6,'韩国',4)
insert into testtable values(7,'北京',5)
insert into testtable values(8,'首尔',6)

递归代码

 ;WITH CTE AS 
( 
SELECT id,name,parentid ,CAST(space(50)+CAST(Id AS VARCHAR) AS VARCHAR(MAX)) AS sort,1 AS lv
FROM testtable WHERE ParentId = 0
UNION ALL
SELECT t.id,t.name,t.parentid ,CAST(sort+ space(50)+ CAST(t.Id AS VARCHAR) AS VARCHAR(MAX)) AS sort,CTE.lv+1
FROM CTE
INNER JOIN testtable t ON CTE.Id = t.ParentId
)
SELECT id,replace(space(lv),space(1),'·')+name name,parentid,lv FROM CTE ORDER BY sort

  

【5.2】获取父层到现在的值 1-2-3 这类

create table testtable(id int,name varchar(20),parentid int)
insert into testtable values(1,'银河系',0)
insert into testtable values(2,'太阳系',1)
insert into testtable values(3,'地球',2)
insert into testtable values(4,'亚洲',3)
insert into testtable values(5,'中国',4)
insert into testtable values(6,'韩国',4)
insert into testtable values(7,'北京',5)
insert into testtable values(8,'首尔',6)

;with cte as (
 select *,cast(name as varchar(100)) as all_name, cast(id as varchar(100)) as all_id  
 from testtable WHERE ParentId = 0
 union all
 select t1.*
 ,cast(t2.all_name+'-'+t1.name as varchar(100)) as all_name 
 ,cast(t2.all_id+'-'+cast(t1.id as varchar) as varchar(100)) as all_id
 from testtable t1 join cte t2 on t1.parentid=t2.id
)
select * from cte

  

 

 

【5.3】树形层级递归(求出某个节点下的所有子节点)

;WITH t1(id ,pid ,num ) AS 
(
    select 1,0,1 union all
    select 2,1,1 union all
    select 3,2,1 union all
    select 4,2,1 union all
    select 5,2,1 union all
    select 6,3,1 union all
    select 7,3,1
) 
,t2 AS 
(
    SELECT *,id AS sumid FROM t1
    UNION ALL
    SELECT q1.*,q2.sumid FROM t1 q1 JOIN t2 q2 ON q1.pid=q2.id
)
SELECT sumid,count(sumid) AS all_count FROM t2 GROUP BY sumid OPTION(MAXRECURSION 0) 

  

【5.4】利用CTE求累计值

;WITH t1(id ,month ,USED) AS 
(
    select 1,N'一月份',100 union all
    select 2,N'二月份',220 union all
    select 3,N'三月份',310 union all
    select 4,N'四月份',450
) 
,t2 AS 
(
    SELECT *,used AS [已用累计值]  FROM t1 WHERE id=1
    UNION ALL
    SELECT t1.*,t2.[已用累计值]+t1.USED FROM t2 JOIN t1 ON t1.id=t2.id+1 
)
SELECT * FROM t2

  

【5.5】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 的递归引用的提示。

【6】总结:CTE的使用注意事项与限制

1. CTE后面必须直接跟使用CTE的SQL语句(如select、insert、update等),否则,CTE将失效。如下面的SQL语句将无法正常使用CTE

2. CTE后面也可以跟其他的CTE,但只能使用一个with,多个CTE中间用逗号(,)分隔

3. 如果CTE的表达式名称与某个数据表或视图重名,则紧跟在该CTE后面的SQL语句使用的仍然是CTE,当然,后面的SQL语句使用的就是数据表或视图了

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 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾

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 的递归成员中引用了远程服务器,那么将为每个远程表创建一个假脱机,这样就可以在本地反复访问这些表。

参考文档

官网:https://docs.microsoft.com/zh-cn/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15

相关:https://www.jb51.net/article/173764.htm

WITH common_table_expression

posted @ 2018-05-26 18:37  郭大侠1  阅读(435)  评论(0编辑  收藏  举报