Когда следует использовать Cross Apply over Inner Join?
какова основная цель использования КРЕСТ ПРИМЕНИТЬ?
Я прочитал (смутно, через сообщения в интернете), что cross apply
может быть более эффективным при выборе больших наборов данных, если вы разделяете. (На ум приходит пейджинг)
Я также знаю, что CROSS APPLY
не требует UDF в качестве правой таблицы.
в большинстве INNER JOIN
запросы (один ко многим), я мог бы переписать их, чтобы использовать CROSS APPLY
, но они всегда дайте мне эквивалентные планы исполнения.
может ли кто-нибудь дать мне хороший пример того, когда CROSS APPLY
имеет значение в тех случаях, когда INNER JOIN
будет работать?
Edit:
вот тривиальный пример, где планы выполнения точно такие же. (Покажите мне, где они отличаются и где cross apply
быстрее/эффективнее)
create table Company (
companyId int identity(1,1)
, companyName varchar(100)
, zipcode varchar(10)
, constraint PK_Company primary key (companyId)
)
GO
create table Person (
personId int identity(1,1)
, personName varchar(100)
, companyId int
, constraint FK_Person_CompanyId foreign key (companyId) references dbo.Company(companyId)
, constraint PK_Person primary key (personId)
)
GO
insert Company
select 'ABC Company', '19808' union
select 'XYZ Company', '08534' union
select '123 Company', '10016'
insert Person
select 'Alan', 1 union
select 'Bobby', 1 union
select 'Chris', 1 union
select 'Xavier', 2 union
select 'Yoshi', 2 union
select 'Zambrano', 2 union
select 'Player 1', 3 union
select 'Player 2', 3 union
select 'Player 3', 3
/* using CROSS APPLY */
select *
from Person p
cross apply (
select *
from Company c
where p.companyid = c.companyId
) Czip
/* the equivalent query using INNER JOIN */
select *
from Person p
inner join Company c on p.companyid = c.companyId
13 ответов
может ли кто-нибудь дать мне хороший пример того, когда CROSS APPLY имеет значение в тех случаях, когда внутреннее соединение также будет работать?
см. статью в моем блоге для подробного сравнения производительности:
CROSS APPLY
работает лучше на вещах, которые не имеют простой JOIN
состояние.
этот выбор 3
последние записи от t2
для каждой записи из t1
:
SELECT t1.*, t2o.*
FROM t1
CROSS APPLY
(
SELECT TOP 3 *
FROM t2
WHERE t2.t1_id = t1.id
ORDER BY
t2.rank DESC
) t2o
его нельзя легко сформулировать с помощью INNER JOIN
состояние.
вы, вероятно, могли бы сделать что-то подобное, используя CTE
и функция окна:
WITH t2o AS
(
SELECT t2.*, ROW_NUMBER() OVER (PARTITION BY t1_id ORDER BY rank) AS rn
FROM t2
)
SELECT t1.*, t2o.*
FROM t1
INNER JOIN
t2o
ON t2o.t1_id = t1.id
AND t2o.rn <= 3
, но это менее читабельно и, вероятно, менее эффективно.
обновление:
только что проверил.
master
таблица о 20,000,000
записи с PRIMARY KEY
on id
.
запрос:
WITH q AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM master
),
t AS
(
SELECT 1 AS id
UNION ALL
SELECT 2
)
SELECT *
FROM t
JOIN q
ON q.rn <= t.id
работает на протяжении почти 30
секунд, в то время как этот:
WITH t AS
(
SELECT 1 AS id
UNION ALL
SELECT 2
)
SELECT *
FROM t
CROSS APPLY
(
SELECT TOP (t.id) m.*
FROM master m
ORDER BY
id
) q
мгновенно.
cross apply
иногда позволяет делать то, что вы не можете сделать с inner join
.
пример (синтаксическая ошибка):
select F.* from sys.objects O
inner join dbo.myTableFun(O.name) F
on F.schema_id= O.schema_id
это синтаксис, потому что при использовании inner join
, функции таблицы могут принимать только переменные или константы как параметры. (То есть, параметр функции таблицы не может зависеть от столбца другой таблицы.)
select F.* from sys.objects O
cross apply ( select * from dbo.myTableFun(O.name) ) F
where F.schema_id= O.schema_id
это законный.
Edit: Или, альтернативно, более короткий синтаксис: (по ErikE)
select F.* from sys.objects O
cross apply dbo.myTableFun(O.name) F
where F.schema_id= O.schema_id
Edit:
Примечание.: Informix 12.10 xC2+ имеет Боковые Производные Таблицы и Postgresql (9.3+) имеет Боковая Подзапросы который можно использовать для аналогичного эффекта.
рассмотрим у вас есть две таблицы.
ГЛАВНЫЙ СТОЛ
x------x--------------------x
| Id | Name |
x------x--------------------x
| 1 | A |
| 2 | B |
| 3 | C |
x------x--------------------x
ТАБЛИЦЕ
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
есть много ситуаций, когда нам нужно заменить INNER JOIN
С CROSS APPLY
.
1. Присоединяйтесь к двум таблицам на основе TOP n
результаты
рассмотрим, если нам нужно выбрать Id
и Name
С Master
и последние две даты для каждого Id
от Details table
.
SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
INNER JOIN
(
SELECT TOP 2 ID, PERIOD,QTY
FROM DETAILS D
ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID
приведенный выше запрос выдает следующий результат.
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 |
x------x---------x--------------x-------x
см., он сгенерировал результаты для последних двух дат с последними двумя датами Id
и затем присоединился к этим записям только во внешнем запросе на Id
, а это неправильно. Для этого нам нужно использовать CROSS APPLY
.
SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
CROSS APPLY
(
SELECT TOP 2 ID, PERIOD,QTY
FROM DETAILS D
WHERE M.ID=D.ID
ORDER BY CAST(PERIOD AS DATE)DESC
)D
и формирует следующий результат.
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 |
x------x---------x--------------x-------x
вот как это работает. Запрос внутри CROSS APPLY
может ссылаться на внешнюю таблицу, где INNER JOIN
не может этого сделать (он выдает ошибку компиляции). При нахождении последних двух дат присоединение выполняется внутри CROSS APPLY
то есть,WHERE M.ID=D.ID
.
2. Когда нам нужно INNER JOIN
функции с помощью функции.
CROSS APPLY
смогите быть использовано как замена с INNER JOIN
когда нам нужно получить результат из Master
таблица и function
.
SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C
и вот функция
CREATE FUNCTION FnGetQty
(
@Id INT
)
RETURNS TABLE
AS
RETURN
(
SELECT ID,PERIOD,QTY
FROM DETAILS
WHERE ID=@Id
)
который сгенерировал следующий результат
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 |
x------x---------x--------------x-------x
ДОПОЛНИТЕЛЬНОЕ ПРЕИМУЩЕСТВО КРЕСТА ПРИМЕНЯЕТСЯ
APPLY
может использоваться в качестве замены для UNPIVOT
. Либо CROSS APPLY
или OUTER APPLY
можно использовать здесь, которые сменный.
считают, что у вас есть таблица ниже (с именем MYTABLE
).
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
запрос ниже.
SELECT DISTINCT ID,DATES
FROM MYTABLE
CROSS APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)
, который приносит вам результат
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
вот пример, когда CROSS APPLY имеет огромное значение с производительностью:
использование CROSS APPLY для оптимизации соединений между условиями
обратите внимание, что помимо замены внутренних соединений вы также можете повторно использовать код, такой как усечение дат, не платя штраф за выполнение скалярных UDFs, например: расчет третьей среды месяца со встроенным UDFs
мне кажется, что CROSS APPLY может заполнить определенный пробел при работе с вычисляемыми полями в сложных / вложенных запросах и сделать их более простыми и читаемыми.
простой пример: у вас есть DoB, и вы хотите представить несколько связанных с возрастом полей, которые также будут зависеть от других источников данных (таких как занятость), таких как возраст, возрастная группа, AgeAtHiring, MinimumRetirementDate и т. д. для использования в приложении конечного пользователя (например, сводные таблицы Excel).
опции ограниченный и редко элегантный:
вложенные запросы JOIN не могут вводить новые значения в набор данных на основе данных родительского запроса (он должен стоять сам по себе).
UDFs аккуратны, но медленны, поскольку они, как правило, предотвращают параллельные операции. И быть отдельной сущностью может быть хорошей (меньше кода) или плохой (где код) вещью.
таблицы соединений. Иногда они могут работать, но достаточно скоро вы присоединитесь к подзапросам с тонны профсоюзов. Большой беспорядок.
создайте еще один одноцелевой вид, предполагая, что ваши вычисления не требуют данных, полученных в середине основного запроса.
промежуточных таблиц. Да... это обычно работает, и часто хороший вариант, поскольку они могут быть проиндексированы и быстры, но производительность также может снизиться из-за того, что операторы обновления не параллельны и не позволяют каскадировать формулы (повторно использовать результаты) для обновления нескольких полей в одном и том же заявление. И иногда ты просто предпочитаешь делать все за один раз.
запросы вложенности. Да в любой момент Вы можете поставить скобки на весь запрос и использовать его как подзапрос, на котором вы можете манипулировать исходными данными и вычисляемыми полями. Но вы можете сделать это только до того, как это станет уродливым. Очень уродливый.
-
повторяющийся код. Какое наибольшее значение имеет 3 long (CASE...ЕЩЕ...Конец) заявления? Это будет читаемый!
- скажите своим клиентам, чтобы они сами рассчитали эти чертовы вещи.
Я что-то пропустил? Наверное, поэтому не стесняйтесь комментировать. Но эй, CROSS APPLY - это как находка в таких ситуациях: вы просто добавляете простой CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl
и вуаля! Ваше новое поле теперь готово к использованию практически так же, как оно всегда было в ваших исходных данных.
значения, введенные через CROSS APPLY can...
- использоваться для создайте одно или несколько вычисляемых полей без добавления проблем производительности, сложности или читаемости в mix
- как и с соединениями, несколько последующих операторов CROSS APPLY могут ссылаться на себя:
CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
- вы можете использовать значения, введенные крестом, применяются в последующих условиях соединения
- в качестве бонуса есть функция с табличным значением aspect
Черт, нет ничего, что они не могут сделать!
Cross apply также хорошо работает с полем XML. Если вы хотите выбрать значения узлов в сочетании с другими полями.
например, если у вас есть таблица, содержащая некоторый xml
<root> <subnode1> <some_node value="1" /> <some_node value="2" /> <some_node value="3" /> <some_node value="4" /> </subnode1> </root>
использование запроса
SELECT
id as [xt_id]
,xmlfield.value('(/root/@attribute)[1]', 'varchar(50)') root_attribute_value
,node_attribute_value = [some_node].value('@value', 'int')
,lt.lt_name
FROM dbo.table_with_xml xt
CROSS APPLY xmlfield.nodes('/root/subnode1/some_node') as g ([some_node])
LEFT OUTER JOIN dbo.lookup_table lt
ON [some_node].value('@value', 'int') = lt.lt_id
возвращает результат
xt_id root_attribute_value node_attribute_value lt_name
----------------------------------------------------------------------
1 test1 1 Benefits
1 test1 4 FINRPTCOMPANY
Я думаю, это должна быть читаемость ;)
CROSS APPLY будет несколько уникальным для людей, читающих, чтобы сказать им, что используется UDF, который будет применяться к каждой строке из таблицы слева.
конечно, есть и другие ограничения, где крест применяется лучше, чем присоединиться, которые другие друзья разместили выше.
Cross apply можно использовать для замены подзапроса, где вам нужен столбец подзапроса
подзапрос
select * from person p where
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')
здесь я не смогу выбрать столбцы таблицы компании Итак, используя cross apply
select P.*,T.CompanyName
from Person p
cross apply (
select *
from Company C
where p.companyid = c.companyId and c.CompanyName like '%yyy%'
) T
вот статья, которая объясняет все это, с их разницей в производительности и использованием по соединениям.
SQL Server CROSS APPLY и OUTER APPLY over JOINS
как предложено в этой статье, нет разницы в производительности между ними для обычных операций соединения (внутренний и перекрестный).
разница в использовании приходит, когда вам нужно сделать такой запрос:
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)
это, когда вы должны общаться с функцией. Это невозможно сделать с помощью внутреннего соединения, что даст вам ошибку "многосоставной идентификатор" D. DepartmentID " не может быть связан." здесь значение передается функции по мере чтения каждой строки. Звучит круто. :)
Ну, я не уверен, что это квалифицируется как причина для использования Cross Apply против Inner Join, но на этот запрос был дан ответ для меня в сообщении форума с использованием Cross Apply, поэтому я не уверен, есть ли equalivent метод с использованием Inner Join:
Create PROCEDURE [dbo].[Message_FindHighestMatches]
-- Declare the Topical Neighborhood
@TopicalNeighborhood nchar(255)
как Начать
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
Create table #temp
(
MessageID int,
Subjects nchar(255),
SubjectsCount int
)
Insert into #temp Select MessageID, Subjects, SubjectsCount From Message
Select Top 20 MessageID, Subjects, SubjectsCount,
(t.cnt * 100)/t3.inputvalues as MatchPercentage
From #temp
cross apply (select count(*) as cnt from dbo.Split(Subjects,',') as t1
join dbo.Split(@TopicalNeighborhood,',') as t2
on t1.value = t2.value) as t
cross apply (select count(*) as inputValues from dbo.Split(@TopicalNeighborhood,',')) as t3
Order By MatchPercentage desc
drop table #temp
конец
это, возможно, старый вопрос, но я все еще люблю силу Креста, чтобы упростить повторное использование логики и обеспечить "цепной" механизм для результатов.
Я предоставил SQL Fiddle ниже, который показывает простой пример того, как вы можете использовать CROSS APPLY для выполнения сложных логических операций над вашим набором данных без каких-либо беспорядков. Нетрудно экстраполировать отсюда более сложные проведенные расчеты.
суть оператора APPLY заключается в том, чтобы разрешить корреляцию между левой и правой сторонами оператора в предложении FROM.
в отличие от JOIN, корреляция между входами не допускается.
говоря о корреляции в операторе APPLY, я имею в виду, что с правой стороны мы можем поставить:
- производная таблица - как коррелированный подзапрос с псевдонимом
- таблично-значная функция-концептуальное представление с параметрами, где параметр может ссылаться на левую сторону
Как может возвращать несколько столбцов и строк.
на это уже ответили очень хорошо технически, но позвольте мне привести конкретный пример того, как это чрезвычайно полезно:
допустим есть две таблицы Customer и Order. Клиенты имеют много заказов.
Я хочу создать представление, которое дает мне подробную информацию о клиентах и последнем заказе, который они сделали. С just JOINS это потребует некоторых самосоединений и агрегации, что не очень красиво. Но с Cross Apply, его супер легко:
SELECT *
FROM Customer
CROSS APPLY (
SELECT TOP 1 *
FROM Order
WHERE Order.CustomerId = Customer.CustomerId
ORDER BY OrderDate DESC
) T