TSql CTE 递归原理探究
TSQL脚本能实现递归查询,用户使用共用表表达式 CTE(Common Table Expression),只需要编写少量的代码,就能实现递归查询。本文详细介绍CTE递归调用的特性和使用示例,递归查询主要用于层次结构的查询,从叶级(Leaf Level)向顶层(Root Level)查询,或从顶层向叶级查询,或递归的路径(Path)。
一,递归查询原理
递归调用是指自己调用自己,使用CTE实现递归查询必须满足三个条件:初始条件,递归调用表达式,终止条件,CTE 递归查询的伪代码如下:
WITH cte_name ( column_name [,...n] )
AS
(
--Anchor member is defined
CTE_query_definition
UNION ALL
--Recursive member is defined referencing cte_name
CTE_query_definition
)
-- Statement using the CTE
SELECT *
FROM cte_name
1,递归查询至少包含两个子查询:
- 第一个子查询称作定点(Anchor)子查询:定点查询只是一个返回有效表的查询,用于设置递归的初始值;
- 第二个子查询称作递归子查询:该子查询调用CTE名称,触发递归查询,实际上是递归子查询调用递归子查询;
- 两个子查询使用union all,求并集;
2,CTE的递归终止条件
递归查询没有显式的递归终止条件,只有当递归子查询返回空结果集(没有数据行返回)或是超出了递归次数的最大限制时,才停止递归。
默认的递归查询次数是100,可以使用查询提示(hint):MAXRECURSION 控制递归的最大次数:OPTION( MAXRECURSION 16);如果允许无限制的递归次数,使用查询提示:option(maxrecursion 0);当递归查询达到指定或默认的 MAXRECURSION 数量限制时,SQL Server将结束查询并返回错误,如下:
The statement terminated. The maximum recursion 10 has been exhausted before statement completion.
事务执行失败,该事务包含的所有操作都被回滚。在产品环境中,慎用maxrecursion 查询提示,推荐通过 where 条件限制递归的次数。
3,递归步骤
step1:定点子查询设置CTE的初始值,即CTE的初始值Set0;
递归调用的子查询过程:递归子查询调用递归子查询;
step2:递归子查询第一次调用CTE名称,CTE名称是指CTE的初始值Set0,第一次执行递归子查询之后,CTE名称是指结果集Set1;
step3:递归子查询第二次调用CTE名称,CTE名称是指Set1,第二次执行递归子查询之后,CTE名称是指结果集Set2;
step4:在第N次执行递归子查询时,CTE名称是指Set(N-1),递归子查询都引用前一个递归子查询的结果集;
Step5:如果递归子查询返回空数据行,或超出递归次数的最大限制,停止递归;
二,递归查询示例(员工职称)
1,创建测试数据
ManagerID是UserID的父节点,这是一个非常简单的层次结构模型。
use tempdb
go create table dbo.dt_user
(
UserID int,
ManagerID int,
Name Nvarchar(10)
) insert into dbo.dt_user
select 1,-1,N'Boss'
union all
select 11,1,N'A1'
union all
select 12,1,N'A2'
union all
select 13,1,N'A3'
union all
select 111,11,N'B1'
union all
select 112,11,N'B2'
union all
select 121,12,N'C1'
2,查询每个User的的直接上级Manager
;with cte as
(
select UserID,ManagerID,name,name as ManagerName
from dbo.dt_user
where ManagerID=-1 union all
select c.UserID,c.ManagerID,c.Name,p.name as ManagerName
from cte P
inner join dbo.dt_user c
on p.UserID=c.ManagerID
)
select UserID,ManagerID,Name,ManagerName
from cte
order by UserID
step1:查询ManagerID=-1,作为root node,这是递归查询的起始点。
step2:迭代公式是 union all 下面的查询语句。在查询语句中调用中cte,而查询语句就是cte的组成部分,即 “自己调用自己”,这就是递归的真谛所在。
所谓迭代,是指每一次递归都要调用上一次查询的结果集,Union ALL是指每次都把结果集并在一起。
step3-N,迭代公式利用上一次查询返回的结果集执行特定的查询,直到CTE返回null 或达到最大的迭代次数,默认值是32。最终的结果集是迭代公式返回的各个结果集的并集,求并集是由Union All 子句定义的,并且只能使用Union ALL。
3,查询路径,在层次结构中查询子节点到父节点的path
;with cte as
(
select UserID,ManagerID,name,cast(name as nvarchar(max)) as ReportPath
from dbo.dt_user
where ManagerID=-1 union all
select c.UserID,c.ManagerID,c.Name,c.name+'->'+p.ReportPath as ReportPath
from cte P
inner join dbo.dt_user c
on p.UserID=c.ManagerID
)
select UserID,ManagerID,Name,ReportPath
from cte
order by UserID
查询结果如下截图:
三,递归查询示例(行政区划)
1,需求模拟
在TSQL中实现层次结构,例如有这样一种数据结构,省,市,县,乡,村,如何使用一张表表示这种数据结构,并且允许是不对称的,例如,上海市是个直辖市,没有省份。
create table dbo.hierarchy
(
ID int not null primary key,
--type int not null,
ParentID int not null,
name varchar(100) not null
)
type表示类型,可以设置:省,Type是1;市,type是2,以此类推。
ParentID标识的是父级ID,例如信阳市的ParentID是河南省的ID。
2,插入测试数据
测试数据格式说明了归属关系,博主懒,去掉type字段。
insert into dbo.hierarchy
values(1,0,'河南省')
,(2,1,'信阳市'),(3,2,'淮滨县'),(4,3,'芦集乡'),(12,3,'邓湾乡'),(13,3,'台头乡'),(14,3,'谷堆乡')
,(8,2,'固始县'),(9,8,'李店乡')
,(10,2,'息县'),(11,10,'关店乡')
,(5,1,'安阳市'),(6,5,'滑县'),(7,6,'老庙乡')
,(15,1,'南阳市'),(16,15,'方城县')
,(17,1,'驻马店市'),(18,17,'正阳县') select *
from dbo.hierarchy
order by ParentID
3,实现由父级向子级的查询
由于实际的数据可能有很多,所以,要想获取河南省下的所有市,县,乡,村等信息,必须使用递归查询
;with cte(Id,ParentID,Name) as
(
select *
from dbo.hierarchy
where id=1 union all
select h.*
from dbo.hierarchy h
inner join cte c on h.ParentID=c.id
--where c.id!=h.ID
)
select *
from cte
order by ParentID
如果要查看向内递归到多少level,可以使用派生列,level=0是省level,level=1是市level,依次类推。
;with cte(Id,ParentID,Name,Level) as
(
select ID,ParentID,Name,0 as Level
from dbo.hierarchy
where id=1 union all
select h.ID,h.ParentID,h.Name,c.Level+1 as Level
from dbo.hierarchy h
inner join cte c on h.ParentID=c.id
--where c.id!=h.ID
)
select *
from cte
order by ParentID
查询结果如图:
4,由子级向父级的递归查询
;with cte as
(
select ID,ParentID,name
from dbo.hierarchy
where id=4 --芦集乡的ID union all
select h.ID,h.ParentID,h.name
from dbo.hierarchy h
inner join cte c on h.id=c.ParentID
)
select ID,ParentID,name
from cte
order by ParentID
查询结果如图:
参考文档:
Recursive Queries Using Common Table Expressions
WITH common_table_expression (Transact-SQL)
TSql CTE 递归原理探究的更多相关文章
- [原] KVM 虚拟化原理探究(3)— CPU 虚拟化
KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...
- [原] KVM 虚拟化原理探究(1)— overview
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- [原] KVM 虚拟化原理探究 —— 目录
KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...
- [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化
KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...
- [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化
KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...
- [原] KVM 虚拟化原理探究(4)— 内存虚拟化
KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...
- [原] KVM 虚拟化原理探究(2)— QEMU启动过程
KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...
- SQL Server中公用表表达式 CTE 递归的生成帮助数据,以及递归的典型应用
本文出处:http://www.cnblogs.com/wy123/p/5960825.html 我们在做开发的时候,有时候会需要一些帮助数据,必须需要连续的数字,连续间隔的时间点,连续的季度日期等等 ...
- 弱类型变量原理探究(转载 http://www.csdn.net/article/2014-09-15/2821685-exploring-of-the-php)
N首页> 云计算 [问底]王帅:深入PHP内核(一)——弱类型变量原理探究 发表于2014-09-19 09:00| 13055次阅读| 来源CSDN| 36 条评论| 作者王帅 问底PHP王帅 ...
随机推荐
- GIT FLOW 时序图
git flow sequence md link: git branching model master->master branch: use default branch Note rig ...
- new的原罪
一直以为在开发阶段能够直接调用的,速度而言一定是最优秀的,因为总比后期通过反射之类来调用来得快吧. 下面请看一个SB的例子,重新编译以后,这个类在创建100,000,000实体时居然耗费了16秒的时间 ...
- Hive使用技巧
hive默认查询不会显示列名, 当一个表字段比较多的时候,往往看不出值与列之间的对应关系,对日常查错及定位问题带来不便,像下面这样. hive> >select * from exampl ...
- angular下拉
<div class="form-group col-sm-4"> <label class="col-sm-5 control-label" ...
- 网络流EK
#include <iostream> #include <queue> #include <string.h> #define MAX 302 using nam ...
- 页面加载完成后加载多个函数的js完美解决方案
function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') ...
- 查询Oracle锁表和解决方法
Oracle数据库操作中,我们有时会用到锁表查询以及解锁和kill进程等操作,那么这些操作是怎么实现的呢?本文我们主要就介绍一下这部分内容.(1)锁表查询的代码有以下的形式:select count( ...
- CentOS7 服务器 JDK+TOMCAT+MYSQL+redis 安装日志
防火墙配置(参考 CentOS7安装iptables防火墙) 检查是否安装iptables #先检查是否安装了iptables service iptables status #安装iptables ...
- (转)使用Node.js+Socket.IO搭建WebSocket实时应用
Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. W ...
- android wireshark抓包和fiddler抓包
一 wireshark 1.把电脑的网络做为热点 2.开启wifi热点后,被测手机连接到该热点: 3.启动wireshark,选择做为热点的网卡,点击start开始抓包: 4.操作手机,可以抓取到手机 ...