【转】with as

with as使用--Sql语句循环访问指定节点的全部子节点

一.WITH AS的含义
   WITH AS 短语,也叫作子查询部分(subquery factoring),可让你作不少事情,定义一个SQL片段,该SQL片段会被整个SQL语句所用到。有 的时候,是为了让SQL语句的可读性更高些,也有多是在UNION ALL的不一样部分,做为提供数据的部分。
特别对于UNION ALL比较有 用。由于UNION ALL的每一个部分可能相同,可是若是每一个部分都去执行一遍的话,则成本过高,因此可使用WITH AS短语,则只要执行一遍便可。 若是WITH AS短语所定义的表名被调用两次以上,则优化器会自动将WITH AS短语所获取的数据放入一个TEMP表里,若是只是被调用一次,则不 会。而提示materialize则是强制将WITH AS短语里的数据放入一个全局临时表里。不少查询经过这种方法均可以提升速度。
二.使用方法
先看下面一个嵌套的查询语句:

select * from person.StateProvince where CountryRegionCode in
        (select CountryRegionCode from person.CountryRegion where Name like 'C%')

   上面的查询语句使用了一个子查询。虽然这条SQL语句并不复杂,但若是嵌套的层次过多,会使SQL语句很是难以阅读和维护。所以,也可使用表变量的方式来解决这个问题,SQL语句以下:

declare @t table(CountryRegionCode nvarchar(3))
insert into @t(CountryRegionCode) (select CountryRegionCode from person.CountryRegion where Name like 'C%')

select * from person.StateProvince where CountryRegionCode
                    in (select * from @t)


   虽 然上面的SQL语句要比第一种方式更复杂,但却将子查询放在了表变量@t中,这样作将使SQL语句更容易维护,但又会带来另外一个问题,就是性能的损失。由 于表变量实际上使用了临时表,从而增长了额外的I/O开销,所以,表变量的方式并不太适合数据量大且频繁查询的状况。为此,在 SQL Server 2005中提供了另一种解决方案,这就是公用表表达式(CTE),使用CTE,可使SQL语句的可维护性,同时,CTE要比表 变量的效率高得多。

   下面是CTE的语法:

[ WITH <common_table_expression> [ ,n ] ]
<common_table_expression>::=
       expression_name [ ( column_name [ ,n ] ) ]
   AS
       ( CTE_query_definition )

   如今使用CTE来解决上面的问题,SQL语句以下:


with
cr as
(
   select CountryRegionCode from person.CountryRegion where Name like 'C%'
)

select * from person.StateProvince where CountryRegionCode in (select * from cr)

   其中cr是一个公用表表达式,该表达式在使用上与表变量相似,只是SQL Server 2005在处理公用表表达式的方式上有所不一样。

   在使用CTE时应注意以下几点:
1. CTE后面必须直接跟使用CTE的SQL语句(如select、insert、update等),不然,CTE将失效。以下面的SQL语句将没法正常使用CTE:


with
cr as
(
   select CountryRegionCode from person.CountryRegion where Name like 'C%'
)
select * from person.CountryRegion -- 应将这条SQL语句去掉
-- 使用CTE的SQL语句应紧跟在相关的CTE后面 --
select * from person.StateProvince where CountryRegionCode in (select * from cr)


2. CTE后面也能够跟其余的CTE,但只能使用一个with,多个CTE中间用逗号(,)分隔,以下面的SQL语句所示:


with
cte1 as
(
   select * from table1 where name like 'abc%'
),
cte2 as
(
   select * from table2 where id > 20
),
cte3 as
(
   select * from table3 where price < 100
)
select a.* from cte1 a, cte2 b, cte3 c where a.id = b.id and a.id = c.id

3. 若是CTE的表达式名称与某个数据表或视图重名,则紧跟在该CTE后面的SQL语句使用的仍然是CTE,固然,后面的SQL语句使用的就是数据表或视图了,以下面的SQL语句所示:


-- table1是一个实际存在的表

with
table1 as
(
   select * from persons where age < 30
)
select * from table1 -- 使用了名为table1的公共表表达式
select * from table1 -- 使用了名为table1的数据表

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

declare @s nvarchar(3)
set @s = 'C%'
; -- 必须加分号
with
t_tree as
(
   select CountryRegionCode from person.CountryRegion where Name like @s
)
select * from person.StateProvince where CountryRegionCode in (select * from t_tree)

   CTE除了能够简化嵌套SQL语句外,还能够进行递归调用,关于这一部分的内容将在下一篇文章中介绍。

