Quando devo usar a aplicação cruzada sobre a junção interna?


925

Qual é o principal objetivo do uso do CROSS APPLY ?

Li (vagamente, através de postagens na Internet) que cross applypodem ser mais eficientes ao selecionar conjuntos de dados grandes, se você estiver particionando. (Paginação vem à mente)

Eu também sei que CROSS APPLYnão requer uma UDF como a tabela correta.

Na maioria das INNER JOINconsultas (relacionamentos um para muitos), eu poderia reescrevê-las para uso CROSS APPLY, mas elas sempre me oferecem planos de execução equivalentes.

Alguém pode me dar um bom exemplo de quando CROSS APPLYfaz a diferença nos casos em que INNER JOINtambém funcionará?


Editar:

Aqui está um exemplo trivial, onde os planos de execução são exatamente os mesmos. (Mostre-me onde eles diferem e onde cross applyé mais rápido / mais eficiente)

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
Eu sei que isso é PICKIER MESMO, mas 'performance' é definitivamente uma palavra. Apenas não está relacionado à eficiência.
Read1979

2
É muito útil para sql xquery. verifique isso .
ARZ

3
Parece que o uso de "junção de loop interno" seria muito próximo da aplicação cruzada. Desejo que seu exemplo detalhe qual dica de junção era equivalente. Apenas dizer que a junção pode resultar em inner / loop / merge ou até "other" porque pode reorganizar outras junções.
9788 Crokusek

3
Quando a junção criará muitas linhas, mas você só precisará avaliar uma junção de linha por vez. Eu tive um caso em que eu precisava de uma auto-junção em uma tabela com mais de 100 milhões de linhas e não havia memória suficiente. Então eu fui o cursor para diminuir a área de memória. Do cursor, passei a aplicar como pegada de memória ainda gerenciada e fui 1/3 mais rápida que o cursor.
Paparazzo

10
CROSS APPLYtem seu uso óbvio em permitir que um conjunto dependa de outro (diferente do JOINoperador), mas isso não tem um custo: ele se comporta como uma função que opera sobre cada membro do conjunto esquerdo ; portanto, nos termos do SQL Server, sempre execute a Loop Join, que quase nunca é a melhor maneira de juntar conjuntos. Portanto, use APPLYquando precisar, mas não use demais JOIN.
Gerardo Lima

Respostas:


668

Alguém pode me dar um bom exemplo de quando o CROSS APPLY faz a diferença nos casos em que INNER JOIN também funcionará?

Veja o artigo no meu blog para uma comparação detalhada de desempenho:

CROSS APPLYfunciona melhor em coisas que não têm uma JOINcondição simples .

Este seleciona os 3últimos registros t2para cada registro entre 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

Não pode ser facilmente formulado com uma INNER JOINcondição.

Você provavelmente poderia fazer algo assim usando CTEas funções de janela e:

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

, mas isso é menos legível e provavelmente menos eficiente.

Atualizar:

Apenas verificado.

masteré uma tabela de 20,000,000registros com um PRIMARY KEYon id.

Esta consulta:

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

é executado por quase 30segundos, enquanto este:

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

é instantâneo.


2
Veja o final do link de Ariel. Uma consulta row_number () é igualmente agradável e nem exige uma associação. Portanto, acho que não devo usar cross apply para essa situação (selecione as 3 principais, particione por t1.id).
Jeff Meatball Yang

375
Embora essa seja a resposta mais popular, acho que não responde à pergunta real "Qual é o principal objetivo do uso do CROSS APPLY?". O principal objetivo é habilitar as funções da tabela com parâmetros a serem executados uma vez por linha e, em seguida, unidos aos resultados.
MikeKulls

5
@ Mike: como você chama um TVFcom INNER JOIN?
Quassnoi 12/08/11

15
@MikeKulls Sim, mas o OP não pediu o objetivo principal do uso CROSS APPLY, ele pediu quando escolher INNER JOIN, quando isso também funcionaria.
ErikE

8
Pode valer a pena mencionar que este é chamado de lateral joinno padrão (ANSI) SQL
a_horse_with_no_name

198

