Коли я повинен використовувати перехресне нанесення на внутрішнє з'єднання?


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. перевірити це .
ARZ

3
Схоже, використання "внутрішнього з'єднання циклу" було б дуже близьким до перехресного застосування. Я хочу, щоб ваш приклад детально пояснив, який натяк на приєднання був рівнозначним. Просте сказати приєднання може призвести до внутрішнього / циклу / злиття або навіть до "іншого", оскільки воно може переупорядкуватись з іншими з'єднаннями.
crokusek

3
Коли об'єднання створить багато рядків, але вам потрібно оцінити лише один рядок за один раз. У мене був випадок, якщо мені потрібно було самостійно приєднатися до столу з понад 100 мільйонами рядків, і було простої недостатньо пам'яті. Тому я пішов курсором, щоб знизити слід пам’яті. З курсору я перейшов застосувати як досі керований слід пам'яті і був на 1/3 швидше, ніж курсор.
папараццо

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

Відповіді:


667

Чи може хтось надати мені хороший приклад того, коли 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функцію 'і 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 () такий же приємний і навіть не вимагає приєднання. Тож я не думаю, що я повинен використовувати перехресне застосування для цієї ситуації (виберіть верхній 3, розділ за t1.id).
Jeff Meatball Yang

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

крос застосувати також створює хороший план виконання у поєднанні з вбудованими функціями таблиці, зберігаючи дуже потрібну модульність.
Нуреттін

14
Не SELECTпотрібно всередині CROSS APPLY. Будь ласка, спробуйте CROSS APPLY dbo.myTableFun(O.name) F.
ЕрікЕ

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

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таблиці та a 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 ряди для ID 1, а не 4 рядки для ID 1, 2. Чи не ми б просто використали лівий з’єднання замість цього.
Джозеф Чо

43

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

Простий приклад: у вас є DoB і ви хочете представити кілька полів, пов’язаних з віком, які також будуть покладатися на інші джерела даних (наприклад, зайнятість), наприклад Age, AgeGroup, AgeAtHiring, MinimumRetirementDate тощо для використання у вашому додатку для кінцевих користувачів. (Наприклад, Exvot PivotTables).

Параметри обмежені та рідко елегантні:

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

  • АДС є акуратними, але повільними, оскільки вони, як правило, запобігають паралельним операціям. І бути окремою сутністю може бути хорошою (менше коду) або поганою (де код) річчю.

  • Сполучні столи. Іноді вони можуть спрацювати, але досить скоро ви приєднуєтесь до підзапитів з тоннами UNION. Великий безлад.

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

  • Посередницькі таблиці. Так ... це зазвичай працює і часто є хорошим варіантом, оскільки їх можна індексувати і швидко, але продуктивність також може знизитися через те, що оператори UPDATE не паралельні і не дозволяють каскадно формулам (повторному використанню результатів) оновлювати кілька полів у межах те саме твердження. І іноді ви просто хочете робити речі за один прохід.

  • Внесення запитів. Так, у будь-який момент ви можете поставити дужки на весь запит і використовувати його як підзапит, за допомогою якого ви можете маніпулювати вихідними даними та обчисленими полями. Але ти можеш зробити це лише стільки, поки не стане потворним. Дуже огидний.

  • Повторний код. Яке найбільше значення має три довгі (CASE ... ELSE ... END) заяви? Це буде читабельно!

    • Скажіть своїм клієнтам самі прорахувати прокляті речі.

Я щось пропустив? Напевно, тому сміливо коментуйте. Але ей, КРИСТА ЗАЯВКА - це як знахідка в таких ситуаціях: ти просто 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 , як похідний) derivedTbl - і т.д ...
mrmillsy

1
Будь-яка інформація / приклади, як використовувати Cross Apply як заміну для CASE..ELSE..END?
przemo_li

3
@przemo_li ЗАСТОСУВАННЯ можна використовувати для зберігання результату заяви справи (серед іншого) для посилання на нього. Структура може бути чимось на кшталт: SELECT CASE, коли subquery.intermediateResult> 0 THEN "так" ELSE "ні" END OF someTable OUTER APPLY (виберіть CASE ... END ... ELSE як intermediateResult) як підзапит.
mtone

14

Cross application також добре працює і з 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

На це вже відповіли дуже технічно, але дозвольте навести конкретний приклад того, як це надзвичайно корисно:

Скажімо, у вас дві таблиці, «Клієнт» та «Замовлення». Клієнти мають багато замовлень.

Я хочу створити представлення, яке дає мені детальну інформацію про клієнтів та останнє замовлення, яке вони зробили. Щойно ПРИЄДНАЙТЕСЬ, це потребує певних самостійних приєднань та об'єднання, що не дуже. Але з 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 краще використовувати, ніж ПРИЄДНАЙТЕСЬ, які інші друзі розмістили вище.


4

Ось стаття, яка пояснює все це, різницю їхньої продуктивності та використання в системі JOINS.

SQL Server CROSS APPLY, а ВНУТРІШНЕ ЗАЯВКА через ПРИЄДНАЙТЕСЬ

Як запропоновано в цій статті, різниця в продуктивності між ними для звичайних операцій приєднання (ВНУТРІШНЯ ТА КРЕСЬКА) не існує.

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

Різниця у використанні виникає, коли вам потрібно зробити такий запит:

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:

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, можна переписати за допомогою ВНУТРІШНЬОГО СПОСОБУ, CROSS APPLY може принести кращий план виконання та більш високу продуктивність, оскільки це може обмежити приєднання набору ще до того, як відбудеться з'єднання.

Вкрадено звідси

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.