with as短语,也叫做子查询部分(subquery factoring),可以定义一个sql片断,该sql片断会被整个sql语句用到。可以使sql语句的可读性更高,也可以在union all的不同部分,作为提供数据的部分。
对于union all,使用with as定义了一个union all语句,当该片断被调用2次以上,优化器会自动将该with as短语所获取的数据放入一个temp表中。而提示meterialize则是强制将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。不允许前向引用。
--使用递归公用表表达式显示递归的多个级别 with directreports(managerid, employeeid, employeelevel) as ( select managerid, employeeid, 0 as employeelevel from humanresources.employee where managerid is null union all select e.managerid, e.employeeid, employeelevel + 1 from humanresources.employee e inner join directreports d on e.managerid = d.employeeid ) select managerid, employeeid, employeelevel from directreports ; --使用递归公用表表达式显示递归的两个级别 with directreports(managerid, employeeid, employeelevel) as ( select managerid, employeeid, 0 as employeelevel from humanresources.employee where managerid is null union all select e.managerid, e.employeeid, employeelevel + 1 from humanresources.employee e inner join directreports d on e.managerid = d.employeeid ) select managerid, employeeid, employeelevel from directreports where employeelevel <= 2 --使用递归公用表表达式显示层次列表 with directreports(name, title, employeeid, employeelevel, sort) as (select convert(varchar(255), c.firstname + ' ' + c.lastname), e.title, e.employeeid, 1, convert(varchar(255), c.firstname + ' ' + c.lastname) from humanresources.employee as e join person.contact as c on e.contactid = c.contactid where e.managerid is null union all select convert(varchar(255), replicate ('| ' , employeelevel) + c.firstname + ' ' + c.lastname), e.title, e.employeeid, employeelevel + 1, convert (varchar(255), rtrim(sort) + '| ' + firstname + ' ' + lastname) from humanresources.employee as e join person.contact as c on e.contactid = c.contactid join directreports as d on e.managerid = d.employeeid ) select employeeid, name, title, employeelevel from directreports order by sort --使用 maxrecursion 取消一条语句 --可以使用 maxrecursion 来防止不合理的递归 cte 进入无限循环。以下示例特意创建了一个无限循环,然后使用 maxrecursion 提示将递归级别限制为两级 with cte (employeeid, managerid, title) as ( select employeeid, managerid, title from humanresources.employee where managerid is not null union all select cte.employeeid, cte.managerid, cte.title from cte join humanresources.employee as e on cte.managerid = e.employeeid ) --uses maxrecursion to limit the recursive levels to 2 select employeeid, managerid, title from cte option (maxrecursion 2) --在更正代码错误之后,就不再需要 maxrecursion。以下示例显示了更正后的代码 with cte (employeeid, managerid, title) as ( select employeeid, managerid, title from humanresources.employee where managerid is not null union all select e.employeeid, e.managerid, e.title from humanresources.employee as e join cte on e.managerid = cte.employeeid ) select employeeid, managerid, title from 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)