Search code examples
sql-serversql-server-2008left-joinouter-apply

Where to use Outer Apply


MASTER TABLE

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x

DETAILS TABLE

x------x--------------------x-------x
| Id   |      PERIOD        |   QTY |
x------x--------------------x-------x
|  1   |   2014-01-13       |   10  |
|  1   |   2014-01-11       |   15  |
|  1   |   2014-01-12       |   20  |
|  2   |   2014-01-06       |   30  |
|  2   |   2014-01-08       |   40  |
x------x--------------------x-------x

I am getting the same results when LEFT JOIN and OUTER APPLY is used.

LEFT JOIN

SELECT T1.ID,T1.NAME,T2.PERIOD,T2.QTY 
FROM MASTER T1
LEFT JOIN DETAILS T2 ON T1.ID=T2.ID

OUTER APPLY

SELECT T1.ID,T1.NAME,TAB.PERIOD,TAB.QTY 
FROM MASTER T1
OUTER APPLY
(
   SELECT ID,PERIOD,QTY 
   FROM DETAILS T2
   WHERE T1.ID=T2.ID
)TAB

Where should I use LEFT JOIN AND where should I use OUTER APPLY


Solution

  • A LEFT JOIN should be replaced with OUTER APPLY in the following situations.

    1. If we want to join two tables based on TOP n results

    Consider if we need to select Id and Name from Master and last two dates for each Id from Details table.

    SELECT M.ID,M.NAME,D.PERIOD,D.QTY
    FROM MASTER M
    LEFT JOIN
    (
        SELECT TOP 2 ID, PERIOD,QTY 
        FROM DETAILS D  
        ORDER BY CAST(PERIOD AS DATE)DESC
    )D
    ON M.ID=D.ID
    

    which forms the following result

    x------x---------x--------------x-------x
    |  Id  |   Name  |   PERIOD     |  QTY  |
    x------x---------x--------------x-------x
    |   1  |   A     | 2014-01-13   |  10   |
    |   1  |   A     | 2014-01-12   |  20   |
    |   2  |   B     |   NULL       |  NULL |
    |   3  |   C     |   NULL       |  NULL |
    x------x---------x--------------x-------x
    

    This will bring wrong results ie, it will bring only latest two dates data from Details table irrespective of Id even though we join with Id. So the proper solution is using OUTER APPLY.

    SELECT M.ID,M.NAME,D.PERIOD,D.QTY
    FROM MASTER M
    OUTER APPLY
    (
        SELECT TOP 2 ID, PERIOD,QTY 
        FROM DETAILS D  
        WHERE M.ID=D.ID
        ORDER BY CAST(PERIOD AS DATE)DESC
    )D
    

    Here is the working : In LEFT JOIN , TOP 2 dates will be joined to the MASTER only after executing the query inside derived table D. In OUTER APPLY, it uses joining WHERE M.ID=D.ID inside the OUTER APPLY, so that each ID in Master will be joined with TOP 2 dates which will bring the following result.

    x------x---------x--------------x-------x
    |  Id  |   Name  |   PERIOD     |  QTY  |
    x------x---------x--------------x-------x
    |   1  |   A     | 2014-01-13   |  10   |
    |   1  |   A     | 2014-01-12   |  20   |
    |   2  |   B     | 2014-01-08   |  40   |
    |   2  |   B     | 2014-01-06   |  30   |
    |   3  |   C     |   NULL       |  NULL |
    x------x---------x--------------x-------x
    

    2. When we need LEFT JOIN functionality using functions.

    OUTER APPLY can be used as a replacement with LEFT JOIN when we need to get result from Master table and a function.

    SELECT M.ID,M.NAME,C.PERIOD,C.QTY
    FROM MASTER M
    OUTER APPLY dbo.FnGetQty(M.ID) C
    

    And the function goes here.

    CREATE FUNCTION FnGetQty 
    (   
        @Id INT 
    )
    RETURNS TABLE 
    AS
    RETURN 
    (
        SELECT ID,PERIOD,QTY 
        FROM DETAILS
        WHERE ID=@Id
    )
    

    which generated the following result

    x------x---------x--------------x-------x
    |  Id  |   Name  |   PERIOD     |  QTY  |
    x------x---------x--------------x-------x
    |   1  |   A     | 2014-01-13   |  10   |
    |   1  |   A     | 2014-01-11   |  15   |
    |   1  |   A     | 2014-01-12   |  20   |
    |   2  |   B     | 2014-01-06   |  30   |
    |   2  |   B     | 2014-01-08   |  40   |
    |   3  |   C     |   NULL       |  NULL |
    x------x---------x--------------x-------x
    

    3. Retain NULL values when unpivoting

    Consider you have the below table

    x------x-------------x--------------x
    |  Id  |   FROMDATE  |   TODATE     |
    x------x-------------x--------------x
    |   1  |  2014-01-11 | 2014-01-13   | 
    |   1  |  2014-02-23 | 2014-02-27   | 
    |   2  |  2014-05-06 | 2014-05-30   |    
    |   3  |   NULL      |   NULL       | 
    x------x-------------x--------------x
    

    When you use UNPIVOT to bring FROMDATE AND TODATE to one column, it will eliminate NULL values by default.

    SELECT ID,DATES
    FROM MYTABLE
    UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P
    

    which generates the below result. Note that we have missed the record of Id number 3

      x------x-------------x
      | Id   |    DATES    |
      x------x-------------x
      |  1   |  2014-01-11 |
      |  1   |  2014-01-13 |
      |  1   |  2014-02-23 |
      |  1   |  2014-02-27 |
      |  2   |  2014-05-06 |
      |  2   |  2014-05-30 |
      x------x-------------x
    

    In such cases an APPLY can be used(either CROSS APPLY or OUTER APPLY, which is interchangeable).

    SELECT DISTINCT ID,DATES
    FROM MYTABLE 
    OUTER APPLY(VALUES (FROMDATE),(TODATE))
    COLUMNNAMES(DATES)
    

    which forms the following result and retains Id where its value is 3

      x------x-------------x
      | Id   |    DATES    |
      x------x-------------x
      |  1   |  2014-01-11 |
      |  1   |  2014-01-13 |
      |  1   |  2014-02-23 |
      |  1   |  2014-02-27 |
      |  2   |  2014-05-06 |
      |  2   |  2014-05-30 |
      |  3   |     NULL    |
      x------x-------------x