先看以下一个数据表(t_tree):

   上 图显示了一个表中的数据,这个表有三个字段:id、node_name、parent_id。实际上,这个表中保存了一个树型结构,分三层:省、市、区。 其中id表示当前省、市或区的id号、node_name表示名称、parent_id表示节点的父节点的id。
   如今有一个需求,要查询出某个省下面的全部市和区(查询结果包含省)。若是只使用SQL语句来实现,须要使用到游标、临时表等技术。但在SQL Server2005中还可使用CTE来实现。

   从这个需求来看属于递归调用,也就是说先查出知足调价的省的记录,在本例子中的要查“辽宁省”的记录,以下:

id  node_name  parent_id

1    辽宁省       0

   而后再查全部parent_id字段值为1的记录,以下:

id  node_name  parent_id

2     沈阳市      1

3     大连市      1

   最后再查parent_id字段值为2或3的记录,以下:

id   node_name   parent_id

4      大东区       2

5      沈河区       2

6      铁西区       2

   将上面三个结果集合并起来就是最终结果集。

   上述的查询过程也能够按递归的过程进行理解,即先查指定的省的记录(辽宁省),获得这条记录后,就有了相应的id值,而后就进入了的递归过程,以下图所示。



   从上面能够看出,递归的过程就是使用union all合并查询结果集的过程,也就是至关于下面的递归公式:

   resultset(n) = resultset(n-1) union all current_resultset

   其 中resultset(n)表示最终的结果集,resultset(n - 1)表示倒数第二个结果集,current_resultset表示当前查出 来的结果集,而最开始查询出“辽宁省”的记录集至关于递归的初始条件。而递归的结束条件是current_resultset为空。下面是这个递归过程的 伪代码:


public resultset getResultSet(resultset)
{
   if(resultset is null)
    {
        current_resultset =第一个结果集(包含省的记录集)
        将结果集的id保存在集合中
        getResultSet(current_resultset)
    }
    current_resultset = 根据id集合中的id值查出当前结果集
   if(current_result is null) return resultset
    将当前结果集的id保存在集合中
   return  getResultSet(resultset union all current_resultset)
}

// 得到最终结果集
resultset = getResultSet(null)


   从上面的过程能够看出,这一递归过程实现起来比较复杂,然而CTE为咱们提供了简单的语法来简化这一过程。
   实现递归的CTE语法以下:



[ WITH <common_table_expression> [ ,n ] ]
<common_table_expression>::=
        expression_name [ ( column_name [ ,n ] ) ]
   AS (
       CTE_query_definition1 --  定位点成员(也就是初始值或第一个结果集)
      union all
       CTE_query_definition2 --  递归成员
    )

   



with
district as
(
   --  得到第一个结果集,并更新最终结果集
   select * from t_tree where node_name= N'辽宁省'
   union all
   --  下面的select语句首先会根据从上一个查询结果集中得到的id值来查询parent_id        
   --  字段的值,而后district就会变当前的查询结果集,并继续执行下面的select 语句
   --  若是结果集不为null,则与最终的查询结果合并,同时用合并的结果更新最终的查
   --  询结果;不然中止执行。最后district的结果集就是最终结果集。
   select a.* from t_tree a, district b
              where a.parent_id = b.id
)
select * from district






with
district as
(
   select * from t_tree where node_name= N'辽宁省'
   union all
   select a.* from t_tree a, district b
              where a.parent_id = b.id
),
district1 as
(
   select a.* from district a where a.id in (select parent_id from district)   
)
select * from district1


  



   注:只有“辽宁省”和“沈阳市”有下子节点。

   在定义和使用递归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 的递归引用的提示。

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

 

递归实例:express

CREATE TABLE Dept(
   id int PRIMARY KEY,
   parent_id int,
   name nvarchar(20))
INSERT Dept
SELECT 0, 0, N'<所有>' UNION ALL
SELECT 1, 0, N'财务部' UNION ALL
SELECT 2, 0, N'行政部' UNION ALL
SELECT 3, 0, N'业务部' UNION ALL
SELECT 4, 0, N'业务部' UNION ALL
SELECT 5, 4, N'销售部' UNION ALL
SELECT 6, 4, N'MIS' UNION ALL
SELECT 7, 6, N'UI' UNION ALL
SELECT 8, 6, N'软件开发' UNION ALL
SELECT 9, 8, N'内部开发'
GO服务器


-- 查询指定部门下面的全部部门
DECLARE @Dept_name nvarchar(20)
SET @Dept_name = N'MIS'
;WITH
DEPTS AS(
   -- 定位点成员
   SELECT * FROM Dept
   WHERE name = @Dept_name
   UNION ALL
   -- 递归成员, 经过引用CTE自身与Dept基表JOIN实现递归
   SELECT A.*
   FROM Dept A, DEPTS B
   WHERE A.parent_id = B.id
)
SELECT * FROM DEPTS
GO性能

-- 删除演示环境
--DROP TABLE Dept优化

相关文章
相关标签/搜索