cross applyàs vezes permite que você faça coisas com as quais não pode fazer inner join.

Exemplo (um erro de sintaxe):

select F.* from sys.objects O  
inner join dbo.myTableFun(O.name) F   
on F.schema_id= O.schema_id

Este é um erro de sintaxe , porque, quando usadas com inner join, funções de tabela podem aceitar apenas variáveis ​​ou constantes como parâmetros. (Ou seja, o parâmetro da função de tabela não pode depender da coluna de outra tabela.)

Contudo:

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

Isso é legal.

Editar: ou, alternativamente, sintaxe mais curta: (por ErikE)

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

Editar:

Nota: O Informix 12.10 xC2 + possui tabelas derivadas laterais e o Postgresql (9.3+) possui subconsultas laterais que podem ser usadas para um efeito semelhante.


11
Eu acho que esse é o raciocínio por trás do motivo de aplicarmos cruzadamente. Se você verificar o link abaixo, esta é a primeira coisa que a MS diz sobre a aplicação cruzada. Pode ter outros usos, mas acho que é por isso que foi introduzido. Sem ela, as funções da tabela não seriam utilizáveis ​​em muitas situações. technet.microsoft.com/en-us/library/ms175156.aspx
MikeKulls

O cross apply também produz um bom plano de execução quando associado às funções da tabela embutida, mantendo a modularidade necessária.
Nurettin

14
Não é SELECTnecessário dentro do CROSS APPLY. Por favor tente CROSS APPLY dbo.myTableFun(O.name) F.
ErikE

1
Com certeza, você sempre pode usar a sintaxe menos flexível para aplicar de maneira cruzada. Eu estava mostrando a versão mais generalizada que você pode usar algumas vezes para evitar trazer colunas de difícil cálculo para a consulta.
achou

2
A junção interna do @Bolu não funcionará se o parâmetro de função da tabela depender da coluna de outra tabela (também conhecida como referência externa) na seleção externa. Funcionará se o parâmetro da função de tabela for literal ou variável. A aplicação cruzada funcionará nos dois casos.
precisa

175

Considere que você tem duas tabelas.

TABELA MESTRE

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x

TABELA DE DETALHES

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

Há muitas situações em que precisamos de substituir INNER JOINcom CROSS APPLY.

1. Junte duas tabelas com base nos TOP nresultados

Considere se precisamos selecionar Ide Namede Mastere últimas duas datas para cada Idpartir 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

A consulta acima gera o seguinte resultado.

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

Veja, ele gerou resultados para as duas últimas datas e as duas últimas Ide uniu esses registros apenas na consulta externa ativada Id, o que está errado. Isso deve retornar Ids1 e 2, mas retornou apenas 1 porque 1 tem as duas últimas datas. Para conseguir isso, precisamos usar 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

e forma o seguinte resultado.

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

Aqui está como isso funciona. A consulta interna CROSS APPLYpode fazer referência à tabela externa, onde INNER JOINnão pode fazer isso (gera erro de compilação). Ao encontrar as duas últimas datas, a união é feita dentro CROSS APPLY, ou seja WHERE M.ID=D.ID,.

2. Quando precisamos de INNER JOINfuncionalidade usando funções.

CROSS APPLYpode ser usado como um substituto INNER JOINquando precisamos obter resultados da Mastertabela e a function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C

E aqui está a função

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

que gerou o seguinte resultado

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

VANTAGEM ADICIONAL DE APLICAÇÃO TRANSVERSAL

APPLYpode ser usado como um substituto para UNPIVOT. De qualquer CROSS APPLYou OUTER APPLYpodem ser usados aqui, que são intercambiáveis.

