设为首页 收藏本站
查看: 715|回复: 0

[经验分享] SQL Server-聚焦APPLY运算符(二十七)

[复制链接]

尚未签到

发表于 2017-7-13 12:42:19 | 显示全部楼层 |阅读模式
前言
  其实有些新的特性在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;
DSC0000.png

  上述完成的是返回每个客户最近的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
DSC0001.png

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



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是怎样实现的。



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子句。



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
DSC0002.png

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



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则是如下:



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;
DSC0003.png

  上面我们通过封装内嵌表值函数代替派生表使代码更具可读性和可维护性。到此我们可以得出一点基本结论。
  APPLY运算符使用分析结论:当需要对表中的每一行进行应用时,且需要将所有结果集组合到一个结果集表中时,此时我们应该使用APPLY运算符,至于是使用CROSS APPLY还是OUTER APPLY根据场景而定,虽然APPLY右侧表可以用相关子查询或者派生表来实现,但是使得代码臃肿和可维护性差,通过封装内嵌表值函数来实现可以说是对右侧表通过相关子查询或者派生表来实现的完美替代者。

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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-393417-1-1.html 上篇帖子: SQL Server CONVERT() 截取日期 下篇帖子: SQL Server中SELECT会真的阻塞SELECT吗?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表