1、表表达式概述
(1)表表达式(table expression) 是一个命名的查询表达式、代表一个有效的关系表
(2)在DML 中,使用表表达式和使用其他表非常类似
(3)sqlserver 支持4种表表达式: 派生表、公用表达式(CTE)、视图 、内嵌值函数(内嵌TVF)
(4)表表达式没有任何的物理实例化、是虚拟的、内部查询是嵌套 的
(5)使用表表达式的好处是代码逻辑方便有关、与性能无关、表表达式本身不会对性能带来正负影响
2、有效定义表表达式查询需要满足的3个要求
2.1 无法保证顺序
(1)表表达式用于代表一个关系表、关系表的行是无序的,有序那是游标。
(2)如果在表表达式,如派生表中使用order by ,必须使用top 或者 OFFSET 否则报错
(3)即使在 派生表中使用order by ,在外部查询没有使用order by的情况下,查询结果仍然是无序的
2.2 所有列都必须具有名称
(1)在定义表表达式的查询中,必须有名称、如果出现表达式 或者 函数 导致没有名称的列出现,直接报错
2.3所有列名必须唯一
(1)当连表查询、两种表具有相同名称的列,就会到时该表表达式invalid ,此时通过别名解决
3、派生表
(1)派生表 也称子查询表、临时表,是在from 子句中定义的,他们存在的范围是外部查询,一旦外部查询接收,派生表就消失了
(2)派生表的查询需要定义在括号内
(3)使用表表达式的一个好处就是,外部查询可以引用内部查询select 子句中分配的列别名、这就可以绕开where 和 group by 无法使用select子句分配别名的问题
(4)在定义派生表的查询中,可以引用参数,参数可以是例行的能够用于存储过程或函数的变量
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
(5)如果定义的派生表查询需要引用林外一个派生表,这将是一个嵌套派生表。嵌套是常见的编程问题方面、会导致代码复制、可读性差
(6)派生表是定义在外部查询的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;
不能引用同一派生表的多个实例,导致维护多份相同代码,这也是派生表的一个问题
4、公用表表达式(CTE)
(1)公用表表达式(CTE)是表表达式的另一种标准形式,与派生表类似,但具有几个优势
(2)CTE 通过with 语句定义
with USACusts as
(
select custid, companyname
from Sales.Customers
where country = N'USA'
)
select * from USACusts
(3) 表表达式的三个要求,在CTE 中也是都要满足
(4)T-SQL 中with 子句可用于不同的目的,为避免歧义,在with子句用于定义CTE时 ,with前面的分句必须打分号隔开
(5)CTE 和 派生表一样也支持量好难过命名方式、内嵌式 和 外部式
(6)CTE 中也可以使用参数
(7)表面上看CTE 和 派生表只有语义上的区别。但是先定义后使用的特点,解决了嵌套问题
可定义多个CTE ,用分号隔离;
相互引用,不再嵌套;
(8)不能在派生表中的括号内去定义CTE
(9)就外部查询的from子句而言,CTE 在其之前就已经存在了,可以已用同一个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 + 1;
(10)CTE 支持递归操作,但是递归操作很危险,有可能吧数据库搞崩的,特别是取消了递归次数限制的情况
5、视图(VIEW)
(1)派生表和CTE范围有限、外部查询完成就消失 、无法重复使用
(2)视图和内嵌值函数(TVF)是两种可以重复使用的表达式类型、其定义被存储为数据库对象、永久保存、直到显式删除
(3)其他方便,视图和TVF 和CTE 一样,比如查询时,sql server 都会拓展表表达式的定义、直接查底层的对象
(4)创建视图
create view Sales.USACusts
as
select
custid, companyname, contactname, contacttitle, address,
city, region, postalcode, country, phone, fax
FROM Sales.Customers
WHERE country = N'USA';
(5)和CTE 一样,视图的别名也有内嵌式 和 外部式两种
(6) 由于视图是数据库的对象,可以控制视图的访问权限、包括select 、insert 、update 、delete 权限
(7) 定义视图是尽量不要使用select * ,因为在创建视图之后,底层对象表发生改变、视图中的列是不会同步的、显式指定列,然后通过alter view 修改视图定义
(8)视图的select 也必须满足表表达式的所有要求 、视图是无法保证行的顺序的、列名称必须唯一且存在
(9)要想查询视图返回有序的结果集,需要在外部查询中进行order by
(10) sql server 2012 尝试通过 orderby xxx offset 0 rows 获取 有序视图,但这是当前的一种优化手段,不能绝对保证有序
(11)获取视图定义(创建视图的sql 就是视图定义)
select OBJECT_DEFINITION(OBJECT_ID('Sales.USACusts'))
(11)视图选项
在创建和更改视图时,可以指定作为视图定义一部分的视图属性 和 视图选项。
①encryption 选项
encryption选项指示sql server 在内部以代码混淆方式存储对象定义文本,代码混淆文本对通过任何目录对象的用户都是不可直接见的,仅对通过特定方法的特权用户可见
-- 修改视图、指定encryption选项 ,修改之后查询视图定义返回NULL
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';
②schemabinding 选型
schemabinding 选项对视图和UDF可用 ,将“被引用对象的架构和列(就是视图的元数据表和列)” 绑定到 引用对象(视图)的架构中 ,此时不能删除和修改 被引用的列 和对象(不能删除修改元数据表和列)
ALTER VIEW Sales.USACusts
WITH schemabinding,encryption
AS
SELECT
custid, companyname, contactname, contacttitle, address,
city, region, postalcode, country, phone, fax
FROM Sales.Customers
WHERE country = N'USA';
③check option 选项
check option的目的是防止出现视图修改与视图视图塞选的冲突,下面演示该冲突
-- 定义视图,值查来自美国(USA)客户 CREATE VIEW Sales.USACusts
AS
SELECT
custid, companyname, contactname, contacttitle, address,
city, region, postalcode, country, phone, fax
FROM Sales.Customers
WHERE country = N'USA'; -- 通过视图往源数据表Customers表插入一条英国的用户数据
INSERT INTO Sales.USACusts(
companyname, contactname, contacttitle, address,
city, region, postalcode, country, phone, fax)
VALUES(
N'Customer ABCDE', N'Contact ABCDE', N'Title ABCDE', N'Address ABCDE',
N'London', NULL, N'', N'UK', N'012-3456789', N'012-3456789'); -- 冲突出现,此时从视图查询不到刚刚往视图插入的英国用户数据,但是直接去Customers 表中查是有这条数据的
SELECT custid, companyname, country
FROM Sales.USACusts
WHERE companyname = N'Customer ABCDE';
check option 就是解决上述冲突、此时当通过视图插入的数据不满足视图塞选条件,会直接报错
-- 修改视图定义,添加check point 属性
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; -- 测试插入与视图塞选条件不符 的数据,报错
INSERT INTO Sales.USACusts(
companyname, contactname, contacttitle, address,
city, region, postalcode, country, phone, fax)
VALUES(
N'Customer FGHIJ', N'Contact FGHIJ', N'Title FGHIJ', N'Address FGHIJ',
N'London', NULL, N'', N'UK', N'012-3456789', N'012-3456789');
6、内嵌表值函数(TVF)
(1)内嵌TVF 是支持输入参数的可重复使用的表表达式 ,可以理解成参数化视图
(2)定义
-- 创建TVF ,传入客户cid, 查询该客户的订单
create function
dbo.GetCustOrders
(@cid as int) returns TABLE
return
select
orderid, custid, empid, orderdate, requireddate,
shippeddate, shipperid, freight, shipname, shipaddress, shipcity,
shipregion, shippostalcode, shipcountry
from Sales.Orders
where custid = @cid
(3)使用
-- 强烈建议给TVF 取别名,参数在括号内传入
SELECT orderid, custid
FROM dbo.GetCustOrders(1) AS O;