Когда я должен использовать крест применить поверх внутреннего соединения? - PullRequest
855 голосов
/ 16 июля 2009

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

Я прочитал (смутно, через сообщения в Интернете), что 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 ]

623 голосов
/ 16 июля 2009

Может ли кто-нибудь привести хороший пример того, когда 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 и оконную функцию:

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

мгновенно.

188 голосов
/ 03 марта 2011

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+) имеет Боковые подзапросы , которые можно использовать для аналогичного эффекта.

147 голосов
/ 26 февраля 2015

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

ОСНОВНОЙ СТОЛ

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

ДОПОЛНИТЕЛЬНОЕ ПРЕИМУЩЕСТВО ПРИМЕНЕНИЯ CROSS

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
37 голосов
/ 11 июня 2012

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

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

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

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

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

  • Соединительные столы. Иногда они могут работать, но достаточно скоро вы присоединяетесь к подзапросам с тоннами 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
  • В качестве бонуса есть аспект табличной функции

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

36 голосов
/ 16 июля 2009

Вот пример, когда CROSS APPLY сильно влияет на производительность:

Использование CROSS APPLY для оптимизации соединений в условиях между

Обратите внимание, что помимо замены внутренних объединений вы также можете повторно использовать код, такой как усечение дат, без уплаты штрафа за производительность за использование скалярных пользовательских функций, например: Расчет третьей среды месяца с помощью встроенных пользовательских функций

14 голосов
/ 01 февраля 2013

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
7 голосов
/ 09 июля 2018

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

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

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

SELECT *
FROM Customer
CROSS APPLY (
  SELECT TOP 1 *
  FROM Order
  WHERE Order.CustomerId = Customer.CustomerId
  ORDER BY OrderDate DESC
) T
7 голосов
/ 11 декабря 2014

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

подзапрос

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 голосов
/ 16 июля 2009

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

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

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

4 голосов
/ 21 марта 2016

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

SQL Server CROSS APPLY и OUTTER APPLY поверх JOINS

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...