Пример из реальной жизни, когда использовать OUTER / CROSS APPLY в SQL - PullRequest
115 голосов
/ 14 февраля 2012

Я смотрел на CROSS / OUTER APPLY с коллегой, и мы изо всех сил пытаемся найти реальные примеры того, как их использовать.

Я потратил довольно много времени, глядя на Когда я должен использовать Cross Apply вместо Inner Join? и поиск в Google, но основной (единственный) пример кажется довольно странным (использование rowcount из таблицы для определить, сколько строк выбрать из другой таблицы).

Я думал, что этот сценарий может выиграть от OUTER APPLY:

Таблица контактов (содержит 1 запись для каждого контакта) Таблица записей сообщений (может содержать номер телефона, факса, адрес электронной почты для каждого контакта)

Но, используя подзапросы, обычные табличные выражения, OUTER JOIN с RANK() и OUTER APPLY, похоже, работают одинаково. Я предполагаю, что это означает, что сценарий не применим к APPLY.

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

Ответы [ 4 ]

163 голосов
/ 14 февраля 2012

Некоторые варианты использования APPLY являются ...

1) Top N для групповых запросов (может быть более эффективным для некоторых мощностей)

SELECT pr.name,
       pa.name
FROM   sys.procedures pr
       OUTER APPLY (SELECT TOP 2 *
                    FROM   sys.parameters pa
                    WHERE  pa.object_id = pr.object_id
                    ORDER  BY pr.name) pa
ORDER  BY pr.name,
          pa.name 

2) Вызов табличной функции для каждой строки во внешнем запросе

SELECT *
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)

3) Повторное использование псевдонима столбца

SELECT number,
       doubled_number,
       doubled_number_plus_one
FROM master..spt_values
CROSS APPLY (SELECT 2 * CAST(number AS BIGINT)) CA1(doubled_number)  
CROSS APPLY (SELECT doubled_number + 1) CA2(doubled_number_plus_one)  

4) Отключение нескольких групп столбцов

Предполагается, что 1NF нарушает структуру таблицы ....

CREATE TABLE T
  (
     Id   INT PRIMARY KEY,

     Foo1 INT, Foo2 INT, Foo3 INT,
     Bar1 INT, Bar2 INT, Bar3 INT
  ); 

Пример использования синтаксиса 2008+ VALUES.

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (VALUES(Foo1, Bar1),
                          (Foo2, Bar2),
                          (Foo3, Bar3)) V(Foo, Bar); 

В 2005 году вместо него можно использовать UNION ALL.

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (SELECT Foo1, Bar1 
                    UNION ALL
                    SELECT Foo2, Bar2 
                    UNION ALL
                    SELECT Foo3, Bar3) V(Foo, Bar);
80 голосов
/ 22 февраля 2015

В различных ситуациях вы не можете избежать CROSS APPLY или OUTER APPLY.

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

MASTER TABLE

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.Если мы хотим объединить 2 таблицы по TOP n результатам с INNER JOIN функциональностью

Подумайте, нужно ли нам выбирать 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



НАРУЖНОЕ ПРИМЕНЕНИЕ

1.Если мы хотим объединить 2 таблицы по TOP n результатам с LEFT JOIN функциональностью

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

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
LEFT 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   |
|   2  |   B     |   NULL       |  NULL |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

Это приведет к неправильным результатам, т. е. будет только последние две даты данных из таблицы Details независимо от Idхотя мы присоединяемся с Id.Таким образом, правильное решение использует OUTER APPLY.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
OUTER 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   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

2.Когда нам нужна функциональность LEFT JOIN с использованием functions.

OUTER APPLY можно использовать вместо LEFT JOIN, когда нам нужно получить результат из таблицы Master и function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
OUTER 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   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x



Общая особенность CROSS APPLY и OUTER APPLY

CROSS APPLY или OUTER APPLY может использоваться для сохранения значений NULL при отключении, которыевзаимозаменяемы.

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

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

Когда вы используете UNPIVOT для переноса FROMDATE И TODATE в один столбец, это исключит значения NULL напо умолчанию.

SELECT ID,DATES
FROM MYTABLE
UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P

, что приводит к приведенному ниже результату.Обратите внимание, что мы пропустили запись Id число 3

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

. В таких случаях CROSS APPLY или OUTER APPLY будет полезно

SELECT DISTINCT ID,DATES
FROM MYTABLE 
OUTER APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)

, который формируетследующий результат и сохраняет Id, где его значение равно 3

  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
8 голосов
/ 14 февраля 2012

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

select t.taskName, lg.logResult, lg.lastUpdateDate
from task t
cross apply (select top 1 taskID, logResult, lastUpdateDate
             from taskLog l
             where l.taskID = t.taskID
             order by lastUpdateDate desc) lg
5 голосов
/ 14 февраля 2012

Чтобы ответить на вышеприведенный вопрос, приведите пример:

create table #task (taskID int identity primary key not null, taskName varchar(50) not null)
create table #log (taskID int not null, reportDate datetime not null, result varchar(50) not null, primary key(reportDate, taskId))

insert #task select 'Task 1'
insert #task select 'Task 2'
insert #task select 'Task 3'
insert #task select 'Task 4'
insert #task select 'Task 5'
insert #task select 'Task 6'

insert  #log
select  taskID, 39951 + number, 'Result text...'
from    #task
        cross join (
            select top 1000 row_number() over (order by a.id) as number from syscolumns a cross join syscolumns b cross join syscolumns c) n

А теперь запустите два запроса с планом выполнения.

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        left join (select taskID, reportDate, result, rank() over (partition by taskID order by reportDate desc) rnk from #log) lg
            on lg.taskID = t.taskID and lg.rnk = 1

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        outer apply (   select  top 1 l.*
                        from    #log l
                        where   l.taskID = t.taskID
                        order   by reportDate desc) lg

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

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