2009-07-16 1 views
722

Какова основная цель использования CROSS APPLY?Когда следует использовать Cross Apply над Inner Join?

Я прочитал (смутно, через сообщения в Интернете), что cross apply может быть более эффективным при выборе больших наборов данных, если вы занимаетесь секционированием. (Paging приходит на ум)

Я также знаю, что CROSS APPLYdoesn't require a UDF as the right-table.

В большинстве 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 
+41

Я знаю, что это EVEN PICKIER, но «исполнитель» - это, безусловно, слово. Это не связано с эффективностью. – Rire1979

+2

Это очень полезно для sql xquery. проверьте [это] (http://stackoverflow.com/a/10511719/474679). – ARZ

+2

Похоже, что использование «соединения с внутренней петлей» было бы очень близко к кресту. Я хотел бы, чтобы ваш пример подробно указывал, что подсказка для соединения была эквивалентной. Просто сказать, что соединение может привести к внутреннему/циклу/слиянию или даже к «другому», потому что он может перестраиваться с другими объединениями. – crokusek

ответ

535

Может кто-нибудь дать мне хороший пример, когда CROSS ОТНОСИТЬСЯ делает разницу в тех случаях, когда INNER JOIN будет работать, а?

Смотрите статью в своем блоге для детального сравнения производительности:

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 

, но это менее читаемыми и, вероятно, менее эффективными.

Update:

Только что проверил.

master представляет собой таблицу около 20,000,000 записей с PRIMARY KEY по номеру 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 

мгновенно.

+1

Смотрите конец ссылки Ариэля. Запрос row_number() так же хорош и даже не требует соединения. Поэтому я не думаю, что я должен использовать cross apply для этой ситуации (выберите top 3, partition by t1.id). –

+1

Хороший пример! Необычное увеличение производительности очень очевидно. Как отличаются планы выполнения? –

+0

@Jeff: они отличаются друг от друга, как вы можете видеть :) 'JOIN' использует' NESTED LOOPS' с 'master' как ведущую таблицу,' CROSS APPLY' использует 'NESTED LOOPS', но' t' является ведущим и 'TOP 'применяется к' master' для каждого цикла. – Quassnoi

5

Я предполагаю, что это должно быть читаемость;)

CROSS ОТНОСИТЬСЯ будет несколько уникальных для людей, читающих, чтобы сказать им, что UDF используется, которая будет применяться к каждой строке из таблицы слева.

Конечно, существуют другие ограничения, в которых лучше использовать CROSS APPLY, чем JOIN, которые другие друзья разместили выше.

36

вот пример, когда CROSS ОТНОСИТЬСЯ делает огромную разницу с производительностью:

Using CROSS APPLY to optimize joins on BETWEEN conditions

Заметим, что помимо замены внутренних соединений можно также повторно использовать код, такой как усечение даты без уплаты штрафа производительность для involing скалярные UDF, , например: Calculating third Wednesday of the month with inline UDFs

170

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 + имеет Lateral Derived Tables и Postgresql (9.3+) имеет Lateral Subqueries которые могут быть использованы для аналогичного эффекта.

+11

. Я думаю, что это причина того, почему мы применяем крест. Если вы посмотрите ссылку ниже, это первое, что говорит MS о кресте. Это может быть другое использование, но я думаю, что это причина его введения. Без него функции таблицы не будут использоваться во многих ситуациях. http://technet.microsoft.com/en-us/library/ms175156.aspx – MikeKulls

+0

cross apply также создает приятный план выполнения в сочетании со встроенными функциями таблицы при сохранении необходимой модульности. – nurettin

+11

В '' CROSS APPLY'' 'SELECT' нет' SELECT'. Попробуйте 'CROSS APPLY dbo.myTableFun (O.name) F'. – ErikE

2

Я не уверен, что это может служить основанием для использования Cross Apply по сравнению с Inner Join, но этот запрос был получен для меня в сообщении форума с использованием Cross Apply, поэтому я не уверен, есть ли метод выравнивания, используя Внутренняя Регистрация:

Create PROCEDURE [dbo].[Message_FindHighestMatches] 

-- Declare the Topical Neighborhood 
@TopicalNeighborhood nchar(255) 

AS НАЧАТЬ

-- 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 

END

30

мне кажется, что CROSS ОТНОСИТЬСЯ может заполнить определенный пробел при работе остроумия h вычисляемых полей в сложных/вложенных запросах и сделать их более простыми и читаемыми.

Простой пример: у вас есть DoB, и вы хотите представить несколько возрастных полей, которые также будут полагаться на другие источники данных (например, на работу), такие как Age, AgeGroup, AgeAtHiring, MinimumRetirementDate и т. Д. Для использования в вашем приложение конечного пользователя (например, сводные таблицы Excel).

