shanzm-2020年5月15日 23:07:32

0. 背景说明

在编写存储过程的时候,大多数情况都会使用公用表表达式,本文对公用表表达式(CTE)作简单的介绍

①准备工作

创建一个产品表T_Product

CREATE TABLE [dbo].[T_Product]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,--自增字段Id
[Category] [nvarchar](50) NOT NULL,--产品种类
[Name] [nvarchar](50) NOT NULL,--产品名
[Price] [decimal](18, 0) NOT NULL,--产品价格
)

随便插入一些数据即可

②子表达式

了解公用表表达式之前我们要回顾一下SQL语句中的子查询

之前在我们在select语句中可以使用子查询,

例如:

select * from (select  * from  T_Product where Category='clothes' )T where T.Price>500`

这里就是使用了一个子查询:查询出T_Product表中的所有的种类为clothes的产品,子查询的结果表定义别名为T,我们在T表中继续查询所有Price大于500的产品

③表变量

子查询虽然不复杂,但是使用多层的子查询嵌套,则SQL语句会变得难以阅读,

当然,我们可以使用表变量,即:定义一个表变量,然后将子查询的结果集存入表变量中,以供后续的使用。

例如:

--声明一个表变量
declare @T table
(
Id bigint not null ,
Category nvarchar(50) not null,
Name nvarchar(50) not nul ,
Prince decimal not null
)
--将子查询的数据插入表变量中
insert into @T select * from T_Product where Category='clothes' --使用表变量
select * from @T where Price>500

④公用表表达式

将子查询放在了表变量@T中,这样做将使SQL语句更容易维护,但又会带来另一个问题,就是性能的损失。

由于表变量实际上使用了临时表,从而增加了额外的I/O开销,因此,表变量的方式并不太适合数据量大且频繁查询的情况。

为此,在SQL Server 2005中提供了另外一种解决方案,这就是公用表表达式(CTE),使用CTE,可以使SQL语句的可维护性,同时,CTE要比表变量的效率高得多。

这里就先演示一下使用CTE,具体的语法细节后面做说明

--声明一个CTE
with T as
(
select * from T_Product where Category='clothes'
) --使用CTE
select * from T where Price >500

这里我们就是使用公用表表达式替换这里的子查询的结果集。

所以使用使用with&as定义的公用表表达式也称为子查询部分(subquery factoring)

同时,公用表表达式在存储过程中可以用于代替视图的使用,在某个存储过程中需要使用是个结果集,我们没必要为其创建一个视图,可以使用公用表表达式。



1. 定义及语法细节

1.1 基本定义

定义:公用表表达式(Common Table Expression) 是SQL Server2005版本的引入的一个特性。CTE可以看组是一个临时的结果集,可以再接下来来的一个select,insert,update,delete,merge语句中多次引用。使用公用表达式CTE可以让语句更加清晰简练。

简而言之:公用表表达式就是用于临时存储结果集。

1.2 基本语法

创建一个公用表表达式,并使用改公用表表达式的基本语法:

with  subquery_name(column1,column2,column3……)--定义一个CTE
as
(
select column1,column2,column3 from table_name
)
select * from subquery_name-- 引用CTE

注意事项:

  • 公用表表达式只能使用在其定义后的第一个sql语句中,否则会报错

  • 定义CTE的时候,可以省略列名,即最终的该CTE的列名就是该CTE中select查询的结果,但是我现在觉得尽量不要省略,可以方便后续阅读。

正确示例:

with temp as
(
select * from T_Product where Category='外套'
)
select * from temp as temp1 left join temp as temp2 on temp1.Id =temp2.Id

上面是声明了一个公用表表达式temp,在其之后的第一句sql语句中使用到temp,完全没有问题。

错误示例:

with temp as
(
select * from T_Product where Category='外套'
)
select * from temp --公用表表达式temp之后的第一句sql语句
select * from temp as temp1 left join temp as temp2 on temp1.Id =temp2.Id--公用表表达式temp之后的第二句sql语句

结果是报错:显示第一句sql语句已执行,但是第二句sql语句报错“对象名'temp'无效”

正是因为只有CTE之后的第一句sql语句可以使用该CTE,所以如果CTE的表达式名称与某个数据表或视图重名,则紧跟在该CTE后面的SQL语句使用的仍然是CTE

CTE语法细节:

  1. 如果将 CTE 用在属于批处理的一部分的语句中,那么在CTE之前的语句必须以分号结尾。

    【补充】:批处理是指从应用程序一次性地发送一组完整sql语句到sql server上执行,批处理的所有语句被当做一个整体,被成批地分析,编译和执行,所有的批处理 指令以GO 作为结束标志。同时写多个批处理,如果前面所有的批处理没有问题,最后一个有错误那么前面所有的批处理都不会执行

  2. CTE中的sql语句是不能使用order by语句,除非select语句中有top(其实这里我也没有想明白,理论上子查询使用order by完全是没有问题的)

  3. 前面的with子句定义的查询在后面的with子句中可以使用。但是一个with子句内部不能嵌套with子句

1.3 多个CTE同时声明

这里就要思考一个问题了,那就是若是在语句sql语句中需要多个公用表表达式,我们可以连续的声明多个公用表表达式,但是注意只需要在第一个公用表表达式上使用with,之后相连的则不需在使用with

with temp1 as
(
select * from T_Product where Category='外套'
)
,temp2 as--注意这里不在需要使用with ,但是不要忘记as
(
select * from T_Product where Category='裤子'
)
select * from temp1 ,temp2 where temp1.Price=temp2.Price

1.4 CTE嵌套使用

一次声明的多个公用表表达式,后面的公用表表达式可以使用前面的公用表表达式

例如:

with temp1 as
(
select * from T_Product where Category='外套'
)
,temp2 as
(
select * from temp1 where Price>200--在相连的第二个CTE中使用第一个CTE
)
select * from temp2



2. CTE递归查询

2.1 简介

递归查询主要用于层次结构的查询,从叶级(Leaf Level)向顶层(Root Level)查询,或从顶层向叶级查询,或递归的路径(Path)。

递归 CTE 定义至少必须包含两个 CTE 查询定义,一个定位点成员和一个递归成员。可以定义多个定位点成员和递归成员;但必须将所有定位点成员查询定义置于第一个递归成员定义之前。

第一个子查询称作定点(Anchor)子查询:定点查询只是一个返回有效表的查询,用于设置递归的初始值;

第二个子查询称作递归子查询:该子查询调用CTE名称,触发递归查询,实际上是递归子查询调用递归子查询;

两个子查询使用union all,求并集;

2.2 准备工作

创建一个公司表,公司中的部门是分等级的,PId即该部门的上一级部门

CREATE TABLE [dbo].[Company]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[PId] [bigint] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
)

插入数据,我们使用行政等级模拟上下级部门:

Id        PId       Name
--------- --------- ----------
1 0 中国
2 1 江苏省
3 2 苏州市
4 3 吴中区
5 1 山东省
6 5 济南市
7 5 青岛市
8 5 烟台市
9 2 南京市
11 9 玄武区

2.3 计算每个部门的等级序列

USE [ShanTest]
GO with temp as
(
select *,0 as Level from Company where Pid =0
union all
select c.Id,c.Pid,c.Name,temp.Level+1 as Level
from Company as c,temp where temp.Id=c.Pid
)
select * from temp

运行测试结果:

Id        PId       Name           Level
--------- --------- ------------ ------------
1 0 中国 0
2 1 江苏省 1
5 1 山东省 1
6 5 济南市 2
7 5 青岛市 2
8 5 烟台市 2
3 2 苏州市 2
9 2 南京市 2
11 9 玄武区 3
4 3 吴中区 3

简单的理一理这里的递归:

首先:

select *,0 as Level from Company where Pid =0

结果是:

Id        PId       Name           Level
--------- --------- ------------ ------------
1 0 中国 0

接着:

union all
select c.Id,c.Pid,c.Name,temp.Level+1 as Level from Company as c,temp where c.Pid=temp .Id

第一次递归结果:

Id        PId       Name           Level
--------- --------- ------------ ------------
1 0 中国 0
2 1 江苏省 1
5 1 山东省 1

第二次递归结果:

Id        PId       Name           Level
--------- --------- ------------ ------------
1 0 中国 0
2 1 江苏省 1
5 1 山东省 1
6 5 济南市 2
7 5 青岛市 2
8 5 烟台市 2
3 2 苏州市 2
9 2 南京市 2

第三次递归结果:

Id        PId       Name           Level
--------- --------- ------------ ------------
1 0 中国 0
2 1 江苏省 1
5 1 山东省 1
6 5 济南市 2
7 5 青岛市 2
8 5 烟台市 2
3 2 苏州市 2
9 2 南京市 2
11 9 玄武区 3
4 3 吴中区 3

【注意】:

  • 递归CTE可能会出现无限递归。从而大量消耗SQL Server的服务器资源.因此,SQL Server提供了OPTION选项,可以设定最大的递归次数。

    这个最大递归次数往往是根据数据所代表的具体业务相关的,比如这里,我们定义最大递归数是2:option(maxrecursion 2)

  • 递归查询没有显式的递归终止条件,只有当递归子查询返回空结果集(没有数据行返回)或是超出了递归次数的最大限制时,才停止递归。

USE [ShanTest]
GO with temp as
(
select *,0 as Level from Company where Pid =0
union all
select c.Id,c.Pid,c.Name,temp.Level+1 as Level
from Company as c,temp where c.Pid=temp .Id
)
select * from temp
option(maxrecursion 2)--设置最大递归数是2

则结果现实如下,即最大递归两次则只能查询到市一级,无法查询到区一级

Id        PId       Name           Level
--------- --------- ------------ ------------
1 0 中国 0
2 1 江苏省 1
5 1 山东省 1
6 5 济南市 2
7 5 青岛市 2
8 5 烟台市 2
3 2 苏州市 2
9 2 南京市 2 消息 530,级别 16,状态 1,第 6 行
语句被终止。完成执行语句前已用完最大递归 2。

2.4 查询所有的子级与父级匹配结果

作为层级结构,可以使用自连接查询每个部门的上级部门:

--隐式内连接
select a.Id ,a.Pid ,a.Name ,b.Name as PName
from Company a ,Company b where a.Pid=b.Id --显式内连接:
select a.Id ,a.Pid ,a.Name ,b.Name as PName
from Company a inner join Company b on a.Pid =b.Id

查询结果:

Id       Pid      Name         PName
-------- -------- --------- ----------
2 1 江苏省 中国
3 2 苏州市 江苏省
4 3 吴中区 苏州市
5 1 山东省 中国
6 5 济南市 山东省
7 5 青岛市 山东省
8 5 烟台市 山东省
9 2 南京市 江苏省
11 9 玄武区 南京市

下面演示使用递归CTE实现,所有的子级匹配所有的父级

with subq as
(
select Id ,Pid ,Name ,Name as PName
from Company where Pid =0
union all
select c.Id ,c.Pid,c.Name ,s.Name as PName
from subq as s inner join Company as c on s.Id =c.Pid
--from subq as s,Company as c where s.Id=c.Pid
)
select * from subq
Id       Pid      Name          PName
-------- -------- ---------- ---------
1 0 中国 中国
2 1 江苏省 中国
5 1 山东省 中国
6 5 济南市 山东省
7 5 青岛市 山东省
8 5 烟台市 山东省
3 2 苏州市 江苏省
9 2 南京市 江苏省
11 9 玄武区 南京市
4 3 吴中区 苏州市

理解递归的方式就是,从头理一理:

首先:

select  Id ,Pid ,Name  ,Name as PName from Company  where Pid =0

结果是:

Id       Pid      Name          PName
-------- -------- ---------- ---------
1 0 中国 中国

接着

select  c.Id ,c.Pid,c.Name  ,s.Name as PName  from subq as s inner join  Company as c on s.Id =c.Pid

第一次递归结果:

Id       Pid      Name          PName
-------- -------- ---------- ---------
1 0 中国 中国
2 1 江苏省 中国
5 1 山东省 中国

第二次递归:

Id       Pid      Name          PName
-------- -------- ---------- ---------
1 0 中国 中国
2 1 江苏省 中国
5 1 山东省 中国
6 5 济南市 山东省
7 5 青岛市 山东省
8 5 烟台市 山东省
3 2 苏州市 江苏省
9 2 南京市 江苏省

第三次递归

Id       Pid      Name          PName
-------- -------- ---------- ---------
1 0 中国 中国
2 1 江苏省 中国
5 1 山东省 中国
6 5 济南市 山东省
7 5 青岛市 山东省
8 5 烟台市 山东省
3 2 苏州市 江苏省
9 2 南京市 江苏省
11 9 玄武区 南京市
4 3 吴中区 苏州市

2.5 父查子-查询某个部门的下级部门

比如说,这里查询表中所有江苏省以下的行政区域

with temp as
(
select * from Company where Id=2--江苏省的Id是2,所以递归初始值就是2
union all
select c.*
from temp ,szmCompany as c where temp.Id =c.Pid
) select * from temp
--option(maxrecursion 1)

查询结果:

Id     Pid    Name
------ ------ ----------
2 1 江苏省
3 2 苏州市
9 2 南京市
10 9 玄武区
4 3 吴中区

其实这里,若是我们只需要江苏省的下一级(即:市级),而不需要下下级(即:区县级)

则可以设置递归的次数为1即可:option(maxrecursion 1)

结果为:

Id     Pid    Name
------ ------ ----------
2 1 江苏省
3 2 苏州市
9 2 南京市
消息 530,级别 16,状态 1,第 1 行
语句被终止。完成执行语句前已用完最大递归 1。

2.6 子查父-查询某个下级部门的上级部门

通过子部门查询其父部门,比如查询吴中区的上级行政区域

with temp as
(
select * from Company where Id=4--吴中区Id
union all
select c.*
from temp ,Company as c where temp.Pid =c.Id
) select * from temp
--option(maxrecursion 1)

查询结果:

Id    Pid   Name
----- ----- --------
4 3 吴中区
3 2 苏州市
2 1 江苏省
1 0 中国

若是只需要查询吴中区的直系上级行政区域,则只要限制最大递归次数为1即可

当然,若是只需要查直系上级,我们可以使用之前的上下级匹配的结果集,筛选特定的记录:

select * from
(
select a.* ,b.Name as PName from Company as a ,Company as b where a.Pid=b.Id--所有的上下级匹配结果集
)X
where X.Id =4--吴中区Id

查询结果:

Id     Pid     Name       PName
------ ------- --------- ----------
4 3 吴中区 苏州市



3. 参考

【待读】

想要找一本关于存储过程的书籍,一直没有找到,所以都是在网上的一些博文中学习相关的技巧和语法细节

感觉不系统,隐隐约约感觉自己关于T-SQL以及存储过程的使用还有许多不了解的地方!

存储过程——公用表表达式(CTE)的更多相关文章

  1. 详解公用表表达式(CTE)

    简介 对于SELECT查询语句来说,通常情况下,为了使T-SQL代码更加简洁和可读,在一个查询中引用另外的结果集都是通过视图而不是子查询来进行分解的.但是,视图是作为系统对象存在数据库中,那对于结果集 ...

  2. T-SQL查询进阶--详解公用表表达式(CTE)

    简介 对于SELECT查询语句来说,通常情况下,为了使T-SQL代码更加简洁和可读,在一个查询中引用另外的结果集都是通过视图而不是子查询来进行分解的. 但是,视图是作为系统对象存在数据库中,那对于结果 ...

  3. SQL Server中公用表表达式 CTE 递归的生成帮助数据,以及递归的典型应用

    本文出处:http://www.cnblogs.com/wy123/p/5960825.html 我们在做开发的时候,有时候会需要一些帮助数据,必须需要连续的数字,连续间隔的时间点,连续的季度日期等等 ...

  4. 公用表表达式CTE

    公用表表达式CTE表面上和派生表非常相似,看起来只是语义上的区别.但和派生表比较起来,CTE具有几个优势:第一,如果须要在一个CTE中引用另一个CTE,不需要像派生表那样嵌套,相反,只要简单地在同一个 ...

  5. T-SQL 公用表表达式(CTE)

    公用表表达式(CTE) 在编写T-SQL代码时,往往需要临时存储某些结果集.前面我们已经广泛使用和介绍了两种临时存储结果集的方法:临时表和表变量.除此之外,还可以使用公用表表达式的方法.公用表表达式( ...

  6. 公用表表达式 (CTE)、递归、所有子节点、sqlserver

    指定临时命名的结果集,这些结果集称为公用表表达式 (CTE).公用表表达式可以包括对自身的引用.这种表达式称为递归公用表表达式. 对于递归公用表达式来说,实现原理也是相同的,同样需要在语句中定义两部分 ...

  7. SQL Server进阶(六)表表达式--派生表、公用表表达式(CTE)、视图和内联表值函数

    概述 表表达式是一种命名的查询表达式,代表一个有效地关系表.可以像其他表一样,在数据处理中使用表表达式. SQL Server支持四种类型的表表达式:派生表,公用表表达式,视图和内联表值函数. 为什么 ...

  8. 公用表表达式(CTE)

    在编写T-SQL代码时,往往需要临时存储某些结果集.前面我们已经广泛使用和介绍了两种临时存储结果集的方法:临时表和表变量.除此之外,还可以使用公用表表达式的方法.公用表表达式(Common Table ...

  9. SQL Server 公用表表达式(CTE)实现递归

    公用表表达式简介: 公用表表达式 (CTE) 可以认为是在单个 SELECT.INSERT.UPDATE.DELETE 或 CREATE VIEW 语句的执行范围内定义的临时结果集.CTE 与派生表类 ...

随机推荐

  1. Linux常见提权

    常见的linux提权 内核漏洞提权 查看发行版 cat /etc/issue cat /etc/*-release 查看内核版本 uname -a 查看已经安装的程序 dpkg -l rpm -qa ...

  2. 编码理解的漫漫长路(Unicode、GBK、ISO)

    Ø 那么现在开始康康都有哪些编码方式  1.  ASCII

  3. Joomla 3.4.6 RCE 分析

    Joomla 3.4.6 RCE 漏洞分析,首发先知社区: https://xz.aliyun.com/t/6522 漏洞环境及利用 Joomla 3.4.6 : https://downloads. ...

  4. docker(1)

    什么是Docker? Docker 最初是dotCloud公司创始人Solomon Hykes在法国期间发起的一个公司内部项目,它是基于dotCloud公司多年云服务技术的一次革新. Docker使用 ...

  5. 如何使用Markdown 编写文档

    Markdown 是一种轻量级标记语言,用来编写文本文档,一般后缀名为.md.该语言在 2004 由约翰·格鲁伯(John Gruber)创建. 由于Markdown 语法简单,易读易写,变得越来越通 ...

  6. Solidity的Bytecode和Opcode简介

    Solidity的Bytecode和Opcode简介 随着我们更深入地编写智能合约,我们将遇到诸如" PUSH1"," SSTORE"," CALLV ...

  7. 汇编 之 win10 下安装dosbox 和 MASM

    所需工具链接: 链接:https://pan.baidu.com/s/1nenMsSdgEkeRKc6wh9DQRA 提取码:1r89 只需要以下两个工具 安装dosbox 和MASM步骤 (1)解压 ...

  8. Apache Storm 官方文档 —— Storm 与 Kestrel

    本文说明了如何使用 Storm 从 Kestrel 集群中消费数据. 前言 Storm 本教程中使用了 storm-kestrel 项目和 storm-starter 项目中的例子.建议读者将这几个项 ...

  9. mybatis if test标签的使用

    2019独角兽企业重金招聘Python工程师标准>>> 在使用mybatis 有时候需要进行判断的. 而我们知道mybatis获取值有两种方式 #{}和${}的. 那么,在mybat ...

  10. Ubuntu+FastDFS+Nginx

    一.安装libfastcommon 1.wget https://github.com/happyfish100/libfastcommon/archive/V1.0.7.tar.gz 2.tar - ...