Когда следует использовать 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 [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 может использоваться в качестве замены для 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

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

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)

это, когда вы должны общаться с функцией. Это невозможно сделать с помощью внутреннего соединения, что даст вам ошибку "многосоставной идентификатор" 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 для выполнения сложных логических операций над вашим набором данных без каких-либо беспорядков. Нетрудно экстраполировать отсюда более сложные проведенные расчеты.

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


суть оператора 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