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

概述

  表表达式是一种命名的查询表达式,代表一个有效地关系表。可以像其他表一样,在数据处理中使用表表达式。

  SQL Server支持四种类型的表表达式:派生表公用表表达式视图内联表值函数

为什么使用表表达式:

  1.使用表表达式的好处是逻辑方面,在性能上没有提升。

  2.通过模块化的方法简化问题的解决方案,规避语言上的某些限制。在外部查询的任何字句中都可以引用在内部查询的SELECT字句中分配的列别名。比如在SELECT字句中起的别名,不能在WHERE,group by等字句(逻辑顺序位于SELECT字句之前的字句)中使用,通过表表达式可以解决这类问题

派生表

派生表(也称为表子查询)是在外部查询的FROM子句中定义的,只要外部查询一结束,派生表也就不存在了。

派生表可以简化查询,避免使用临时表。相比手动生成临时表性能更优越。派生表与其他表一样出现在查询的FROM子句中

select * from (select * from athors)  temp

temp 就是派生表

派生出来的表必须要是一个有效的表.因此,它必须遵守以下几条规则:

  1. 所有列必须要有名称

  2. 列名称必须是要唯一

  3. 不允许使用ORDER BY(除非指定了TOP)

派生表:比如要查找一个叫张铁牛的人的信息,我们知道他是男性,为了缩小查找范围我把所有的男性都找出来,然后从这些男性中里面再去找张铁牛.这里男性的集合就相当于派生表,转成sql语句:

select 姓名,住址,身份证

from (select * from 表名 where 性别='男性') temp 

where 姓名='张铁牛'

(这里只是为了举例子),这里的 temp这个数据集就是派生表,它是虚表,在数据库中不存在的,是我们构建的,在这里的目的是为了缩小数据的查找范围.

分配列别名(建议使用内嵌别名形式)

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;

 嵌套

如果须要用一个本身就引用了某个派生表的查询去定义另一个派生表,最终得到的就是嵌套派生表。

例子:查询每年处理客户数超过70的订单年度和每年所处理的客户数量。

方案一:我们用第一节中单表查询查询出结果

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

方案二:嵌套派生表

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;

嵌套查询看起来非常复杂,嵌套查询也是很容易产生问题的一个方面。在这个例子中,使用嵌套派生表的目的是为了重用列别名。但是,由于嵌套增加了代码的复杂性,所以对于本例考虑使用方案一。

多个引用

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

使用参数

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

CTE和派生表相关具有以下优势:

如果要在一个CTE中引用另一个CTE,不须要像派生表那样进行嵌套,只需要在同一个WITH字句中定义多个CTE,并用逗号把它们分隔开。每个CTE可以引用在它前面定义的所有CTE,而外部查询则可以引用所有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 > 70;

CET中的多个引用

公用表表达式的好处之一是可以在接下来一条语句中多次引用:

WITH CTE_Test
  AS
  (
      SELECT * FROM Person_1
  )
  SELECT * FROM CTE_Test AS a  --第一次引用
  INNER JOIN  CTE_Test AS b    --第二次引用
  ON a.Id = b.Id
  ORDER BY a.Id DESC

 

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;

递归CET

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;

  CTE是一种十分优雅的存在。CTE所带来最大的好处是代码可读性的提升,这是良好代码的必须品质之一。使用递归CTE可以更加轻松愉快的用优雅简洁的方式实现复杂的查询。

CET、临时表、表变量

CET优点:

  相对于派生表最主要的优势在于可以一次定义,多次使用。

  CET大部分地方可以代替临时表。CTE最优秀的地方是在实现递归操作,和替代绝大部分游标的功能。

  CET后面必须直接跟使用CTE的SQL语句(如select、insert、update等),否则,CET将失效。但是临时表一直存在,除非drop掉。