Considere que você tem a tabela abaixo (nomeada 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

A consulta está abaixo.

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

que traz o resultado

  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
Excelente exemplo com os registros 2 vs 4 e me ajudou a entender o contexto em que isso seria necessário.
trnelson

13
Esta resposta prova que realmente vale a pena rolar a página em vez de apenas escolher a aceita.
Mostafa Armandi

2
Melhor exemplo até agora para explicar o uso de APPLY ... Eu li muitos posts e percebi que essa explicação limpa a imagem como água. Muito obrigado mano.
AG7 24/11/19

1
Para o ponto 1, onde temos 2 linhas para o ID 1 em vez de 4 linhas para o ID 1, 2. Não usaríamos apenas uma junção esquerda.
Joseph Cho

43

Parece-me que o CROSS APPLY pode preencher uma certa lacuna ao trabalhar com campos calculados em consultas complexas / aninhadas e torná-los mais simples e legíveis.

Exemplo simples: você tem um DoB e deseja apresentar vários campos relacionados à idade que também dependerão de outras fontes de dados (como emprego), como Age, AgeGroup, AgeAtHiring, MinimumRetirementDate etc. para uso em seu aplicativo de usuário final (Tabelas dinâmicas do Excel, por exemplo).

As opções são limitadas e raramente elegantes:

  • As subconsultas JOIN não podem introduzir novos valores no conjunto de dados com base nos dados da consulta pai (ele deve permanecer por si próprio).

  • UDFs são legais, mas lentos, pois tendem a impedir operações paralelas. E ser uma entidade separada pode ser uma coisa boa (menos código) ou ruim (onde está o código).

  • Tabelas de junção. Às vezes, eles podem funcionar, mas em breve você estará juntando subconsultas a toneladas de UNIÕES. Grande bagunça.

  • Crie ainda outra visão de uso único, supondo que seus cálculos não exijam dados obtidos no meio da consulta principal.

  • Tabelas intermediárias. Sim ... isso geralmente funciona, e muitas vezes é uma boa opção, pois eles podem ser indexados e rápidos, mas o desempenho também pode diminuir devido ao fato das instruções UPDATE não serem paralelas e não permitirem que as fórmulas em cascata (resultados de reutilização) atualizem vários campos no campo mesma declaração. E, às vezes, você prefere fazer as coisas de uma só vez.

  • Aninhando consultas. Sim, a qualquer momento, você pode colocar parênteses em toda a consulta e usá-la como uma subconsulta na qual é possível manipular os dados de origem e os campos calculados. Mas você só pode fazer isso muito antes que fique feio. Muito feio.

  • Repetindo código. Qual é o maior valor de três instruções longas (CASE ... ELSE ... END)? Isso será legível!

    • Diga a seus clientes que calculem as próprias coisas.

Perdi alguma coisa? Provavelmente, fique à vontade para comentar. Mas, ei, CROSS APPLY é como uma dádiva de Deus em tais situações: basta adicionar um simples CROSS APPLY (select tbl.value + 1 as someFormula) as crossTble pronto! Seu novo campo agora está pronto para uso praticamente como sempre existiu nos dados de origem.

Os valores introduzidos através do CROSS APPLY ...

  • ser usado para criar um ou vários campos calculados sem adicionar problemas de desempenho, complexidade ou legibilidade à mistura
  • Assim como nos JOINs, várias instruções CROSS APPLY subsequentes podem se referir a elas mesmas: CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
  • você pode usar valores introduzidos por um CROSS APPLY em condições JOIN subseqüentes
  • Como bônus, há o aspecto da função com valor de tabela

Dang, não há nada que eles não possam fazer!


1
Este é um grande +1, pois estou surpreso por não ser mencionado com mais frequência. Talvez você possa estender este exemplo para mostrar como você pode executar cálculos "procedimentais" na cadeia de valores derivados? Por exemplo: CROSS Aplicar (seleccionar crossTbl.value * tbl.multiplier como Multiplied) multiTbl - CROSS Aplicar (seleccionar multiTbl.Multiplied / tbl.DerivativeRatio como derivado) derivedTbl - etc ...
mrmillsy

1
Mais informações / exemplos de como usar o Cross Apply como substituto do CASE..ELSE..END?
przemo_li

3
O @przemo_li APPLY pode ser usado para armazenar o resultado de uma declaração de caso (entre outras coisas), a fim de se referir a ela. Uma estrutura pode ser algo como: SELECT CASE when subquery.intermediateResult> 0 THEN "yes" ELSE "no" END from someTable APLICAR EXTERIOR (selecione CASE ... END ... ELSE como intermediárioResult) como subconsulta.
mtone 12/09/16

14

A aplicação cruzada também funciona bem com um campo XML. Se você deseja selecionar valores de nó em combinação com outros campos.

Por exemplo, se você tiver uma tabela contendo alguns xml

<root>
    <subnode1>
       <some_node value="1" />
       <some_node value="2" />
       <some_node value="3" />
       <some_node value="4" />
    </subnode1>
</root>

Usando a consulta

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

Retornará um resultado

xt_id root_attribute_value node_attribute_value lt_name
----------------------------------------------------------------------
1     test1            1                    Benefits
1     test1            4                    FINRPTCOMPANY

13

Isso já foi respondido muito bem tecnicamente, mas deixe-me dar um exemplo concreto de como é extremamente útil:

Digamos que você tenha duas tabelas, Cliente e Pedido. Os clientes têm muitos pedidos.

Quero criar uma exibição que me forneça detalhes sobre os clientes e o pedido mais recente que eles fizeram. Com apenas JOINS, isso exigiria algumas auto-junções e agregação, o que não é bonito. Mas com o Cross Apply, é super fácil:

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

7

A aplicação cruzada pode ser usada para substituir a subconsulta onde você precisa de uma coluna da subconsulta

subconsulta

select * from person p where
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')

aqui não poderei selecionar as colunas da tabela da empresa, usando 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

5

Eu acho que deve ser legibilidade;)

