SQL Server’s CROSS APPLY and how to use it
CROSS APPLY is a beautiful alternative for your FROM clause. It isn’t JOINed to anything but it can be correlated to other fields in your query. It also allows for an aggregate to be run on a row-by-row basis. CROSS APPLY can be very useful, and the following will show you what it is and how you can start using it today.
CROSS APPLY is used instead of a JOIN when you want to use a correlated query. The results of a CROSS APPLY will be applied to every row. CROSS APPLYs are perfect for when you want to include an aggregate of data (MAX, MIN, COUNT, etc.) with the results pertaining to each row. It can be correlated inside the CROSS APPLY query, making it handy to refer to specific data elements. You can also not correlate it, so that way the query results will appear on every row without having to JOIN it to something.
In this first example, I just want the number of rows in the table to appear as a field in every row. It could be done in the SELECT clause, but this is just a very simple example that can actually be very powerful when used in larger queries.
DECLARE @test TABLE (ID int IDENTITY(1,1), val CHAR(1)); INSERT INTO @test (val) VALUES ('A'),('B'),('C'),('D'); SELECT * FROM @test SELECT * FROM @test t CROSS APPLY ( SELECT COUNT(*) cnt FROM @test ) cnt;
The results would be as follows:
SELECT * FROM @test
ID | val |
1 | A |
2 | B |
3 | C |
4 | D |
SELECT * FROM @test with the CROSS APPLY added:
ID | val | cnt |
1 | A | 4 |
2 | B | 4 |
3 | C | 4 |
4 | D | 4 |
A correlated CROSS APPLY can give you aggregated information based on data for each row. This time, I’m going to add values A – D again, for a total of 8 rows. Notice the “mx” CROSS APPLY that has been added. This will answer the question, “What is the highest ID per val in my table?”
DECLARE @test TABLE (ID int IDENTITY(1,1), val CHAR(1)); INSERT INTO @test (val) VALUES ('A'),('B'),('C'),('D'), ('A'),('B'),('C'),('D'); SELECT * FROM @test SELECT * FROM @test t CROSS APPLY ( SELECT COUNT(*) cnt FROM @test ) cnt CROSS APPLY ( SELECT MAX(ID) maxid FROM @test t2 WHERE t.val = t2.val ) mx;
My first SELECT * FROM @test looks just like the first one, except four more rows are added: rows 5 – 8.
ID | val |
1 | A |
2 | B |
3 | C |
4 | D |
5 | A |
6 | B |
7 | C |
8 | D |
My new results answer my question about the Max ID. I want to see that for A it is 5, for B it is 6, for C it is 7, and for D it is 8. In the CROSS APPLY, I am correlating on val and I am SELECTing MAX(ID). So, my results show me that MAX(ID) result for each val below.
ID | val | cnt | maxid |
1 | A | 8 | 5 |
2 | B | 8 | 6 |
3 | C | 8 | 7 |
4 | D | 8 | 8 |
5 | A | 8 | 5 |
6 | B | 8 | 6 |
7 | C | 8 | 7 |
8 | D | 8 | 8 |
Here’s a more practical example. Sometimes, we want to know the first or last date something happened. CROSS APPLY can help with this, as well. Building onto our current example, DATETIMEs are added to the @test table. The CROSS APPLY called fldt gets the first and last dates.
DECLARE @test TABLE (ID int IDENTITY(1,1), val CHAR(1), dt DATETIME); INSERT INTO @test (val, dt) VALUES ('A','2018-01-04 13:04:00'), ('B','2018-01-05 15:04:00'), ('C','2018-01-06 05:33:00'), ('D','2018-01-07 08:22:00'), ('A','2018-01-08 11:02:00'), ('B','2018-01-09 12:12:00'), ('C','2018-01-10 19:05:00'), ('D','2018-01-11 21:21:00'); SELECT * FROM @test SELECT * FROM @test t CROSS APPLY ( SELECT COUNT(*) cnt FROM @test ) cnt CROSS APPLY ( SELECT MAX(ID) maxid FROM @test t2 WHERE t.val = t2.val ) mx CROSS APPLY ( SELECT MIN(dt) minDt, MAX(dt) maxDt FROM @test t2 WHERE t.val = t2.val ) fldt;
The results are as follows. Notice for val A, the minDt is the dt value given in row 1. The maxDt is the dt value given in row 5.
ID | val | dt | cnt | maxid | minDt | maxDt |
1 | A | 01/04/2018 13:04:00 | 8 | 5 | 01/04/2018 13:04:00 | 01/08/2018 11:02:00 |
2 | B | 01/05/2018 15:04:00 | 8 | 6 | 01/05/2018 15:04:00 | 01/09/2018 12:12:00 |
3 | C | 01/06/2018 05:33:00 | 8 | 7 | 01/06/2018 05:33:00 | 01/10/2018 19:05:00 |
4 | D | 01/07/2018 08:22:00 | 8 | 8 | 01/07/2018 08:22:00 | 01/11/2018 21:21:00 |
5 | A | 01/08/2018 11:02:00 | 8 | 5 | 01/04/2018 13:04:00 | 01/08/2018 11:02:00 |
6 | B | 01/09/2018 12:12:00 | 8 | 6 | 01/05/2018 15:04:00 | 01/09/2018 12:12:00 |
7 | C | 01/10/2018 19:05:00 | 8 | 7 | 01/06/2018 05:33:00 | 01/10/2018 19:05:00 |
8 | D | 01/11/2018 21:21:00 | 8 | 8 | 01/07/2018 08:22:00 | 01/11/2018 21:21:00 |
While the first and last dates can be accomplished with window functions, as well, the CROSS APPLY allows you to do more with the results. Window functions are a discussion for another day.
So, that’s CROSS APPLY – stick that in your toolbox! Let me know if you have any questions or tips in the comments.