Apr 12, 2014

APPLY operator: a correlated subquery and JOIN blended together.

APPLY operator was first introduced with SQL Server 2005. And here is my own summary about this operator. 

Example code

Select  S.Supplierid, S.CompanyName as supplier, A.*
     From Production.Suppliers s
             CROSS APPLY ( Select productid, productname, unitprice   
                                               from Production.Products p
                                               where p.supplierid = s.supplierid
                                               order by unitprice, productid
                                               offset 0 rows fetch first 2 rows only )
as A

APPLY operator
(1) It applies a table expression in the right side to each row from a table expression in the left side.
      (correspondingly color-coded in the example code)
(2) And usually a table expression in the right side references to an element from the left table. In other words, the right table expression is correlated to the left table expression.    
(3) So APPLY operator seems to have a mixed concept of a correlated subquery and JOIN.
        APPLY operator 
     - evaluates the right table expression for each row of the left table and yields a result set
     - and then, joins the evaluated result set back to the row in the left table.
(4) Two different types: Cross Apply and Outer Apply


Re-explained in the context of the provided example code
The left table contains supplier records, and each record has a supplierid.  
The right table contains product records.

(1) The Apply operator takes each supplierid from the left table and evaluates the right table for that supplierid, which yields a evaluated result set. The operator now JOIN the result set back to the record of the supplierid in the left table.
(2) The above process is repeated for each supplierid(each row) of the left table(cursor-based operation).
(3) When the whole operation is completed, the result of the example query will show the two products with the lowest unit prices for each suppliers. See the query result below.
(4) **IF a supplier doesn't have any product record in the product table, that supplier will be omitted from the query result. It is because the example query uses CROSS Apply. OUTER Apply will, however, include such a supplier with NULLs in the columns from the right table 

** CROSS APPLY vs. OUTER APPLY is similar to INNER JOIN vs. LEFT OUTER JOIN.
* Note : If the right table doesn't reference any element in the left table, the query result will be exactly same as that of CROSS JOIN between the two tables(Cartesian product).


Result of the example query  (order by supplierid)




APPLY operator challenged.
Before I got a good grasp of APPLY operator, I was just wondering if I could find a different way to generate the same query result without using APPLY operator. And I tried and came up with the following query. It yields the same result. To be honest, the following query did make more sense to me before I become comfortable with APPLY operator. But I now see the benefits of using APPLY operator, so I will probably use the operator when it is necessary.

Select   S.supplierid, S.companyName as supplier, P.productid, p.productname, p.unitprice
    From   Production.suppliers s
             INNER JOIN ( select  ROW_NUMBER() over ( partition by supplierid
                                                                                           order by unitprice, productid) as rownum
                                                 ,supplierid, productid, productname, unitprice
                                            from  Production.Products ) P   ON s.supplierid = p.supplierid
    where ISNULL(p.rownum,0) <=2




APPLY w/ Inline Table-valued Function
Apply operator also allows us to use an inline table-valued function as a table expression in the right side. So if we put the right table expression into a table-valued function, we could make our CROSS APPLY query a lot simpler.

(1) Create a table-valued function

   IF OBJECT_ID(N'Production.', N'IF') IS NOT NULL
        DROP FUNCTION Production.GetTop2Product;
   GO

   CREATE FUNCTION Production.GetTop2Product(@supplierid AS INT)
         RETURNS TABLE
   AS
        RETURN

       Select productid, productname, unitprice  
           from Production.Products p
           where p.supplierid = @supplierid
           order by unitprice, productid
           offset 0 rows fetch first 2 rows only
   GO

(2) Re-write the query

Select  S.Supplierid, S.CompanyName as supplier, A.*
     From Production.Suppliers s
             CROSS APPLY Production.GetTop2Product(s.Supplierid) as A

-------------------------------------------------------------------------------------------------------------
Reference: Querying Microsoft SQL Server 2012 Training Kit by Itzik Ben-Gan, Dejan Sarka, Ron Talmage

No comments:

Post a Comment