GB.
SQL Server - Step by Step
Step 3 of 650% through series
  1. 3
  2. 4
  3. 5
  4. 6
2026-04-207 min read

CROSS APPLY vs OUTER APPLY: SQL Server's Secret Weapon

#SQL Server#SQL#CROSS APPLY#OUTER APPLY#Query Optimization#Database#T-SQL

CROSS APPLY vs OUTER APPLY: SQL Server's Secret Weapon for Real-World Queries

Most SQL developers are comfortable with:

JOIN
WHERE
GROUP BY
CTE

But there is another powerful SQL Server feature that is often overlooked:

CROSS APPLY
OUTER APPLY

The easiest way to understand APPLY is not by memorizing its syntax.

Start with a problem.


1. The Problem: Latest Order for Every Customer

Suppose we have:

Customers
----------------
CustomerId
Name

Orders
----------------
OrderId
CustomerId
OrderDate
Amount

We need:

Get every customer with their latest order.

A common solution is a correlated subquery:

SELECT
    c.CustomerId,
    c.Name,
    o.OrderId,
    o.OrderDate,
    o.Amount
FROM Customers c
JOIN Orders o
    ON o.CustomerId = c.CustomerId
WHERE o.OrderDate = (
    SELECT MAX(OrderDate)
    FROM Orders
    WHERE CustomerId = c.CustomerId
);

It works, but the query becomes more complicated as the requirement grows.

For example:

Get the latest order, including its OrderId, Amount, Status, and other columns.

This is where APPLY becomes interesting.


2. CROSS APPLY

The easiest mental model is:

For each row on the left, execute the right-side query.

SELECT
    c.CustomerId,
    c.Name,
    o.OrderId,
    o.OrderDate,
    o.Amount
FROM Customers c
CROSS APPLY
(
    SELECT TOP 1
        OrderId,
        OrderDate,
        Amount
    FROM Orders o
    WHERE o.CustomerId = c.CustomerId
    ORDER BY OrderDate DESC, OrderId DESC
) o;

Think of it like this:

Customer 1
    |
    +--> Find latest order
    |
    +--> Return it

Customer 2
    |
    +--> Find latest order
    |
    +--> Return it

Customer 3
    |
    +--> Find latest order
    |
    +--> Return it

The right-side query can directly reference the current row from the left side:

WHERE o.CustomerId = c.CustomerId

That correlation is what makes APPLY useful.


3. Why Not Just Use JOIN?

JOIN is excellent when you're matching sets of rows.

But consider this requirement:

Get the 3 latest orders for every customer.

With APPLY:

SELECT
    c.CustomerId,
    c.Name,
    o.OrderId,
    o.OrderDate,
    o.Amount
FROM Customers c
CROSS APPLY
(
    SELECT TOP 3
        OrderId,
        OrderDate,
        Amount
    FROM Orders o
    WHERE o.CustomerId = c.CustomerId
    ORDER BY OrderDate DESC
) o;

The logic is almost exactly how you would describe the requirement:

For each customer
       ↓
Find TOP 3 orders
       ↓
Order by latest
       ↓
Return them

This is one of the most practical uses of CROSS APPLY.


4. CROSS APPLY vs OUTER APPLY

This is the most important difference.

Suppose we have:

Customer 1 → Orders
Customer 2 → Orders
Customer 3 → No Orders

With CROSS APPLY:

SELECT
    c.CustomerId,
    c.Name,
    o.OrderId
FROM Customers c
CROSS APPLY
(
    SELECT TOP 1 *
    FROM Orders o
    WHERE o.CustomerId = c.CustomerId
    ORDER BY OrderDate DESC
) o;

Customer 3 disappears because the right-side query returned no rows.

Conceptually:

CROSS APPLY

Customer 1 → Order
Customer 2 → Order
Customer 3 → Not returned

Now change it to:

OUTER APPLY
SELECT
    c.CustomerId,
    c.Name,
    o.OrderId
