Когда я должен использовать крест применить поверх внутреннего соединения?


925

Какова основная цель использования CROSS APPLY ?

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

Я также знаю, что CROSS APPLYне требует UDF в качестве правой таблицы.

В большинстве INNER JOINзапросов (отношения «один ко многим») я мог бы переписать их для использования CROSS APPLY, но они всегда дают мне эквивалентные планы выполнения.

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


Редактировать:

Вот тривиальный пример, где планы выполнения точно такие же. (Покажите мне, где они отличаются и где 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

50
Я знаю, что это ДАЖЕ ПИКЕР из меня, но «исполнитель» - это определенно слово. Это просто не связано с эффективностью.
Rire1979

2
Это очень полезно для sql xquery. проверь это .
АРЗ

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

3
Когда соединение создаст много строк, но вам нужно только оценить одно соединение строк за раз. У меня был случай, когда мне нужно было самостоятельное объединение таблицы с более чем 100 миллионами строк, и там просто не хватало памяти. Поэтому я пошел курсором, чтобы уменьшить объем памяти. От курсора я перешел применить, как все еще управляемый след памяти и был на 1/3 быстрее, чем курсор.
Папараццо

10
CROSS APPLYимеет очевидное применение, позволяя множеству зависеть от другого (в отличие от JOINоператора), но это не обходится без затрат: оно ведет себя как функция, которая работает над каждым членом левого набора, поэтому в терминах SQL Server это всегда выполняйте Loop Join, что почти никогда не является лучшим способом объединения наборов. Так что используйте, APPLYкогда вам нужно, но не злоупотребляйте им JOIN.
Херардо Лима,

Ответы:


668

Кто-нибудь может дать мне хороший пример того, когда CROSS APPLY имеет значение в тех случаях, когда 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функцию s и window:

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

мгновенно


2
Смотрите конец ссылки Ариэль. Запрос row_number () так же хорош и даже не требует объединения. Так что я не думаю, что я должен использовать перекрестное применение для этой ситуации (выберите top 3, partition by t1.id).
Джефф Фрикадель Ян

375
Хотя это самый популярный ответ, я не думаю, что он отвечает на реальный вопрос «Какова основная цель использования CROSS APPLY?». Основная цель - дать возможность табличным функциям с параметрами выполняться по одному разу в строке и затем присоединяться к результатам.
MikeKulls

5
@Mike: как вы называете TVFс INNER JOIN?
Quassnoi

15
@MikeKulls Да, но ОП не спрашивал об основной цели использования CROSS APPLY, он спрашивал, когда выбрать INNER JOIN, когда это сработает.
ErikE

8
Возможно, стоит упомянуть, что это называется lateral joinстандартным (ANSI) SQL
a_horse_with_no_name

198

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

Это законно

Изменить: Или, альтернативно, более короткий синтаксис: (ErikE)

select F.* from sys.objects O  
cross apply dbo.myTableFun(O.name) F
where F.schema_id= O.schema_id

Редактировать:

Примечание: Informix 12.10 xC2 + имеет Боковые производные таблицы, а Postgresql (9.3+) имеет Боковые подзапросы, которые можно использовать для аналогичного эффекта.


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

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

14
Нет SELECTнеобходимости внутри CROSS APPLY. Пожалуйста, попробуйте CROSS APPLY dbo.myTableFun(O.name) F.
ErikE

1
@ Erik Конечно, вы всегда можете использовать менее гибкий синтаксис для перекрестного применения. Я показывал более обобщенную версию, которую иногда можно использовать, чтобы избежать сложного вычисления столбцов в запросе.
Нуреттин

2
Внутреннее объединение @Bolu не будет работать, если параметр табличной функции зависит от столбца другой таблицы (или внешней ссылки) во внешнем выборе. Это будет работать, если параметр табличной функции является литералом или переменной. Перекрестное применение будет работать в обоих случаях.
Нуреттин

175

Предположим, у вас есть две таблицы.

МАСТЕР СТОЛ

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, что неверно. Должно быть возвращено как Ids1, так и 2, но возвращено только 1, потому что 1 имеет последние две даты. Для этого нам нужно использовать 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

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

13
Этот ответ доказывает, что действительно стоит прокрутить страницу вниз, а не просто выбрать принятую.
Мостафа Арманди

2
Лучший пример, пока объясняющий использование APPLY ... Я прочитал много постов и понял, что это объяснение очищает картину как воду. Большое спасибо, брат.
AG7

1
Для точки 1, где у нас есть 2 строки для идентификатора 1 вместо 4 строк для идентификатора 1, 2. Разве мы не использовали бы вместо этого левое соединение?
Джозеф Чо

43

Мне кажется, что CROSS APPLY может заполнить определенный пробел при работе с вычисляемыми полями в сложных / вложенных запросах и сделать их более простыми и удобочитаемыми.

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

Варианты ограничены и редко изящны:

  • Подзапросы JOIN не могут вводить новые значения в набор данных на основе данных в родительском запросе (он должен стоять самостоятельно).

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

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

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

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

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

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

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

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

Значения, введенные через CROSS APPLY, могут ...

  • использоваться для создания одного или нескольких вычисляемых полей без добавления проблем с производительностью, сложностью или удобочитаемостью
  • как и в случае JOIN, несколько последующих операторов CROSS APPLY могут ссылаться на себя: CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
  • Вы можете использовать значения, введенные CROSS APPLY в последующих условиях JOIN
  • В качестве бонуса есть функция табличных функций

Черт, они ничего не могут сделать!


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

1
Любая дополнительная информация / примеры о том, как использовать Cross Apply вместо CASE..ELSE..END?
przemo_li

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

14

Перекрестное применение хорошо работает и с полем 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

13

На это уже очень хорошо технически ответили, но позвольте мне привести конкретный пример того, как это чрезвычайно полезно:

Допустим, у вас есть две таблицы: клиент и заказ. У клиентов много заказов.

Я хочу создать представление, которое дает мне подробную информацию о клиентах и ​​о самом последнем заказе, который они сделали. С просто JOINS это потребует некоторых самостоятельных объединений и агрегации, что не очень красиво. Но с Cross Apply это супер просто:

SELECT *
FROM Customer
CROSS APPLY (
  SELECT TOP 1 *
  FROM Order
  WHERE Order.CustomerId = Customer.CustomerId
  ORDER BY OrderDate DESC
) T

7

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

подзапрос

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

5

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

CROSS APPLY будет несколько уникальным для читателей, которые сообщат им, что используется UDF, который будет применен к каждой строке таблицы слева.

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


4

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

SQL Server CROSS APPLY и OUTTER APPLY через соединения

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

введите описание изображения здесь

Разница в использовании возникает, когда вам нужно выполнить запрос, подобный следующему:

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». Здесь значение передается функции при чтении каждой строки. Звучит круто для меня. :)


3

Ну, я не уверен, является ли это причиной для использования Cross Apply по сравнению с Inner Join, но на этот вопрос я получил ответ в сообщении на форуме с использованием Cross Apply, поэтому я не уверен, существует ли такой же метод с использованием 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

КОНЕЦ


3

Суть оператора APPLY состоит в том, чтобы разрешить корреляцию между левой и правой частью оператора в предложении FROM.

В отличие от JOIN, корреляция между входами не допускается.

Говоря о корреляции в операторе APPLY, я имею в виду, что с правой стороны мы можем поместить:

  • производная таблица - как коррелированный подзапрос с псевдонимом
  • табличная функция - концептуальное представление с параметрами, где параметр может ссылаться на левую сторону

Оба могут возвращать несколько столбцов и строк.


2

Возможно, это старый вопрос, но мне все еще нравится возможность CROSS APPLY упростить повторное использование логики и обеспечить механизм «цепочки» для результатов.

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

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


1

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

Украдено отсюда

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.