在编写存储过程的时候,大多数状况都会使用公用表表达式,本文对公用表表达式(CTE)做简单的介绍html
①准备工做
建立一个产品表T_Productsql
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的产品.net
③表变量code
子查询虽然不复杂,可是使用多层的子查询嵌套,则SQL语句会变得难以阅读,server
固然,咱们可使用表变量,即:定义一个表变量,而后将子查询的结果集存入表变量中,以供后续的使用。
例如:
--声明一个表变量 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)
同时,公用表表达式在存储过程当中能够用于代替视图的使用,在某个存储过程当中须要使用是个结果集,咱们不必为其建立一个视图,可使用公用表表达式。
定义:公用表表达式(Common Table Expression) 是SQL Server2005版本的引入的一个特性。CTE能够看组是一个临时的结果集,能够再接下来来的一个select
,insert
,update
,delete
,merge
语句中屡次引用。使用公用表达式CTE可让语句更加清晰简练。
简而言之:公用表表达式就是用于临时存储结果集。
建立一个公用表表达式,并使用改公用表表达式的基本语法:
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语法细节:
若是将 CTE 用在属于批处理的一部分的语句中,那么在CTE以前的语句必须以分号结尾。
【补充】:批处理是指从应用程序一次性地发送一组完整sql语句到sql server上执行,批处理的全部语句被当作一个总体,被成批地分析,编译和执行,全部的批处理 指令以GO
做为结束标志。同时写多个批处理,若是前面全部的批处理没有问题,最后一个有错误那么前面全部的批处理都不会执行
CTE中的sql语句是不能使用order by
语句,除非select语句中有top
(其实这里我也没有想明白,理论上子查询使用order by
彻底是没有问题的)
前面的with子句定义的查询在后面的with子句中可使用。可是一个with子句内部不能嵌套with子句
这里就要思考一个问题了,那就是如果在语句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
一次声明的多个公用表表达式,后面的公用表表达式可使用前面的公用表表达式
例如:
with temp1 as ( select * from T_Product where Category='外套' ) ,temp2 as ( select * from temp1 where Price>200--在相连的第二个CTE中使用第一个CTE ) select * from temp2
递归查询主要用于层次结构的查询,从叶级(Leaf Level)向顶层(Root Level)查询,或从顶层向叶级查询,或递归的路径(Path)。
递归 CTE 定义至少必须包含两个 CTE 查询定义,一个定位点成员和一个递归成员。能够定义多个定位点成员和递归成员;但必须将全部定位点成员查询定义置于第一个递归成员定义以前。
第一个子查询称做定点(Anchor)子查询:定点查询只是一个返回有效表的查询,用于设置递归的初始值;
第二个子查询称做递归子查询:该子查询调用CTE名称,触发递归查询,其实是递归子查询调用递归子查询;
两个子查询使用union all,求并集;
建立一个公司表,公司中的部门是分等级的,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 玄武区
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。
做为层级结构,可使用自链接查询每一个部门的上级部门:
--隐式内链接 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 吴中区 苏州市
好比说,这里查询表中全部江苏省如下的行政区域
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。
经过子部门查询其父部门,好比查询吴中区的上级行政区域
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 吴中区 苏州市
【待读】:
想要找一本关于存储过程的书籍,一直没有找到,因此都是在网上的一些博文中学习相关的技巧和语法细节
感受不系统,隐隐约约感受本身关于T-SQL以及存储过程的使用还有许多不了解的地方!