SQL Server-聚焦APPLY运算符(二十七)

前言

其实有些新的特性在SQL Server早就已经出现过,可是若非系统的去学习数据库你会发如今实际项目中别人的SQL实际上是比较复杂的,其实利用新的SQL Server语法会更加方便和简洁,从本节开始咱们将讲述一些SQL Server中早已出现的新语法,简短的内容,深刻的理解,Always to reivew the basics。数据库

初探APPLY运算符

APPLY运算符是一个很是强大的表运算符,可是APPLY不是标准的,相对应的标准叫作LATERAL,可是此标准并未在SQL Server中实现。像全部表运算符同样,该运算符用于查询的FROM子句中。APPLY运算符支持的类型是CROSS APPLY和OUTER APPLY。CROSS APPY仅仅实施一个逻辑查询处理阶段,而OUTER APPLY实施了两个阶段,APPLY运算符对两个输入表进行操做,第二个能够是一个表表达式,咱们将APPLY两侧的表分别叫作左侧表和右侧表,右侧表一般是一个派生表或TVF(内嵌表值函数)。CROSS APPLY运算符实施一个逻辑查询处理阶段-它将右侧的表表达式应用到左侧表的每一行,并生成一个组合结果集的结果表。CROSS APPLYl相似于交叉联接中的CROSS JOIN,可是使用CROSS APPLY运算符,右侧的表表达式能够对来自左侧表的每一行表示一个不一样的行集,这是与联接的不一样之处。当在右侧使用一个派生表,而且派生表查询中引用来自左侧表的属性,就能够实现此目标,或者是在右侧使用一个内嵌TVF,能够传递左侧的属性做为输入参数,一样能够实现此目的-摘抄自SQL Server 2012基础教程。下面咱们看一个简单的例子。函数

USE TSQL2012
GO

SELECT C.custid, A.orderid, A.orderdate
FROM Sales.Customers AS C
    CROSS 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;

上述完成的是返回每一个客户最近的3个订单。咱们能够将右侧的表表达式看作是一个相关子查询,右侧的表表达式经过引用custid对来自Customers表的每一行进行处理并返回每一个客户的最近的3个订单,是否是看起来很清爽呢,下面咱们将进一步探讨APPLY运算符的做用。性能

进一步探讨APPLY运算符

上面咱们看到经过相关子查询来进行查询显得代码有点丑陋,咱们再来看一个例子。查询每一个单价最高的订单,咱们经过子查询来实现。学习

CROSS APPLY

USE AdventureWorks2012
GO

SELECT 
     SalesOrderID
    ,OrderDate
    ,MaxUnitPrice =(SELECT MAX(sod.UnitPrice) FROM Sales.SalesOrderDetail sod WHERE soh.SalesOrderID = sod.SalesOrderID)
FROM Sales.SalesOrderHeader AS soh

如上操做看似代码比较简洁也能完成咱们的查询诉求,可是咱们用派生表来进行查询又是怎样的呢? ui

USE AdventureWorks2012
GO

SELECT 
    soh.SalesOrderID
    ,soh.OrderDate
    ,sod.max_unit_price
FROM Sales.SalesOrderHeader AS soh
JOIN
(
    SELECT 
        max_unit_price = MAX(sod.UnitPrice),
        SalesOrderID
    FROM Sales.SalesOrderDetail AS sod
    GROUP BY sod.SalesOrderID
) sod
ON sod.SalesOrderID = soh.SalesOrderID

此时因为两个表彻底不相关,咱们须要经过GROUP BY完成再进行JOIN,代码不是显得很是臃肿吗,这仍是简单的,当有多个表时就比较复杂了,致使代码就再也不具备可读性。可是自从在SQL Server 2005中有了APPLY妈妈不再用担忧我读不懂复杂的代码了,咱们看看CROSS APPLY是怎样实现的。spa

USE AdventureWorks2012
GO

SELECT 
    soh.SalesOrderID
    ,soh.OrderDate
    ,sod.max_unit_price
FROM Sales.SalesOrderHeader AS soh
CROSS APPLY
(
    SELECT 
        max_unit_price = MAX(sod.UnitPrice)
    FROM Sales.SalesOrderDetail AS sod
    WHERE soh.SalesOrderID = sod.SalesOrderID
) sod

当咱们利用内部联接时此时JOIN中的查询是独立的因此须要进行GROUP BY,而对于CROSS APPLY它自己就是对来自左侧的表中每一行就行处理并返回,同时利用CROSS APPLY它也超越了相关子查询,好比说咱们还须要查出每一个订单的总价呢,咱们利用相关子查询须要再次嵌入SELECT子句。code

