表表达式是一种命名的查询表达式,代表一个有效的关系表。可以像其他表一样,在数据处理语句中使用表表达式。SQL Server支持4种类型的表表达式:派生表(derived table)、公用表表达式(CTE,common table expression)、视图以及内联表值函数(inline TVF,inline table-valued function)。

表表达式并不是物理上真实存在的什么对象,它们是虚拟的。对于表表达式的查询在数据库引擎内部都将转换为对底层对象的查询。使用表表达式的好处通常体现在代码的逻辑方面,而不是性能方面。

1.派生表

派生表(也称为表子查询)是在外部查询的FROM子句中定义的,派生表的存在范围为定义它的外部查询,只要外部查询一结束,派生表也就不存在了。 定义派生表的查询语句要写在一对圆括号内,后面跟着AS子句和派生表的名称。例如,以下代码定义了一个名为USACusts的派生表,它是一个返回所有美国客户的查询:

SELECT *
FROM (SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA') AS USACusts;

要有效地定义任何类型的表表达式,查询语句必须满足三个要求:

(1).不保证有一定的顺序。表表达式代表的是一个表,而关系表中的行是没有固定顺序的。ANSI SQL不允许在用于定义表表达表的查询语句中有ORDER BY子句,T-SQL大体上遵守了这一限制,只有一个例外情况--当在语句中指定了TOP。在带有TOP选项的查询语句中,ORDER BY子句的逻辑目的只有一个:为TOP选项定义要筛选出哪些行。

(2).所有的列必须有名称。

(3).所有的列名必须是唯一的。

1.1 分配列别名

(1)用内联别名的形式对派生表进行查询

SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM (SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders) AS D
GROUP BY orderyear;

(2)用外部命名格式对派生表进行查询

SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM (SELECT YEAR(orderdate), custid
FROM Sales.Orders) AS D(orderyear, custid)
GROUP BY orderyear;

2.公用表表达式(CTE)

以下代码定义了一个名为USACusts的CTE,它的内部查询返回所有来自美国的客户,外部查询则选择了CTE中的所有行,示例如下:

WITH USACusts AS
(
SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'
)
SELECT * FROM USACusts;

2.1 分配列别名

CTE也支持两种格式的列别名命名方式--内联格式和外部格式。对于内联格式,要指定<expression> AS <column_alias>;对于外部格式,在CTE名称后面的一对圆括号中指定目标列的列表。示例如下:

-- Inline column aliasing
WITH C AS
(
SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders
) -- External column aliasing
WITH C(orderyear, custid) AS
(
SELECT YEAR(orderdate), custid
FROM Sales.Orders
)

2.2 定义多个CTE

从表面来看,派生表和CTE之间的区别可能只是语义方面。不过CTE具有几个重要优势。优势之一就是:如果须要在一个CTE中引用另一个CTE,不须要像派生表那样进行嵌套,相反,只要简单地在同一WITH子句中定义多个CTE,并用逗号把它们分隔开。每个CTE可以引用在它前面定义的所有CTE,而外部查询则可以引用所有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 >

2.3 CTE的多引用

CTE是先定义再查询,就外部查询的FROM子句来说,CTE是已经存在的,因此,可以引用同一个CTE的多个实例。

2.4 递归CTE

定义一个递归CTE至少需要两个查询,第一个查询称为定位点成员(anchor member),第二个查询称为递归成员(recursive member)。定位点成员查询只会被调用一次。递归成员是一个引用了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;

定位点成员对HR.Employees表进行查询,只返回雇员等于2的记录行。第一次调用递归成员,返回雇员2的直接下属,比如3和5。第二次调用递归成员,返回雇员3和5的直接下属,以此类推,直到递归成员返回一个空的结果集。

3.视图

到目前为止,我们讨论了两种类型的表表达式(派生表和CTE),它们的作用范围都非常有限,仅限于在单个语句的范围内使用。只要包含这些表表达式的外部查询完成操作,它们便会随之消失。而视图和内联表值函数(inline TVF)是两种可重用的表表达式,它们的定义储存在一个数据库对象中,一旦创建,就些对象就是数据库的永久部分。示例如下所示:

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';

一般推荐在和视图在关的应用上下文中应用避免使用SELECT *语句,列是在编译视图时进行枚举的,新加的列可能不会自动添加到视图中。如果在底层表中添加了列,而且在视图中需要这些新加的列,则可以使用ALTER VIEW语句对视图进行相应的修改。

3.1 视图选项

(1)ENCRYPTION选项

在创建或修改视图、存储过程、触发器用用户定义函数时,都可以使用ENCRYPTION选项。如果指定ENCRYPTION选项,SQL Server在内部会对定义对象的文本进行混淆处理,普通用户通过任何目录对象都无法直接看到这种经过混淆处理的文本,只有特权用户通过特殊手段才能访问创建对象的文本。以下代码可以返回对象的创建文本。

SELECT OBJECT_DEFINITION(OBJECT_ID('Sales.USACusts'));
GO

但是当修改视图定义,并指定ENCRYPTION选项时,视图定义的文本则返回NULL

ALTER VIEW Sales.USACusts WITH ENCRYPTION
AS

(2)SCHEMABINDING选项

一旦指定了这个选项,被引用的对象就不能删除,被引用的列也不能删除或修改。

ALTER VIEW Sales.USACusts WITH SCHEMABINDING

(3)CHECK OPTION

选项的目的是为了防止通过视图执行的数据修改与视图中设置的过滤条件发生冲突,如果没有设置CHECK OPTION选项,则可以通过USACusts视图插入一条UK的客户,这样的更新会传递到底层的数据表中。但是,修改后的客户将不能在视图中显示出来,因为它已经不再满足视图的查询过滤条件。如果想防止这种与视图的查询过滤条件相冲突的修改,只须在定义视图的查询语句末尾加上WITH CHECK OPTION即可。

-- Add CHECK OPTION to the View
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

4.内联表值函数

内联表值函数是一种可重要的表表达式,能够支持输入参数,支持输入参数以外,内联表值函数在其他方面都与视图类似。示例如下所示:

-- Creating fn_GetCustOrders function
USE TSQLFundamentals2008;
IF OBJECT_ID('dbo.fn_GetCustOrders') IS NOT NULL
DROP FUNCTION dbo.fn_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
-- Test Function
SELECT orderid, custid
FROM dbo.fn_GetCustOrders() AS CO;

5.APPLY运算符

APPLY运算符支持两种形式:CROSS APPLY和OUTER APPLY。APPLY运算符对两个输入表进行操作,其中第二个可以是一个表表达式,将它们分别称为左表和右表。CROSS APPLY实现了一个逻辑处理步骤:将右表表达式应用到左表中的每一行,再将结果集组合起来,生成一个统一的结果表。示例如下所示:

以下代码使用CROSS APPLY返回每个客户最新的三个订单:

SELECT C.custid, A.orderid, A.orderdate
FROM Sales.Customers AS C
CROSS APPLY
(SELECT TOP() orderid, empid, orderdate, requireddate
FROM Sales.Orders AS O
WHERE O.custid = C.custid
ORDER BY orderdate DESC, orderid DESC) AS A;

如果右表表达式返回的是一个空集,CROSS APPLY则不会返回相应左边的数据行。如果要在右表表达式返回空集时也照样返回相应左表中的行,则可以用OUTER APPLY运算符代替CROSS APPLY。

总结

借助表表达式可以简化代码,提高代码的可维护性,还可以封装查询逻辑。当需要使用表表达式,而且不计划重用它们的定义时,可以使用派生表或CTE。与派生表相比,CTE具有两个优点:CTE不用像派生表那样嵌套使用,此外,还可以引用同一CTE的多个实例,也派生表不能这么用。

当需要定义可重用的表表达式时,可以使用视图和内联表值函数。如果不须要支持输入参数,则使用视图,相反则使用内联表值函数

SQL SERVER技术内幕之5 表表达式的更多相关文章

  1. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  2. SQL Server进阶(六)表表达式--派生表、公用表表达式(CTE)、视图和内联表值函数

    概述 表表达式是一种命名的查询表达式,代表一个有效地关系表.可以像其他表一样,在数据处理中使用表表达式. SQL Server支持四种类型的表表达式:派生表,公用表表达式,视图和内联表值函数. 为什么 ...

  3. SQL SERVER技术内幕之10 可编程对象

    一.变量 变量用于临时保存数据值,以供在声明它们的同一批处理语句中引用.例如,以下代码先声明一个数据类型为INT的变量@i,再将它赋值为10; DECLARE @i as INT; SET @i = ...

  4. SQL SERVER技术内幕之7 透视与逆透视

    1.透视转换 透视数据(pivoting)是一种把数据从行的状态旋转为列的状态的处理,在这个过程中可能须要对值进行聚合. 每个透视转换将涉及三个逻辑处理阶段,每个阶段都有相关的元素:分组阶段处理相关的 ...

  5. SQL SERVER技术内幕之10 事务并发

    1.事务 1.1事务的定义 事务是作为单个工作单元而执行的一系列操作.定义事务边界有显式和隐式两种.显式事务的定义以BEGIN TRAN作为开始,以COMMIT TRAN提交事务,以ROLLBACK ...

  6. SQL SERVER技术内幕之8 分组集

    分组集就是分组(GROUP BY子句)使用的一组属性,在传统的SQL中,一个聚合查询只能定义一个分组集: 假设现在不想生成4个单独的结果集,而是希望生成一个统一的结果集,其中包含所有4个分组集的聚合 ...

  7. SQL SERVER技术内幕之3 联接查询

    JOIN表运算符对两个输入表进行操作.联接有三种基本类型:交叉联接.内联接和外联接.这三种联接的区别是它们采用的逻辑查询处理步骤各不相同,每种联接都有一套不同的步骤.交叉联接只有一个步骤----笛卡尔 ...

  8. SQL SERVER技术内幕之6 集合查询

    1.定义 集合运算会对两个输入查询的结果集进行逐行比较,根据比较结果和所使用的集合运算来确定某一行是否应该包含在集合运算的结果中.因为集合运算是针对集合之间进行的计算,所以集合运算涉及的两个查询不能包 ...

  9. SQL SERVER技术内幕之4 子查询

    最外层查询的结果集会返回给调用者,称为外部查询.内部查询的结果是供外部查询使用的,也称为子查询.子查询可以分成独立子查询和相关子查询两类.独立子查询不依赖于它所属的外部查询,而相关子查询则须依赖它所属 ...

随机推荐

  1. python三大神器之装饰器

    装饰器的形成过程 假如你要写一个计算函数执行时间的函数,代码如下: import time def func1(): print('in func1') def timer(func): def in ...

  2. spark ---词频统计(二)

    利用python来操作spark的词频统计,现将过程分享如下: 1.新建项目:(这里是在已有的项目中创建的,可单独创建wordcount项目) ①新建txt文件: wordcount.txt (文件内 ...

  3. MongoDB入门---文档查询操作之条件查询&and查询&or查询

    经过前几天的学习之路,今天终于到了重头戏了.那就是文档查询操作.话不多说哈,直接看下语法: db.collection.find(query, projection) query :可选,使用查询操作 ...

  4. sas简单使用

    1 数据存取: 逻辑库: libname  自己起的名字 ‘文件所在的路径’,若无这步数据则存在默认的work中. 另一个方法在sas里自己建立一个逻辑库,但是关闭后就消失了. 新建数据:data  ...

  5. 佛山Uber优步司机奖励政策(7月27日-8月2日)

    周上线时间少于等于7个小时,以下奖励条件无效,仅返还车资.   滴滴快车单单2.5倍,注册地址:http://www.udache.com/如何注册Uber司机(全国版最新最详细注册流程)/月入2万/ ...

  6. Netty示例

    一,服务端 ** * 测试Netty类库:服务端代码 * Created by LiuHuiChao on 2016/10/24. */ public class NettyServerTest { ...

  7. CC2541广播机制和代码分析(未完成)

    1. 广播通道有3个,是固定的吗?设备为了节省功耗,可以忽略掉几个应答? 连接间隔可以是7.5ms到4s内的任意值,但必须是1.25ms的整数倍,从设备延迟,实际上是一个连接间隔的倍数,代表从设备在必 ...

  8. 三 Hive 数据处理 自定义函数UDF和Transform

    三  Hive 自定义函数UDF和Transform 开篇提示: 快速链接beeline的方式: ./beeline -u jdbc:hive2://hadoop1:10000 -n hadoop 1 ...

  9. SpringBoot入门(五)——自定义配置

    本文来自网易云社区 大部分比萨店也提供某种形式的自动配置.你可以点荤比萨.素比萨.香辣意大利比萨,或者是自动配置比萨中的极品--至尊比萨.在下单时,你并没有指定具体的辅料,你所点的比萨种类决定了所用的 ...

  10. libevent学习四(Working with events)

    1.事件的分类 文件可写 文件可读 超时发生 信号发生 用户触发事件   2事件的生命周期        --非 persistent                                 ...