FROM Customers c
OUTER APPLY
(
    SELECT TOP 1 *
    FROM Orders o
    WHERE o.CustomerId = c.CustomerId
    ORDER BY OrderDate DESC
) o;

Now:

OUTER APPLY

Customer 1 → Order
Customer 2 → Order
Customer 3 → NULL

The easiest way to remember:

CROSS APPLY
    ≈ INNER JOIN behavior

OUTER APPLY
    ≈ LEFT JOIN behavior

5. A More Practical Example: Top Employees

Suppose we have:

Departments
----------------
DepartmentId
Name

Employees
----------------
EmployeeId
Name
DepartmentId
Salary

Requirement:

Get the top 2 highest-paid employees from every department.

SELECT
    d.DepartmentId,
    d.Name AS DepartmentName,
    e.EmployeeId,
    e.Name,
    e.Salary
FROM Departments d
CROSS APPLY
(
    SELECT TOP 2
        EmployeeId,
        Name,
        Salary
    FROM Employees e
    WHERE e.DepartmentId = d.DepartmentId
    ORDER BY Salary DESC
) e;

Again, the query reads naturally:

For each department
       ↓
Find TOP 2 employees
       ↓
Order by salary
       ↓
Return them

This pattern appears frequently in real applications.


6. APPLY with Table-Valued Functions

APPLY is also useful with table-valued functions.

Suppose we have:

dbo.GetCustomerOrders(CustomerId)

which returns orders for a customer.

We can write:

SELECT
    c.CustomerId,
    c.Name,
    o.OrderId,
    o.Amount
FROM Customers c
CROSS APPLY
    dbo.GetCustomerOrders(c.CustomerId) o;

The function receives the current customer's ID.

Conceptually:

Customer 1 → GetCustomerOrders(1)
Customer 2 → GetCustomerOrders(2)
Customer 3 → GetCustomerOrders(3)

This is another situation where APPLY provides a natural way to express a correlated operation.


7. The Performance Question

You might be tempted to say:

"CROSS APPLY is faster than JOIN."

Don't.

That's not generally true.

APPLY is a query pattern, not a guaranteed performance optimization.

Performance depends on:

Query
  ↓
Indexes
  ↓
Data distribution
  ↓
Execution plan
  ↓
Actual workload

For example, our latest-order query can benefit from an index such as:

CREATE INDEX IX_Orders_CustomerId_OrderDate
ON Orders(CustomerId, OrderDate DESC)
INCLUDE (Amount);

This can help SQL Server efficiently locate the relevant orders for each customer.

But whether it actually improves performance should be verified using the execution plan and measurements.


8. When Should You Think About APPLY?

When you see requirements like:

For each customer...
    find the latest order

For each department...
    find the top 3 employees

For each product...
    find the latest price

For each account...
    find the most recent transaction

stop and consider:

CROSS APPLY

or:

OUTER APPLY

The pattern is especially useful when the right-side query needs to reference the current row from the left side.


9. JOIN vs APPLY

A simple mental model:

JOIN
  ↓
Match sets of rows

while:

APPLY
  ↓
For each left-side row,
run a correlated operation
on the right side

And:

CROSS APPLY
  ↓
No right-side result?
Left row is removed.
OUTER APPLY
  ↓
No right-side result?
Keep the left row with NULLs.

Final Takeaway

CROSS APPLY and OUTER APPLY are not replacements for JOIN.

They solve a different kind of problem.

When the requirement sounds like:

"For each row on the left, find something from the right."

APPLY should come to mind.

The three patterns worth remembering are:

Latest record per group
        ↓
CROSS APPLY + TOP 1
Top N per group
        ↓
CROSS APPLY + TOP N
Keep the parent even when
there is no matching child
        ↓
OUTER APPLY

And just like any SQL optimization:

Don't assume APPLY is faster. Check the execution plan, indexes, and actual workload.

The real power of APPLY is that it lets you express row-by-row correlated logic in a clean, set-based SQL query.

Enjoyed this article? Share it with your network!