CET缺点:

  对于大数据量,由于CET不能建索引,所以明显比临时表差。我给开发的建议是少于1万数据的话,CET和表变量就不要用于做暂存数据的功能。而改用临时表。

  数据量大时,CET的性能要比临时表差很多(即使临时表不建索引)

  CET要比表变量效率高得多!

临时表和表变量的选择

  我们对于较小的数据或者是通过计算出来的推荐使用表变量。如果数据的结果比较大,在代码中用于临时计算,在选取的时候没有什么分组的聚合,就可以考虑使用表变量

  一般对于大的数据结果,或者因为统计出来的数据为了便于更好的优化,我们就推荐使用临时表,同时还可以创建索引,由于临时表是存放在Tempdb中,一般默认分配的空间很少,需要对tempdb进行调优,增大其存储的空间。

  表变量实际上使用了临时表,从而增加了额外的I/O开销,因此,表变量的方式并不太适合数据量大且频繁查询的情况

 视图

1.视图和派生表和CTE的区别和共同点

区别:

  派生表和CTE不可重用:只限于在单个语句的范围内使用,只要包含这些表表达式的外部查询完成操作,它们就消失了。

  视图和内联表值函数是可重用的:它们的定义存储在一个数据对象中,一旦创建,这些对象就是数据库的永久部分;只有用删除语句显示删除或用右键删除,它们才会从数据库中移除。

共同点:

  在很多方面,视图和内联表值函数的处理方式都类似于派生表和CTE。当查询视图和内联表值函数时,SQL Server会先扩展表表达式的定义,再直接查询底层对象。

创建一个视图:

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 * FROM Sales.USACusts;

内联表值函数(内嵌TVF)

什么是内联表值函数

一种可重用的表表达式,能够支持输入参数。除了支持输入参数以外,内联表值函数在其他方面都与视图相似。

内嵌表值函数是支持输入参数的可重复使用的表表达式。除了支持输入参数之外的其他所有方面都和视图类似。我们来看下怎么创建内嵌表值函数。

下面演示如何创建函数:

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

如何使用函数:

SELECT orderid, custid
FROM dbo.fn_GetCustOrders(1) AS CO;

 小结

  借助表表达式可以简化代码,提高代码的可维护性,还可以封装查询逻辑。

  当需要使用表表达式,而且不计划重用它们的定义时,可以使用派生表或CTE。与派生表相比,CTE具有两个优点:CTE不用像派生表那样嵌套使用,此外,还可以引用同一CTE的多个实例,也派生表不能这么用。

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

  (1)表表达式可以简化代码,提高代码的可维护性和封装查询逻辑。

  (2)当需要使用表表达式并且不打算重复使用其定义时,可以使用派生表或CTE,而CTE对派生表具有更多优势不需要像派生表那样嵌套CTE,使用CTE使代码更加模块化和便于维护,此外,还可以引用同一个CTE的多个实例,这一点是派生表无法实现的。

  (3)当需要使用表表达式并且需要定义可重复使用的表表达式时,可以使用视图或内嵌表值函数,当不需要支持输入参数时,可以使用视图,否则,应当使用内嵌表值函数(TVF)。

 APPLY运算符

APPLY运算符是一个非标准标准运算符。APPLY运算符对两个输入进行操作,其中右边的表可以是一个表表达式。

CROSS APPLY把右边表达式应用到左表中的每一行,再把结果集组合起来,生成一个统一的结果表。和交叉连接相似

OUTER APPLY把右边表达式应用到左表中的每一行,再把结果集组合起来,然后添加外部行。和左外联接中增加外部行的那一步相似

 

 

 

 

 资料

https://www.cnblogs.com/kissdodog/archive/2013/06/24/3153012.html

https://blog.csdn.net/miqi770/article/details/51505720

https://www.cnblogs.com/janneystory/p/5623019.html

http://www.cnblogs.com/jackson0714/p/TSQLFundamentals_04_part1.html

    原文作者:~沐风
    原文地址: https://www.cnblogs.com/cnki/p/9561427.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