CTE(共用表达式)
共用表达式(common table expression) 是sqlserver 2005版本后引入的一个新特性,CET 可以看做是一个
临时的结果集,可以在接下来的select insert update merge语句中北多次引用。
使用它具有如下的好处:
1.使用公用表达式可以让语句更加清晰简练
2.可以定义递归公用表表达式(CTE)
3. 当不需要将结果集作为视图被多个地方引用时,CTE可以使其更加简洁
GROUP BY语句可以直接作用于子查询所得的标量列
4.可以在一个语句中多次引用公用表表达式(CTE)
基本语法
WITH expression_name [ ( column_name [,...n] ) ] AS ( CTE_query_definition )
测试数据如下:
CREATE TABLE CITYINFO( ID INT NOT NULL PRIMARY KEY IDENTITY(1,1), NAME VARCHAR(100), PID INT ) GO INSERT INTO CITYINFO VALUES('四川',0) INSERT INTO CITYINFO VALUES('成都',1) INSERT INTO CITYINFO VALUES('眉山',1) INSERT INTO CITYINFO VALUES('乐山',1) GO INSERT INTO CITYINFO VALUES('重庆',0) INSERT INTO CITYINFO VALUES('合川',5) INSERT INTO CITYINFO VALUES('长寿',5) INSERT INTO CITYINFO VALUES('江津',5)
非递归共用表达式
查询结果仅仅一次性返回结果集用于外部查询调用,并不在定义语句中
调用其自身的CET
WITH CTE_TEST AS ( SELECT * FROM CITYINFO WHERE ID<5 ) SELECT * FROM CTE_TEST GO
你可以将CET 理解成“局部变量”,仅限于当前语句中使用;
WITH CTE_TEST AS ( SELECT * FROM CITYINFO WHERE ID<5 ) SELECT * FROM CTE_TEST GO --去掉go 也会报错 SELECT * FROM CTE_TEST --会报错 GO
(4 行受影响)
消息 208,级别 16,状态 1,第 1 行
对象名 'CTE_TEST' 无效。
可以被多次引用
--查询四川下面的城市 WITH CTE_TEST AS ( SELECT * FROM CITYINFO WHERE ID<5 ) SELECT * FROM CTE_TEST WHERE PID=( --第一次 SELECT ID FROM CTE_TEST WHERE NAME='四川') --第二次
也可以同时定义多个(一个with语句定义多个CTE)
WITH CTE_A AS ( SELECT * FROM CITYINFO WHERE ID<5 ), --用逗号隔开 CTE_B AS ( SELECT * FROM CITYINFO WHERE ID>=5 ) SELECT * FROM CTE_A UNION ALL SELECT * FROM CTE_B
递归公用表表达式(CTE)
递归公用表表达式很像派生表(Derived Tables ),指的是在CTE内的语句中调用其自身的CTE.与派生表不同的是,CTE可以在一次定义多次进行派生递归.对于递归的概念,是指一个函数或是过程直接或者间接的调用其自身(接下来才是他的亮点啦)
例:我们要查询父节点下的所有子几节点:
我们先向表中加入两条数据
INSERT INTO CITYINFO VALUES('尚义',3) INSERT INTO CITYINFO VALUES('悦心',3)
现在结果集是这样的
传统方法:(如果,看不懂,或不想费时,费脑,就跳过吧)
SELECT * FROM CITYINFO WHERE PID=( SELECT ID FROM CITYINFO WHERE NAME='四川') --这样 我们只能获取城市 --ps 那么城市下面的乡镇呢? 那还得用游标,还得用到中间表和变量表 DECLARE CUR CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR SELECT ID,NAME,PID FROM CITYINFO WHERE PID=( SELECT ID FROM CITYINFO WHERE NAME='四川') --这一步 就获取到了城市 OPEN CUR --打开游标 DECLARE @ID INT,@PID INT ,@NAME VARCHAR(100) FETCH NEXT FROM CUR INTO @ID,@NAME,@PID --申明一个临时表来存储中间结果; CREATE TABLE #RESULT( SELFID INT , NAME VARCHAR(100), PID INT ) INSERT #RESULT(SELFID,NAME,PID) VALUES(@ID,@NAME,@PID) --保存中间结果集合 WHILE (@@FETCH_STATUS=0 and @NAME<>'over') BEGIN FETCH NEXT FROM CUR INTO @ID,@NAME,@PID INSERT #RESULT(SELFID,NAME,PID) VALUES(@ID,@NAME,@PID) --保存中间结果集合 IF EXISTS(SELECT * FROM CITYINFO WHERE PID=@ID) BEGIN INSERT INTO #RESULT(SELFID,NAME,PID) SELECT ID,NAME,PID FROM CITYINFO WHERE PID=@ID --城市下的乡镇; END --每次都要清空对象,防止最后一次循环的重复; SELECT @ID=0,@NAME='over',@PID=0 END SELECT * FROM #RESULT CLOSE CUR DEALLOCATE CUR GO
传统方法二:
当然,我们也可以使用多表连接的方法,把它找出来
SELECT * FROM ( SELECT A.ID ,A.NAME,A.PID FROM CITYINFO AS A JOIN (SELECT ID FROM CITYINFO WHERE NAME='四川')AS T ON A.PID=T.ID) AS X LEFT JOIN( SELECT * FROM CITYINFO WHERE PID<>0 ) AS Y ON Y.PID=X.ID
ps:传统方法,虽然很笨重,代码有多,逻辑又复杂,但是可以当练习sql来学习,你可以试试
CTE方法:
上面的仅仅是三级的级联嵌套,就如此麻烦了,如果还有四级,五级呢.....,擦........还有CTE
WITH CTE AS ( SELECT * FROM CITYINFO WHERE NAME='四川' UNION ALL SELECT A.ID,A.NAME,A.PID FROM CITYINFO AS A JOIN CTE AS B ON A.PID=B.ID ) SELECT * FROM CTE
结果:
这么复杂的查询通过递归CTE变得如此优雅和简洁.这也是CTE最强大的地方. 当然,越强大的力量,就需要被约束.如果使用不当的话,递归CTE可能会出现无限递归。从而大量消耗SQL Server的服务器资源.因此,SQL Server提供了OPTION选项,可以设定最大的递归次数:
SELECT * FROM CTE option(MAXRECURSION 2)
ps:如果你不想看到“四川”的结果 在其中;你就:
SELECT * FROM CTE where PID<>0 option(MAXRECURSION 2)
总结:
如果只是想获得某一个父级下的所有子节点,那么cet 是很不错的选择!
其实,在公司业务一般不会存在无限的级联,我们可以选择 传统方法二,将结果组合成一个级联的效果;
例如: