表表达式是一种命名的查询表达式,表明一个有效的关系表。SQLServer支持4种类型的表表达式:
派生表(derived table)、公用表表达式(CTE,common table expression)、视图,以及内联表值函数(inline TVF,inline table-valued function)sql
表表达式并非物理上真实存在的什么对象,它是虚拟的。对于表表达式的查询在数据库引擎内部都将转换为对底层对象的查询。使用表表达式的好处一般体如今代码的逻辑方面,而不是性能方面。数据库
派生表是在外部查询的from子句中定义的。派生表的存在范围为定义它的外部查询,只要外部查询一结束,派生表也就不存在了。express
use TSQLFundamentals2008; select * from (select custid, companyname from Sales.Customers where country=N'USA') as USACusts;
要有效地定义任何类型的表表查询,查询语句必须知足三个要求:
一、不保证有必定的顺序
二、全部的列必须有名称
三、全部的列名必须是惟一的编程
-- 分配列别名 select orderyear, count(distinct custid) as numcusts from (select YEAR(orderdate) as orderyear, custid from Sales.Orders) as d group by orderyear; -- 或 select orderyear, count(distinct custid) as numcusts from (select YEAR(orderdate), custid from Sales.Orders) as d(orderyear, custid) group by orderyear; -- 使用参数 declare @empid as int = 3; select orderyear, count(distinct custid) as numcusts from (select year(orderdate) as orderyear, custid from Sales.Orders where empid = @empid) as d group by orderyear;
嵌套:若是需要用一个自己就引用了某个派生表的查询去定义另外一个派生表,最终获得的就是嵌套派生表。派生表之因此会嵌套,是由于在外部查询的from子句中定义了派生表,而不是单独定义的。嵌套通常是编程过程当中容易产生问题的一个方面,由于它趋于让代码变得复杂,下降代码的可读性。安全
select orderyear, numcusts from (select orderyear, count(distinct custid) as numcusts from (select YEAR(orderdate) as orderyear, custid from sales.orders) as d1 group by orderyear) as d2 where numcusts > 70;
派生表的多引用:派生表另外一个存在问题的方面源于派生表是在外部查询的from子句中定义的,其逻辑顺序并不优先于外部查询。当对外部查询的from子句进行处理时,派生表其实并不存在。所以,若是须要引用派生表的多个实例,这是还不能这样作。相反,必须基于同一查询去定义多个派生表。架构
select cur.orderyear , cur.numcusts as curnumcusts , prv.numcusts as prvnumcusts , cur.numcusts - prv.numcusts as growth from (select year(orderdate) as orderyear , count(distinct custid) as numcusts from sales.Orders group by year(orderdate)) as cur left outer join (select year(orderdate) as orderyear , count(distinct custid) as numcusts from sales.orders group by year(orderdate)) as prv on cur.orderyear = prv.orderyear + 1;
公用表表达式(CTE)是和派生表很类似的另外一种形式的表表达式,并且具备一些重要优点。
CTE使用with子句定义的,它的通常格式为:
WITH <CTE_NAME>[(<TARGET_COLUMN_LIST>)]
AS
(
<INNER_QUERY_DEFINING_CTE>
)
<OUTER_QUERY_AGAINST_CTE>;
前面提到的为了有效定义表表达式而需要遵照的全部规则,对定义CTE的内部查询也一样适用。app
with USACusts as ( select custid, companyname from sales.Customers where country = N'USA' ) select * from USACusts;
和派生表同样,一旦外部查询完成,CTE的生命期也结束了
CTE也支持两种格式的列别名命名方式--内联格式和外部格式。对于内联格式,要指定<expression> AS <column_alias>;对于外部格式,在CTE名称后面的一对圆括号中目标列的列表。函数
with C as ( select year(orderdate) as orderyear, custid from Sales.Orders ) select orderyear, COUNT(distinct custid) as numcusts from c group by orderyear; with C(orderyear, custid) as ( select year(orderdate), custid from Sales.Orders ) select orderyear, count(distinct custid) as numcusts from c group by orderyear;
使用参数post
declare @empid as int = 3; with c as ( select year(orderdate) as orderyear, custid from Sales.Orders where empid = @empid ) select orderyear, count(distinct custid) as numcusts from c group by orderyear;
定义多个CTE性能
with c1 as ( select year(orderdate) as orderyear, custid from Sales.Orders ), c2 as ( select orderyear, count(distinct custid) as numcusts from c1 group by orderyear ) select orderyear, numcusts from c2 where numcusts > 70;
CTE的多引用
with yearlycount as ( select year(orderdate) as orderyear , COUNT(distinct custid) as numcusts from sales.orders group by YEAR(orderdate) ) select cur.orderyear , cur.numcusts as curnumcusts , prv.numcusts as prvnumcusts , cur.numcusts - prv.numcusts as growth from yearlycount as cur left outer join yearlycount as prv on cur.orderyear = prv.orderyear;
递归CTE
CTE之因此与其余表表达式不一样,是由于它支持递归查询。定义一个递归CTE至少须要两个(可能须要更多)
查询:第一个查询称为定位点成员(anchor member),第二个查询称为递归成员(recursive member)。
基本格式为
with <CTE_NAME>[(<target_column_list>)]
as
(
<anchor_member>
union all
<recursive_member>
)
<outer_query_against_CTE>;
递归成员是一个引用了CTE名称的查询。对CTE名称的引用表明的是一个在一个执行序列中逻辑上的“前一个结果集”。第一次调用递归成员时,“前一个结果集”表明由定位点成员返回的任何结果集。以后每次调用递归成员时,对CTE名称的引用表明对递归成员的前一次调用所返回的结果集。递归成员没有显式的递归终止检查(终止检查是隐式的)。递归成员会一直被重复调用,直到返回空的结果集或超出了某种限制条件。在查询返回的结果上,两个成员查询必须在列的个数和相应列的数据类型上保持兼容。外部查询中的CTE名称引用表明对定位点成员调用和全部对递归成员调用的联合结果集。
with empsCte as ( select empid, mgrid, firstname, lastname from hr.Employees where empid = 2 union all select c.empid, c.mgrid, c.firstname, c.lastname from empsCte as p join hr.Employees as c on c.mgrid = p.empid ) select empid, mgrid, firstname, lastname from empsCte;
若是递归成员的联接谓词中存在逻辑错误,或是循环中的数据结果出了问题,均可能致使递归成员被调用无限屡次。为了安全起见,SQLServer默认把递归成员最多能够调用的次数限制为100次,递归成员的调用次数达到101次时,代码将会因递归失败而终止运行。为了修改默认的最大递归次数,能够在外部查询的最后指定option(maxrecursion n)。这里的n是一个范围在0到32767之间的整数,表明想要设定的最大递归调用次数限制。若是想去掉对递归调用次数的限制,能够将maxrecursion设为0。注意,SQLServer把定位点成员和递归成员返回的临时结果集先保存在tempdb数据库的工做表中。若是去掉对递归次数的限制,万一查询失控,工做表的体积将很快变得很是大。当tempdb数据库的体积不能再继续增加时,查询便会失败。
视图
视图和内联表值函数(inline TVF)是两种可重用的表表达式,它们的定义存储在一个数据库对象中。一旦建立,这些对象就是数据库的永久部分:只有用删除语句显式删除,他们才会从数据库中移除。在其余不少方面,视图和内联表值函数的处理方式都相似于派生表和CTE。例如,当查询视图和内联TVF时,SQLServer会先扩展表表达式的定义,再直接查询底层对象,这与派生表和cte的处理方式是同样的。
use TSQLFundamentals2008; if OBJECT_ID('sales.usacusts') is not null drop view Sales.usacusts; go create view sales.usacusts as select custid, companyname, contactname, contacttitle, address, city, region, postalcode, country, phone, fax from Sales.Customers where country = N'USA'; go select custid, companyname from Sales.usacusts;
由于视图是数据库中的一个对象,因此能够用权限来控制对视图的访问,就像其余查询的数据库对象同样。
注意,通常推荐在和视图有关的应用上下文应该避免使用select * 语句。列是在编译视图时进行枚举的,新加的列可能不会自动加到视图中。
用一个名为sp_refreshview的存储过程能够刷新视图的元数据,但为避免混淆,最好的开发实践就是在视图的定义中显式的列出须要的列名。若是在底层表中添加了列,并且在视图中须要这些新加的列,则可使用alter view语句对视图定义进行相应的修改。
视图和order by子句
用于定义视图的查询语句,必须知足以前在介绍派生表时对表表达式提到的全部要求。虽然视图不用保证数据行的任何顺序,但视图的全部列都必须有名称,并且全部列名必须是惟一的。
记住,在定义表表达式的查询语句中不容许出现order by子句,由于关系表的行之间没有顺序。视图建立一个有序视图的想法也不合理,由于这违反了关系模型定义的关系的基本属性。若是为了数据展现的目的,确实须要从视图中返回有序的数据行,这时也不该该让视图作违反规则的事情。相反,应该在使用视图的外部查询中指定一个数据展现用的order by子句。
select custid, companyname, region from sales.usacusts order by region;
输出中行的任何顺序均可以认为是有效的,不会保证有什么特定的顺序。所以,当对表表达式进行查询时,除非在外部查询中指定了order by子句,不然不该该假定输出具备任何顺序。
不要把用于定义表表达式的查询和其余用途的查询混为一谈。对于包含top和order by的查询,只有在表表达式的上下文中,它才不保证输出具备特定的顺序。而对于不是用于定义表表达式的查询,order by子句即用于为top选项提供逻辑筛选服务,也用于控制输出结果的排列顺序。
视图选项
当建立或修改视图时,能够在视图定义中指定视图的属性和选项。在视图定义的头部,能够用with子句来指定诸如encryption和schemabinding这样的属性;在视图查询的末尾,还能够指定with check option。
encryption选项
在建立和修改视图、存储过程、触发器及用户定义函数(UDF)时,均可以使用encryption选项。若是指定encryption选项,SQLServer在内部会对定义对象的文本信息进行混淆(obfuscated)处理。普通用户经过任何目录对象都没法直接看到这种通过混淆处理的文本,只有特权用户经过特殊手段才能反问建立对象的文本。
alter view sales.usacusts as select custid, companyname, contactname, contacttitle, address , city, region, postalcode, country, phone, fax from sales.Customers where country = N'USA'; go -- 获得视图定义 select OBJECT_DEFINITION(object_id('sales.usacusts')); -- 修改视图定义,这一次要包含encryption选项 alter view sales.usacusts with encryption as select custid, companyname, contactname, contacttitle, Address ,city, region, postalcode, country, phone, fax from sales.Customers where country = N'USA'; -- 再一次获取视图定义的文本,获得结果为NULL select OBJECT_DEFINITION(object_id('sales.usacusts')); -- 除了object_definition函数,还可使用存储过程sp_helptext来获取对象的定义。 exec sp_helptext 'sales.usacusts';
SCHEMABINDING选项
视图和UDF支持SCHEMABINDING选项,该选项能够将对象和列的架构绑定到引用其对象的架构。也就是说,一旦指定了这个选项,被引用的对象就不能被删除,被引用的列也不能删除或修改。
alter view sales.usacusts with schemabinding as select custid, companyname, contactname, contacttitle, address, city, region, postalcode, country, phone, fax from sales.Customers where country = N'USA'; -- 试图删除customers表中的address列时会提示错误信息 alter table sales.customers drop column address;
对象定义必须知足两个技术要求,才能支持SCHEMABINDING选项。不容许在查询的select子句中使用星号*, 必须显式的列出列名。此外,在引用对象时,必须使用带有架构名称修饰的完整对象名称。这两个要求都是日常值得遵照的最佳实践原则。能够想象,在建立对象时指定SCHEMABINDING选项,也是一种好的实践方法。
CHECK OPTION选项
check option选项的目的是为了防止经过视图执行的数据修改与视图中设置的过滤条件发生冲突。
-- 例如,插入一个英国客户 insert into sales.usacusts(companyname, contactname, contacttitle, address, city, region, postalcode, country, phone, fax) values(N'Customers ABCDE', N'Contact ABCDE', N'Title ABCDE', N'Address ABCDE', N'London', Null, N'12345', N'UK', N'012-3456789', N'012-3456789'); -- 查找这个客户将的到一个空的结果集 select custid, companyname, country from sales.usacusts where companyname=N'customers ABCDE'; -- 为了查找这个新客户,能够直接查询customers表 select custid, companyname, country from sales.Customers where companyname = N'Customers ABCDE'; -- 若是想防止这种与视图的查询过滤条件相冲突的修改,只须要在定义视图的查询语句末尾加上 -- with check option便可 alter view sales.usacusts with schemabinding as select custid, companyname, contactname, contacttitle, address, city, region, postalcode, country, phone, fax from sales.Customers where country = N'USA' with check option; go -- 这样,在试图插入数据时会报错
内联表值函数是一种可重用的表表达式,可以支持输入参数。除了支持输入参数之外,内联表值函数在其余方面都与视图类似。正由于如此,内联表值函数能够看做是一种参数化视图,尽管并无这种正式的说法。
use tsqlfundamentals2008; if OBJECT_ID('dbo.fn_getcustorders') is not null drop function dbo.fun_getcustorders; go create function dbo.fn_getcustorders (@cid as int) returns table as return select orderid, custid, empid, orderdate, requireddate, shippeddate, shipperid, freight, shipname, shipaddress, shipcity, shipregion, shippostalcode, shipcountry from Sales.Orders where custid = @cid; go
这个内联表值函数接受一个表明客户id的输入参数@cid,返回由输入客户下的全部订单。对内联表值函数的查询和用DML(数据操做语言)对其它表进行的查询同样。若是函数接受输入参数,则能够在函数名称后面的圆括号内列出全部参数。此外,应该确保为表表达式提供别名。并不老是必须为表表达式提供别名,但这的确是一个很好的时间方法,由于它能够加强代码的可读性,减小出错的机会。
select orderid, custid from dbo.fn_getcustorders(1) as co; select co.orderid, co.custid, od.productid, od.qty from dbo.fn_getcustorders(1) as co join sales.OrderDetails as od on co.orderid = od.orderid;
APPLY运算符也是在SQLServer2005中引入的一个非标准表运算符。和其余表运算符同样,这个运算符也是在查询的FROM子句中使用。APPLY运算符支持两种形式:CROSS APPLY和OUTER APPLY。
CROSS APPLY只实现了一个逻辑查询步骤,而OUTER APPLY实现了两个步骤。APPLY 运算符对两个输入表进行操做,其中第二个能够是一个表表达式,而咱们将它们分别称为左表和右表。右表一般是一个派生表或内联表值函数。CROSS APPLY运算符实现了一个逻辑查询处理逻辑:把右表表达式应用到左表中的每一行,再把结果集组合起来,生成一个统一的结果表。就目前来看,CROSS APPLY运算符与交叉链接很是相似,从某种意义上讲也确实如此。
select s.shipperid, e.empid from sales.shippers as s cross join hr.Employees as e; select s.shipperid, e.empid from sales.Shippers as s cross apply hr.Employees as e;
与联接不一样的是,当使用cross apply操做符时, 右表表达式可能表明不一样的数据行集合。为此,能够在右边使用一个派生表,在派生表的查询中去引用左表列;也可使用内联表值函数,把左表中的列做为输入参数进行传递。
select c.custid, a.orderid, a.orderdate from Sales.Customers as c cross apply (select top (3) orderid, orderdate, requireddate from Sales.Orders as o where o.custid = c.custid order by orderdate desc, orderid desc) as a;
能够把上面查询中的表表达式A看做是一个相关子查询。就逻辑查询处理来讲,右表表达式要应用于customers表的每一行。若是右表表达式返回的是一个空集,cross apply运算符则不会返回相应左边的数据行。若是要右表表达式返回空集时也照样返回相应左表中的行,则能够用outer apply运算符代替cross apply。outer apply运算符增长了另外一个逻辑处理阶段:标识出让右表表达式返回空集的坐标中的数据行,并把这些行做为外部行添加到结果集中,来自右表表达式的列用null做为占位符。从某种意义上讲,这个处理步骤相似于左外联接中增长外部行的那一步。
select c.custid, a.orderid, a.orderdate from Sales.Customers as c outer apply (select top(3) orderid, empid, orderdate, requireddate from sales.Orders as o where o.custid = c.custid order by orderdate desc, orderid desc) as a;
如下代码建立了一个内联表值函数fn_toporders
if OBJECT_ID('dbo.fn_toporders') is not null drop function dbo.fn_toporders; go create function dbo.fu_toporders (@custid as int, @n as int) returns table as return select top(@n) orderid, empid, orderdate, requireddate from sales.Orders where custid = @custid order by orderdate desc, orderid desc; go select c.custid, c.companyname, a.orderid, a.empid, a.orderdate, a.requireddate from sales.Customers as c cross apply dbo.fu_toporders(c.custid, 3) as a;