SELECT 
     SalesOrderID           
    ,OrderDate              
    ,MaxUnitPrice           = (SELECT MAX(sod.UnitPrice) FROM Sales.SalesOrderDetail sod WHERE soh.SalesOrderID = sod.SalesOrderID)
    ,SumLineTotal           = (SELECT SUM(LineTotal) FROM Sales.SalesOrderDetail sod WHERE soh.SalesOrderID = sod.SalesOrderID)
FROM Sales.SalesOrderHeader AS soh

而利用CROSS APPLY只需添加集合函数SUM便可blog

USE AdventureWorks2012
GO

SELECT 
    soh.SalesOrderID
    ,soh.OrderDate
    ,sod.max_unit_price
    ,sod.sum_line_total
FROM Sales.SalesOrderHeader AS soh
CROSS APPLY
(
    SELECT 
        max_unit_price = MAX(sod.UnitPrice)
        ,sum_line_total = SUM(sod.LineTotal)
    FROM Sales.SalesOrderDetail AS sod
    WHERE soh.SalesOrderID = sod.SalesOrderID
) sod 

OUTER APPLY

对于OUTER APPLY,若是右侧的表表达式返回一个空集合,CROSS APPLY运算符不会返回相应的左侧行,也就是说OUTER APPLY和在派生表上进行LEFT JOIN是等同的,以下:教程

SELECT 
    soh.SalesOrderID
    ,soh.OrderDate
    ,sod.max_unit_price
FROM Sales.SalesOrderHeader AS soh
LEFT JOIN
(
    SELECT 
        max_unit_price = MAX(sod.UnitPrice),
        SalesOrderID
    FROM Sales.SalesOrderDetail AS sod
    GROUP BY sod.SalesOrderID
) sod
ON sod.SalesOrderID = soh.SalesOrderID

此时咱们利用OUTER APPLY则是以下:it

USE AdventureWorks2012
GO

SELECT 
    soh.SalesOrderID
    ,soh.OrderDate
    ,sod.max_unit_price
FROM Sales.SalesOrderHeader AS soh
OUTER APPLY
(
    SELECT 
        max_unit_price = MAX(sod.UnitPrice)
    FROM Sales.SalesOrderDetail AS sod
    WHERE soh.SalesOrderID = sod.SalesOrderID
) sod

上述对于APPLY右侧表表达式是一个派生表,此时为了封装,咱们可使用TVF内嵌表值函数来实现。其实将内嵌表值函数来代替派生表实现每一个客户最近的3个订单。首先咱们封装一个表值函数

USE TSQL2012
GO

IF OBJECT_ID('dbo.TopOrders') IS NOT NULL
    DROP FUNCTION dbo.TopOrders;
GO

CREATE FUNCTION dbo.TopOrders
    (@custid  AS INT, @n  AS  INT)
    RETURNS TABLE
AS RETURN

    SELECT  orderid, empid, orderdate, requireddate
    FROM Sales.Orders
    WHERE  custid = @custid
    ORDER BY orderdate DESC, orderid DESC
    OFFSET 0 ROWS FETCH FIRST @n ROWS ONLY;
GO

接着利用CROSS APPLY进行查询。

USE TSQL2012
GO

SELECT C.custid, C.companyname, A.orderid, A.empid, A.requireddate
FROM Sales.Customers AS C
 CROSS APPLY dbo.TopOrders(C.custid, 3) AS A;

上面咱们经过封装内嵌表值函数代替派生表使代码更具可读性和可维护性。到此咱们能够得出一点基本结论。

APPLY运算符使用分析结论:当须要对表中的每一行进行应用时,且须要将全部结果集组合到一个结果集表中时,此时咱们应该使用APPLY运算符,至因而使用CROSS APPLY仍是OUTER APPLY根据场景而定,虽然APPLY右侧表能够用相关子查询或者派生表来实现,可是使得代码臃肿和可维护性差,经过封装内嵌表值函数来实现能够说是对右侧表经过相关子查询或者派生表来实现的完美替代者。

总结

本节咱们讲解了APPLY运算符中两种类型的使用,下一节咱们来分析下关于CROSS APPLY VS INNER JOIN的性能问题,同时也说明下CROSS APPLY和OUTER APPLY的应用场景。简短的内容,深刻的理解,咱们下节再会。

相关文章
相关标签/搜索