Варианты ограничены и редко элегантны:

  • РЕГИСТРИРУЙТЕСЬ подзапросы не могут ввести новые значения в наборе данных на основе данных родительского запроса (он должен стоять на своем собственном).

  • UDFs аккуратные, но медленные, поскольку они имеют тенденцию предотвращать параллельные операции. И быть отдельным объектом может быть хороший (меньше кода) или плохой (где это код).

  • Соединительные столы. Иногда они могут работать, но вскоре вы присоединяетесь к подзапросам с множеством UNION. Большой беспорядок.

  • Создайте еще одно универсальное представление, если ваши вычисления не потребуют данных, полученных в середине вашего основного запроса.

  • Промежуточные таблицы. Да ... это обычно работает, и часто хороший вариант, поскольку они могут быть проиндексированы и быстры, но производительность также может снизиться из-за того, что инструкции UPDATE не являются параллельными и не позволяют каскадным формулам (повторное использование) обновлять несколько полей в пределах такое же заявление. И иногда вы просто предпочитаете делать что-то за один проход.

  • Вложенные запросы. Да в любой момент вы можете поместить круглые скобки в свой весь запрос и использовать его в качестве подзапроса, с помощью которого вы можете манипулировать исходными данными и вычисленными полями. Но вы можете сделать это только до того, как он станет уродливым. Очень страшный.

  • Повторяющийся код. Какова максимальная ценность трех длинных (CASE ... ELSE ... END) заявлений? Это будет читаемо!

    • Сообщите своим клиентам, чтобы они сами вычислили проклятые вещи.

ли я что-то пропустил? Наверное, так что не стесняйтесь комментировать. Но эй, CROSS APPLY похож на находку в таких ситуациях: просто добавьте просто CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl и вуаля! Ваше новое поле теперь готово к использованию практически так же, как оно всегда было в ваших исходных данных.

Ценности, представленные через CROSS APPLY can ...

  • использоваться для создания одного или нескольких вычисляемых полей без добавления проблем производительности, сложности или читаемость в миксе
  • как с JOINS, несколько последующих CROSS СОХРАНИТЬ заявления могут относиться к себе: CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
  • вы можете использовать значения, введенные в поперечном APPLY в последующих условий соединения
  • в качестве бонуса, есть таблица-функция аспект

Данг, есть Noth они не могут этого сделать!

+0

Это большой +1 от меня, так как я удивлен, что это не упоминается чаще. Возможно, вы можете расширить этот пример, чтобы показать, как вы можете выполнять «процедурные» вычисления в цепочке производных значений? Например: CROSS APPLY (выберите crossTbl.value * tbl.multiplier as Multiplied) multiTbl - CROSS APPLY (выберите multiTbl.Multiplied/tbl.DerivativeRatio as Derived), полученныйTbl - и т. Д. ... – mrmillsy

+0

Дополнительная информация/примеры использования Cross Apply в качестве замены для CASE..ELSE..END? –

+1

@przemo_li APPLY может использоваться для хранения результата оператора case (между прочим), чтобы ссылаться на него. Структура может быть чем-то вроде: SELECT CASE, когда subquery.intermediateResult> 0 THEN «yes» ELSE «no» END FROM someTable OUTER APPLY (выберите CASE ... END ... ELSE как intermediateResult) в качестве подзапроса. – mtone

12

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 
2

Крест применить баллончик использоваться для замены подзапроса, где требуется столбец подзапроса

подзапроса

select * from person p where 
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%') 

здесь я не буду иметь возможность выбрать столбцы таблицы компании так, используя кросс применять

select P.*,T.CompanyName 
from Person p 
cross apply (
    select * 
    from Company C 
    where p.companyid = c.companyId and c.CompanyName like '%yyy%' 
) T 
95

Рассмотрим вас имеют две таблицы.

MASTER ТАБЛИЦА

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 [email protected] 
) 

который генерируется следующий результат

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

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 
+2

Отличный пример с 2-х и 4-х отчетами и помог мне понять контекст, в котором это было бы необходимо. – trnelson

0

Это, пожалуй, старый вопрос, но я до сих пор люблю власть CROSS ОТНОСИТЬСЯ для упрощения повторного -использование логики и обеспечение «цепочки» механизма для результатов.

Я предоставил SQL-скрипт ниже, который показывает простой пример того, как вы можете использовать CROSS APPLY для выполнения сложных логических операций в вашем наборе данных без каких-либо проблем. Из этого сложнее не экстраполировать более сложные вычисления.

http://sqlfiddle.com/#!3/23862/2

3

Вот статья, которая объясняет все это, с их разницей в производительности и использовании более соединений.

SQL Server CROSS APPLY and OUTER APPLY over JOINS

Как было предложено в этой статье, нет никакой разницы в производительности между ними для обычных операций соединения (внутренний и крестик).

enter image description here

Разница использование приходит, когда вы должны сделать запрос, как это:

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) 

То есть, когда вы должны связать с функцией. Это невозможно сделать с помощью INNER JOIN, который даст вам ошибку «Идентификатор многочастности« D.DepartmentID »не может быть связан». Здесь значение передается функции при чтении каждой строки. Звучит здорово для меня. :)

Смежные вопросы