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

如上操作看似代碼比較簡潔也能完成我們的查詢訴求,但是我們用派生表來進行查詢又是怎樣的呢? 

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

而利用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;

上面我們通過封裝內嵌表值函數代替派生表使代碼更具可讀性和可維護性。到此我們可以得出一點基本結論。

APPLY運算符使用分析結論:當需要對表中的每一行進行應用時,且需要將所有結果集組合到一個結果集表中時,此時我們應該使用APPLY運算符,至於是使用CROSS APPLY還是OUTER APPLY根據場景而定,雖然APPLY右側表可以用相關子查詢或者派生表來實現,但是使得代碼臃腫和可維護性差,通過封裝內嵌表值函數來實現可以說是對右側表通過相關子查詢或者派生表來實現的完美替代者。

總結

本節我們講解了APPLY運算符中兩種類型的使用,下一節我們來分析下關於CROSS APPLY VS INNER JOIN的性能問題,同時也說明下CROSS APPLY和OUTER APPLY的應用場景。簡短的內容,深入的理解,我們下節再會。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM