Примеры использования CROSS APPLY
Для начала давайте определимся с исходными данными, допустим, у нас есть таблица товаров (Products) и таблица продаж (Sales) вот с такими данными:
А также у нас есть функция (FT_GET_Sales), которая просто выводит список продаж по идентификатору товара.
Пример ее работы:
Пример использования CROSS APPLY с табличной функцией
Теперь допустим, нам необходимо получить продажи не только одного товара, но и других, для этого мы можем использовать оператор CROSS APPLY
Где как Вы понимаете, табличная функция FT_GET_Sales была вызвана для каждой строки таблицы Products.
Пример использования CROSS APPLY с подзапросом
Как уже было сказано выше, CROSS APPLY можно использовать и с подзапросом, для примера давайте допустим, что нам нужно получить последнюю дату продажи каждого товара.
Как видим, после CROSS APPLY у нас идет подзапрос.
APPLY with TOP Command
- SELECT * FROM
- (SELECT PRO.*,EMP.Emp_Name,EMP.Emp_Id,EMP.Manager_Id,ROW_NUMBER()OVER(Partition By PRO.Project_Id OrderBy PRO.Project_Id)as Rank FROM dbo.Project AS PRO
- LEFTOUTERJOIN
- dbo.Employee AS EMP
- ON
- PRO.Project_Id=EMP.Project_Id)Tab1
- WHERE Tab1.Rank<=2
Output
Above query return top Employee details for each project. We can perform same operation using APPLY.
- SELECT PRO.*,Tab.* FROM dbo.Project AS PRO
- OUTER APPLY(SELECTTOP 2 EMP.Emp_Name,EMP.Emp_Id,EMP.Manager_Id Fromd bo.Employee AS EMP WHERE EMP.Project_Id=PRO.Project_Id)Tab
Above query produce same result as previous query.
Example 3
- DECLARE @Tab TABLE
- (
- (max),
- City (max)
- )
- INSERTINTO @Tab
- SELECT’Rajasthan’,’Alwar,Laipur,Ajmer,Kota’UNIONALL
- SELECT’Haryana’,’Hisar,Jhajjar,Rohtak’UNIONALL
- SELECT’Maharaster’,’Mumbai,Pune’
- SELECT * FROM @Tab AS
Output
Suppose we have a table that contain State name and City names but city names are stored in comma separated manner, now we want to split each city name. For this we can use APPLY Method as below.
For this we create a function that split city name and return a table that contain the list of city names.
- ALTER FUNCTION SplitString(@Input NVARCHAR(MAX))
- RETURNS @OutputTABLE(City NVARCHAR(1000))
- AS
- BEGIN
- DECLARE @Indexint;
- SET @Input = @Input + ‘,’;
- WHILE(LEN(@Input) > 0)
- BEGIN
- SET @Index = CHARINDEX(‘,’, @Input);
- INSERT INTO @Output(City)
- VALUES(SUBSTRING(@Input, 0, @Index))
- SET @Input = SUBSTRING(@Input, @Index + 1, LEN(@Input));
- END
- RETURN
- END
Now we use this function to split the city name.
- DECLARE @Tab TABLE
- (
- (max),
- City (max)
- )
- INSERTINTO @Tab
- SELECT’Rajasthan’,’Alwar,Laipur,Ajmer,Kota’UNIONALL
- SELECT’Haryana’,’Hisar,Jhajjar,Rohtak’UNIONALL
- SELECT’Maharaster’,’Mumbai,Pune’
- SELECT T.State,Tab.*FROM @Tab AS T
- OUTERAPPLY dbo.SplitString(T.City)Tab
Output
Thanks for reading the article.
Other Notes
As I told you before there are certain scenarios where a query with
an APPLY operator performs better than a query with regular joins. I am not going
to delve into much details rather here are some articles that discuss this
functionality
in greater detail.
-
INNER JOINS vs CROSS APPLY
-
Using CROSS APPLY to optimize joins on BETWEEN conditions
Please note, the APPLY operator is not an ANSI operator but rather an
extension of SQL Server T-SQL (available in SQL Server 2005 and later), so if you
plan to port your database to some other DBMS take this into consideration.
Next Steps
- Review
Dynamic Management
Views\Functions tips. - Review
Splitting Delimited Strings
Using XML in SQL Server tip. - Review
SQL Server Monitoring
Scripts with the DMVs tip. - Review
Collecting Query Statistics
for SQL Server 2005 tip. -
Review
SQL Server Cursor Example tip.
-
Review
UNION vs. UNION ALL in SQL Server tip.
Related Articles
Compare SQL Server Datasets with INTERSECT and EXCEPT
UNION vs. UNION ALL in SQL Server
Join SQL Server tables where columns include NULL values
SQL Server Join Example
Compare SQL Server Datasets with INTERSECT and EXCEPT
UNION vs. UNION ALL in SQL Server
Join SQL Server tables where columns include NULL values
Using CROSS JOIN queries to find records out of sequence
SQL Server Join Example
Calculate Running Totals Using SQL Server CROSS JOINs
Handling cross database joins that have different SQL Server collations
Understanding SQL Server Physical Joins
SQL Server JOIN Hints
SQL Server Operations from Set Theory
How to Join to the Same Table Multiple Times for a SQL Server query
Joining SQL Server tables using large character type columns
SQL LEFT JOIN Examples
Getting Started with SQL INNER JOIN
SQL RIGHT JOIN Examples
Learn SQL FULL OUTER JOIN with Examples
Popular Articles
Date and Time Conversions Using SQL Server
Format SQL Server Dates with FORMAT Function
SQL Server Cursor Example
Rolling up multiple rows into a single row and column for SQL Server data
SQL Server DROP TABLE IF EXISTS Examples
How to tell what SQL Server versions you are running
Add and Subtract Dates using DATEADD in SQL Server
Using MERGE in SQL Server to insert, update and delete at the same time
SQL Server Loop through Table Rows without Cursor
Resolving could not open a connection to SQL Server errors
SQL NOT IN Operator
SQL Convert Date to YYYYMMDD
Concatenate SQL Server Columns into a String with CONCAT()
SQL Server Row Count for all Tables in a Database
Ways to compare and find differences for SQL Server tables and data
How to Get Current Date in SQL Server
Execute Dynamic SQL commands in SQL Server
SQL Server Database Stuck in Restoring State
Searching and finding a string value in all columns in a SQL Server table
CROSS APPLY в T-SQL
CROSS APPLY – это тип оператора APPLY, который позволяет вызывать табличную функцию для каждой строки внешнего табличного выражения. Вместо табличной функции можно также использовать и вложенный запрос, который возвращает производную таблицу.
Есть еще и другой тип OUTER APPLY, он в отличие от CROSS APPLY возвращает и строки, которые формируют результирующий набор, и те, которые этого не делают, т.е. со значениями NULL в столбцах. Например, табличная функция может не возвращать никаких данных для определенных значений, CROSS APPLY в таких случаях подобные строки не выводит, а OUTER APPLY выводит.
Единичный элемент
В этом пункте определения группы нужно обратить внимание на то, что единичный (нейтральный) элемент группы всегда коммутирует с любым элементом множества: AE=EA, даже если группа
некоммутативная. Обратите внимание, что единичные элементы группы на одном и том же множестве могут быть разными для разных операций
Ведь это, по существу, разные группы. Например, для множества вещественных чисел
операция сложения дает группу с единичным элементом равным 0. А операция умножения на том же множестве (без нуля, естественно) дает группу с единичным элементом равным 1
Обратите внимание, что единичные элементы группы на одном и том же множестве могут быть разными для разных операций. Ведь это, по существу, разные группы
Например, для множества вещественных чисел
операция сложения дает группу с единичным элементом равным 0. А операция умножения на том же множестве (без нуля, естественно) дает группу с единичным элементом равным 1.
Еще примеры единичных элементов группы. Если группу образуют матрицы размера nxm с операцией сложения матриц, то единичным элементом группы будет нулевая матрица размера nxm. Если группу
образуют квадратные матрицы размера nxn с операцией умножения матриц, то единичным элементом группы будет матрица размера nxn, с единицами по диагонали и нулями во всех других местах.
Еще обратим внимание на то, что самая маленькая группа состоит только из одного элемента. И этим единственным элементом группы как раз и является единичный элемент
Именно в таких группах унарные
замкнутые операции можно формально считать бинарными замкнутыми операциями. Выше был дан пример такой группы из одной симметричной матрицы с групповой операцией транспонирования матрицы.
Example of Outer Apply
The Outer Apply works in a similar way to Cross Apply, but uses the feature of an Outer Join to show NULL values where no matches are found.
Using our earlier example of products and manufacturers, we can add a new manufacturer that has no products.
Here’s our query with Cross Apply.
This will show all manufacturers and products that are related.
manufacturer_id | manufacturer_name | product_id | product_name | manufacturer_id |
1 | Jims Furniture | 1 | Chair | 1 |
1 | Jims Furniture | 2 | Table | 1 |
1 | Jims Furniture | 5 | Dining Table | 1 |
2 | Luxury Construction | 3 | Couch | 2 |
2 | Luxury Construction | 4 | Desk | 2 |
What about the new manufacturer with no products?
If we want to see this, we have to use an Outer Apply.
The results are:
manufacturer_id | manufacturer_name | product_id | product_name | manufacturer_id |
1 | Jims Furniture | 1 | Chair | 1 |
1 | Jims Furniture | 2 | Table | 1 |
1 | Jims Furniture | 5 | Dining Table | 1 |
2 | Luxury Construction | 3 | Couch | 2 |
2 | Luxury Construction | 4 | Desk | 2 |
3 | XYZ Construction | NULL | NULL | NULL |
We can see that the last row shows the new manufacturer, but NULL values for the product columns.
Joining table valued functions and tables using APPLY operators
In Script #4, I am creating a table-valued function which accepts DepartmentID as
its parameter and returns all the employees who belong to this department. The next
query selects data from the Department table and uses a CROSS APPLY to join with the function
we created. It passes the DepartmentID for each row from the outer table expression
(in our case Department table) and evaluates the function for each row similar to
a
correlated subquery. The next
query uses the OUTER APPLY in place of the CROSS APPLY and hence unlike the CROSS APPLY
which returned only correlated data, the OUTER APPLY returns non-correlated data
as well, placing NULLs into the missing columns.
--Script #4 - APPLY with table-valued function IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'') AND type IN (N'IF')) BEGIN DROP FUNCTION dbo.fn_GetAllEmployeeOfADepartment END GO CREATE FUNCTION dbo.fn_GetAllEmployeeOfADepartment(@DeptID AS INT) RETURNS TABLE AS RETURN ( SELECT * FROM Employee E WHERE E.DepartmentID = @DeptID ) GO SELECT * FROM Department D CROSS APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID) GO SELECT * FROM Department D OUTER APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID) GO
You might be wondering if we can use a
simple join in place of the above
queries, the answer is NO. If you replace the CROSS/OUTER APPLY in the above queries
with an INNER JOIN/LEFT OUTER JOIN, specifying the ON clause with 1=1 and run the
query, you will get the error «The multi-part identifier «D.DepartmentID» could not be bound.». This is because with JOINs the execution context of
the outer query is different
from the execution context of the function (or a
derived table), and you cannot
bind a value/variable from the outer query to the function as a parameter.
Hence the APPLY operator is required for such queries.
So in summary the APPLY operator is required when you have to use
a table-valued function in the query, but it can also be used with inline SELECT
statements.
Пример использования OUTER APPLY
Если Вы заметили, у нас в исходных данных есть позиция «Клавиатура», по которой не было продаж. И в случае возникновения необходимости получить последнюю дату продажи включая товары, по которым не было продаж, т.е. выводить NULL значения, чтобы мы видели, какие товары не продавались вообще, тип CROSS APPLY можно заменить на OUTER APPLY.
Пример:
Теперь видно, что некоторые товары продавались и последняя дата их продажи такая то, а некоторые товары не продавались, т.е. у них отсутствует дата продажи.
Оператор CROSS APPLY появился в Cистема управления реляционными базами данных (СУБД), разработанная корпорацией Microsoft. Язык структурированных запросов) — универсальный компьютерный язык, применяемый для создания, модификации и управления данными в реляционных базах данных. SQL Server 2005. Он позволяет выполнить соединение двух табличных выражений. При этом каждая строка из левой таблицы сочетается с каждой строкой из правой.
Давайте попробуем разобраться в том, какие преимущества дает нам использование этого нестандартного оператора.
Мы получили просто декартово произведение таблиц Product и Laptop. Аналогичный результат мы можем получить с помощью следующих стандартных запросов:
Поставим теперь более осмысленную задачу.
Для каждого ноутбука дополнительно вывести имя производителя.
Эту задачу мы можем решить с помощью обычного соединения:
С помощью CROSS APPLY решение этой же задачи можно написать так:
«И что тут нового»? — спросите вы. Запрос стал даже более громоздким. Пока да, можно согласиться. Но уже здесь можно заметить весьма важную вещь, которая отличает CROSS APPLY от других видов соединений. А именно, мы используем коррелирующий подзапрос в предложении FROM, передавая в него значения из левого табличного выражения. В данном примере это значения из столбца P.model. Т.е. для каждой строки из левой таблицы правая таблица будет своя.
Поняв это, мы можем воспользоваться данными преимуществами. Рассмотрим следующую задачу.
Для каждого ноутбука дополнительно вывести максимальную цену среди ноутбуков того же производителя.
Эту задачу мы можем решить с помощью коррелирующего подзапроса в предложении SELECT:
Пока решение, использующее CROSS APPLY, будет мало чем отличаться от вышеприведенного:
Usage 1: Joining a Table to a Table-Valued Function
Let’s see an example using the product table above, but with some modifications so that we can store the manufacturer of each product as well.
Sample Data
Here’s the script for creating and populating the tables. Notice that the product table now has a foreign key to the manufacturer table, and there is no active column.
Here’s our updated table function. We provide the manufacturer_id as a parameter, and products are returned that match this value.
Let’s select from this function to see what it shows.
The results are:
product_id | product_name | manufacturer_id |
1 | Chair | 1 |
2 | Table | 1 |
5 | Dining Table | 1 |
Try an Inner Join
Let’s say we have this function, but we want to see manufacturer details as well.
We could try this by selecting from the manufacturer table and joining to the results of this function. This function is a table-valued function, which returns a table, so we should be able to join to it, right?
Let’s try it.
This query joins the manufacturer table to the function based on the manufacturer_id.
If we run this query, you’ll get an error.
This error happens because the JOIN feature does not allow you to join to a table-valued function.
So how can you do this? How can you get the results of a table-valued function and include them in your query?
Use Cross Apply
We can combine the results of a table-valued function and other tables in a query using the Cross Apply feature.
It looks similar to a join. Here’s what our example would look like:
This will join the manufacturer table to the results of the GetProductsForManufacturer column. We can provide the parameter to the function and run it without errors.
This is what we’ll get when we run this query:
manufacturer_id | manufacturer_name | product_id | product_name | manufacturer_id |
1 | Jims Furniture | 1 | Chair | 1 |
1 | Jims Furniture | 2 | Table | 1 |
1 | Jims Furniture | 5 | Dining Table | 1 |
2 | Luxury Construction | 3 | Couch | 2 |
2 | Luxury Construction | 4 | Desk | 2 |
This is a simple example, and you can do the same thing without this function, but it shows that using a table-valued function and a Select query with Cross Apply can give you the result you need.
Использование подзапросов в условии соединения
Рекомендации
Не следует использовать подзапросы в условии соединения. Это может привести к значительному замедлению запроса и (в отдельных случаях) к его полной неработоспособности на некоторых СУБД. Пример запроса с использованием подзапроса в условии соединения:
ЗапросТекст
В данном случае подзапрос в условии соединения используется для получения как бы «среза последних» на конец предыдущего периода. Причем, для каждой номенклатуры период может быть разным. Подобный запрос рекомендуется переписать с использованием временных таблиц. Например, это можно сделать следующим образом:
пример
Применить будет использоваться, когда функция table value в правильном выражении.
создайте таблицу отдела для хранения информации о департаментах. Затем создайте таблицу Employee, в которой хранятся сведения о сотрудниках
Обратите внимание: каждый сотрудник принадлежит отделу, поэтому таблица Employee имеет ссылочную целостность с таблицей Департамента
Первый запрос выбирает данные из таблицы Department и использует CROSS APPLY для оценки таблицы Employee для каждой записи таблицы Department. Второй запрос просто присоединяется к таблице Департамента с таблицей Employee, и все соответствующие записи создаются.
Если вы посмотрите на результаты, которые они произвели, это точно такой же результат; Чем он отличается от JOIN и как он помогает в написании более эффективных запросов.
Первый запрос в сценарии # 2 выбирает данные из таблицы Department и использует OUTER APPLY для оценки таблицы Employee для каждой записи таблицы Department. Для тех строк, для которых нет совпадений в таблице Employee, эти строки содержат значения NULL, как вы можете видеть в случае строк 5 и 6. Второй запрос просто использует LEFT OUTER JOIN между таблицей Департамента и таблицей Employee. Как и ожидалось, запрос возвращает все строки из таблицы Department; даже для тех строк, для которых нет совпадений в таблице Employee.
Несмотря на то, что вышеупомянутые два запроса возвращают одну и ту же информацию, план выполнения будет бит другим. Но стоить разумно, не будет большой разницы.
Теперь настало время посмотреть, где действительно нужен оператор APPLY. В сценарии №3 я создаю табличную функцию, которая принимает параметр DepartmentID как параметр и возвращает всех сотрудников, принадлежащих этому отделу. Следующий запрос выбирает данные из таблицы Department и использует CROSS APPLY для объединения с созданной нами функцией. Он передает идентификатор отдела для каждой строки из выражения внешней таблицы (в нашем случае таблица Департамента) и оценивает функцию для каждой строки, подобной коррелированному подзапросу. Следующий запрос использует OUTER APPLY вместо CROSS APPLY и, следовательно, в отличие от CROSS APPLY, который возвращает только коррелированные данные, OUTER APPLY также возвращает некоррелированные данные, помещая NULL в отсутствующие столбцы.
SQL Server CROSS APPLY vs INNER JOIN example
The first query in Script #2 selects data from the Department table and
uses a CROSS APPLY to evaluate the Employee table for each record of the Department
table. The second query simply joins the Department table with the Employee table and
all matching records are produced.
--Script #2 - CROSS APPLY and INNER JOIN SELECT * FROM Department D CROSS APPLY ( SELECT * FROM Employee E WHERE E.DepartmentID = D.DepartmentID ) A GO SELECT * FROM Department D INNER JOIN Employee E ON D.DepartmentID = E.DepartmentID GO
If you look at the results, you can see see they are the same.
Also, the execution plans for these queries are similar and they have an
equal query cost, as you can see in the image below.
So, why use the APPLY operator? How does it differ from a JOIN and how
does it help in writing more efficient queries? I will discuss this later.
Preparing dummy data
First, let’s create a dummy database with some dummy records in it. We will use this dummy database to perform different operations throughout this article. As ever if you are trying things out on a live database be sure to check that you are fully backed up.
Execute the following script:
1 |
CREATEDATABASELibrary GO USELibrary; CREATETABLEAuthor ( idINTPRIMARYKEY, author_nameVARCHAR(50)NOTNULL, ) CREATETABLEBook ( idINTPRIMARYKEY, book_nameVARCHAR(50)NOTNULL, priceINTNOTNULL, author_idINTNOTNULL ) USELibrary; INSERTINTOAuthor (1,’Author1′), (2,’Author2′), (3,’Author3′), (4,’Author4′), (5,’Author5′), (6,’Author6′), (7,’Author7′) INSERTINTOBook (1,’Book1′,500,1), (2,’Book2′,300,2), (3,’Book3′,700,1), (4,’Book4′,400,3), (5,’Book5′,650,5), (6,’Book6′,400,3) |
In the script above we created a database named Library. The database has two tables: Author and Book. Book has an author_id column which contains values from the id column of the Author table. This means that there is a one to many relationships between the Author and Book columns.
Получение данных через точку от полей составного типа
Рекомендации
Если в запросе используется получение значения через точку от поля составного ссылочного типа, то при выполнении этого запроса будет выполняться соединение со всеми таблицами объектов, входящими в этот составной тип. В результате SQL текст запроса чрезвычайно усложняется, и при его выполнении оптимизатор СУБД может выбрать неоптимальный план. Это может привести к серьезным проблемам производительности и даже к неработоспособности запроса в отдельных случаях.
В частности, не рекомендуется обращаться к реквизитам регистратора регистра (например, «ТоварыНаСкладах.Регистратор.Дата») и т.п
При этом не важно в какой части запроса вы используете реквизит, полученный через точку от поля составного типа — в списке возвращаемых полей, в условии и т.п. Во всех случаях такое обращение может привести к проблемам производительности
Общая рекомендация заключается в том, чтобы по возможности ограничить количество соединений в таких запросах. Для этого можно использовать следующие приемы:
- Избегайте избыточности при создании полей составных ссылочных типов. Указывайте ровно столько возможных типов для данного поля, сколько необходимо. Не следует без необходимости использовать типы «любая ссылка» или «ссылка на любой документ» и т.п. Вместо этого следует более тщательно проанализировать прикладную логику и назначить для поля ровно те возможные типы ссылок, которые необходимы для решения задачи.
- При необходимости жертвуйте компактностью хранения данных ради производительности. Если в запросе вам понадобилось значение, полученное через ссылку, то, возможно, это значение можно хранить непосредственно в данном объекте. Например, если при работе с регистром вам требуется информация о дате регистратора, вы можете завести в регистре соответствующий реквизит и назначать ему значение при проведении документов. Это приведет к дублированию информации и некоторому (незначительному) увеличению ее объема, но может существенно повысить производительность и стабильность работы запроса.
- При необходимости жертвуйте компактностью и универсальностью кода ради производительности. Как правило, для выполнения конкретного запроса в данных условиях не нужны все возможные типы данной ссылки. В этом случае, следует ограничить количество возможных типов при помощи функции ВЫРАЗИТЬ. Если данный запрос является универсальным и используется в нескольких разных ситуациях (где типы ссылки могут быть разными), то можно формировать запрос динамически, подставляя в функцию ВЫРАЗИТЬ тот тип, который необходим при данных условиях. Это увеличит объем исходного кода и, возможно, сделает его менее универсальным, но может существенно повысить производительность и стабильность работы запроса.
Пример
В данном запросе используется обращение к реквизитам регистратора. Регистратор является полем составного типа, которое может принимать значения ссылки на один из 56 видов документов.
ЗапросТекст
SQL-текст этого запроса будет включать 56 левых соединений с таблицами документов. Это может привести к серьезным проблемам производительности при выполнении запроса. Однако, для решения данной конкретной задачи нет необходимости соединяться со всеми 56 видами документов. Условия запроса таковы, что при его выполнении будут выбраны только движения документов «РеализацияТоваровУслуг» и «ЗаказыПокупателя». В этом случае мы можем значительно ускорить работу запроса, ограничив количество соединений при помощи функции ВЫРАЗИТЬ().
ЗапросТекст
Этот запрос является более громоздким и, возможно, менее универсальным (он не будет правильно работать для других ситуаций — когда возможны другие значения типов регистратора). Однако, при его выполнении будет сформирован SQL запрос, который будет содержать всего два соединения с таблицами документов. Такой запрос будет работать значительно быстрее и стабильнее, чем запрос в его первоначальном виде.
Joining table valued system functions and tables using APPLY operators
Let me show you another query with a
Dynamic Management Function (DMF).
Script #5 returns all the currently executing user queries except for the queries
being executed by the current session. As you can see in the script below, the
sys.dm_exec_requests dynamic management
view is being CROSS APPLY’ed with the
sys.dm_exec_sql_text dynamic management
function which accepts a «plan handle» for the query and the «plan handle»
is being passed from the left/outer expression to the function to return
the data.
--Script #5 - APPLY with Dynamic Management Function (DMF) USE master GO SELECT DB_NAME(r.database_id) AS , st. AS FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) st WHERE r.session_Id > 50 -- Consider spids for users only, no system spids. AND r.session_Id NOT IN (@@SPID) -- Don't include request from current spid.
Note, for the above query, the column in the query returns all queries
submitted in a batch. If you want to see only the active (currently executing)
query you can use the statement_start_offset and statement_end_offset columns to trim
the active part of the query. Refer to this tip
How to isolate
the current running commands in SQL Server for a good example.
SQL Server APPLY operator has two variants; CROSS APPLY and OUTER APPLY
- The CROSS APPLY operator returns only those rows from the left table expression
(in its final output) if it matches with the right table expression. In other words,
the right table expression returns rows for the left table expression match only. - The OUTER APPLY operator returns all the rows from the left table expression
irrespective of its match with the right table expression. For those rows
for which there are no corresponding matches in the right table expression, it contains
NULL values in columns of the right table expression. - So you might conclude, the
CROSS APPLY is equivalent to an INNER JOIN (or to be more precise its
like a CROSS JOIN with a correlated sub-query) with an implicit join condition of
1=1 whereas the OUTER APPLY is equivalent to a LEFT OUTER JOIN.
You might be wondering if the same can be achieved with a regular
JOIN clause, so why and when do you use the APPLY operator? Although the same can be achieved
with a
normal JOIN, the need of APPLY
arises if you have a table-valued expression on the right part and in some cases
the use of the APPLY operator boosts
performance of your query. Let me explain with some examples.