O CROSS APPLY será um pouco exclusivo para as pessoas que estão lendo, dizendo que está sendo usado um UDF que será aplicado a cada linha da tabela à esquerda.

Claro, existem outras limitações em que um CROSS APPLY é melhor usado do que JOIN, que outros amigos postaram acima.


4

Aqui está um artigo que explica tudo, com sua diferença de desempenho e uso em JOINS.

SQL Server CROSS APPLY e OUTTER APPLY sobre JOINS

Conforme sugerido neste artigo, não há diferença de desempenho entre eles para operações normais de junção (INNER AND CROSS).

insira a descrição da imagem aqui

A diferença de uso chega quando você precisa fazer uma consulta como esta:

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)

Ou seja, quando você precisa se relacionar com a função. Isso não pode ser feito usando INNER JOIN, o que causaria o erro "Não foi possível vincular o identificador de várias partes" D.DepartmentID "." Aqui, o valor é passado para a função à medida que cada linha é lida. Parece legal para mim. :)


3

Bem, não tenho certeza se isso se qualifica como um motivo para usar o Cross Apply versus Inner Join, mas essa consulta foi respondida em uma postagem do fórum usando o Cross Apply, portanto, não tenho certeza se existe um método equivalente usando o Inner Join:

Create PROCEDURE [dbo].[Message_FindHighestMatches]

-- Declare the Topical Neighborhood
@TopicalNeighborhood nchar(255)

COMO INICIAR

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

FIM


3

A essência do operador APPLY é permitir a correlação entre os lados esquerdo e direito do operador na cláusula FROM.

Ao contrário de JOIN, a correlação entre entradas não é permitida.

Falando sobre correlação no operador APPLY, quero dizer, do lado direito, que podemos colocar:

  • uma tabela derivada - como uma subconsulta correlacionada com um alias
  • uma função com valor de tabela - uma visão conceitual com parâmetros, onde o parâmetro pode se referir ao lado esquerdo

Ambos podem retornar várias colunas e linhas.


2

Talvez essa seja uma pergunta antiga, mas eu ainda amo o poder do CROSS APPLY de simplificar o reuso da lógica e fornecer um mecanismo de "encadeamento" para resultados.

Forneci um SQL Fiddle abaixo, que mostra um exemplo simples de como você pode usar o CROSS APPLY para executar operações lógicas complexas em seu conjunto de dados sem que as coisas fiquem confusas. Não é difícil extrapolar daqui cálculos mais complexos.

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


1

Enquanto a maioria das consultas que empregam CROSS APPLY pode ser reescrita usando um INNER JOIN, o CROSS APPLY pode gerar um melhor plano de execução e melhor desempenho, pois pode limitar o conjunto que está sendo associado ainda antes que a associação ocorra.

Roubado daqui

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.