2009-07-16 170 views
722

¿Cuál es el propósito principal de usar CROSS APPLY?¿Cuándo debo usar Cross Apply over Inner Join?

He leído (vagamente, a través de publicaciones en Internet) que cross apply puede ser más eficiente al seleccionar conjuntos de datos grandes si está creando particiones. (Paging viene a la mente)

también sé que CROSS APPLYdoesn't require a UDF as the right-table.

En la mayoría de INNER JOIN consultas (uno a muchos), pude volver a extender el uso CROSS APPLY, pero siempre me dan los planes de ejecución equivalentes.

¿Alguien me puede dar un buen ejemplo de cuándo CROSS APPLY hace una diferencia en aquellos casos en que INNER JOIN funcionará también?


Editar:

Aquí está un ejemplo trivial, donde los planes de ejecución son exactamente los mismos. (Muéstrame una en la que se diferencian y donde cross apply es más rápido/más 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 
+41

Sé que esto es incluso PICKIER de mí, pero 'performant' es definitivamente una palabra. Simplemente no está relacionado con la eficiencia. – Rire1979

+2

Es muy útil para sql xquery. compruebe [esto] (http://stackoverflow.com/a/10511719/474679). – ARZ

+2

Parece que usar "unión de bucle interno" sería muy similar a la aplicación cruzada. Me gustaría que tu ejemplo detallara qué sugerencia de unión era equivalente. Simplemente diciendo join podría resultar en inner/loop/merge o incluso "other" porque puede volver a organizarse con otras uniones. – crokusek

Respuesta

535

¿Puede alguien darme un buen ejemplo de cuando CROSS APPLY hace una diferencia en aquellos casos en combinación interna funcionará tan bien?

Ver el artículo en mi blog para la comparación detallada de rendimiento:

CROSS APPLY funciona mejor en cosas que no tienen la condición sencilla JOIN.

Esta uno selecciona 3 últimos registros de t2 para cada registro de 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 

No se puede formular fácilmente con una condición INNER JOIN.

Probablemente podría hacer algo como que el uso de CTE 's y ventana de funciones:

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 

, pero esto es menos legible y probablemente menos eficiente.

Actualización:

acaba de comprobar.

master es una tabla de aproximadamente 20,000,000 registros con PRIMARY KEY en 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 

tiene una duración de casi 30 segundos, mientras que éste:

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 

es instantánea.

+1

Ver el final del enlace de Ariel. Una consulta row_number() es igual de bonita y ni siquiera requiere una unión. Por lo tanto, no creo que deba usar cross apply para esta situación (seleccione top 3, partition by t1.id). –

+1

¡Buen ejemplo! El aumento en el rendimiento bruto es muy evidente. ¿Cómo difieren los planes de ejecución? –

+0

@Jeff: difieren mucho, como se puede ver :) 'JOIN' usa' NESTED LOOPS' con 'master' como una tabla principal,' CROSS APPLY' también usa 'NOOED LOOPS' pero' t' es leading y 'TOP 'se aplica a' maestro' en cada ciclo. – Quassnoi

5

supongo que debe ser la legibilidad;)

CROSS APPLY será algo único para la gente que lee decirles que una UDF se está utilizando que será aplicado a cada fila de la tabla de la izquierda.

Por supuesto, hay otras limitaciones donde se aplica mejor una SOLICITUD CRUZ que JOIN que otros amigos publicaron anteriormente.

170

cross apply a veces le permite hacer cosas que no puede hacer con inner join.

Ejemplo (un error de sintaxis):

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

Este es un error sintaxis, porque, cuando se utiliza con inner join, funciones de tabla sólo puede tener variables o constantes como parámetros. (Es decir, el parámetro de función de tabla no puede depender de la columna de otra tabla.)

Sin embargo:

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

Esto es legal.

Editar: O, alternativamente, la sintaxis más corta: (por Erike)

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

Editar:

Nota: Informix 12.10 xc2 + tiene Lateral Derived Tables y PostgreSQL (9.3+) tiene Lateral Subqueries que se puede usar para un efecto similar.

+11

. Creo que este es el razonamiento detrás de por qué aplicamos cross. Si consulta el siguiente enlace, esto es lo primero que dice MS sobre cross-apply. Puede tener otros usos, pero creo que esta es la razón por la que se introdujo. Sin él, las funciones de la tabla no serían utilizables en muchas situaciones. http://technet.microsoft.com/en-us/library/ms175156.aspx – MikeKulls

+0

cross-apply también produce un plan de ejecución agradable cuando se combina con funciones de tabla en línea, manteniendo la modularidad necesaria. – nurettin

+11

No se necesita 'SELECT' dentro de' CROSS Apply '. Intenta con 'CROSS APPLY dbo.myTableFun (O.name) F'. – ErikE

2

Bueno, no estoy seguro si esto califica como una razón para usar Cross Apply versus Inner Join, pero esta consulta fue respondida en una publicación del foro usando Cross Apply, así que no estoy seguro si hay un método equalivent combinación interna:

Create PROCEDURE [dbo].[Message_FindHighestMatches] 

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

COMO EMPEZAR

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

FIN

30

me parece que CROSS APPLY puede llenar un cierto vacío cuando la elaboració ng con campos calculados en consultas complejas/anidadas, y los hace más simples y más legibles.

Ejemplo simple: tiene un DoB y desea presentar varios campos relacionados con la edad que también se basarán en otras fuentes de datos (como empleo), como edad, grupo de edad, antigüedad, edad mínima de jubilación, etc. para usar en su aplicación de usuario final (Excel PivotTables, por ejemplo).

opciones son limitadas y rara vez elegante:

  • UNIRSE subconsultas no pueden introducir nuevos valores en el conjunto de datos basado en datos de la consulta de los padres (que debe sostenerse por sí mismo).

  • UDF son ordenadas, pero lento ya que tienden a evitar operaciones paralelas. Y ser una entidad separada puede ser una cosa buena (menos código) o mala (dónde está el código).

  • Junction tables. A veces pueden funcionar, pero pronto se están uniendo subconsultas con toneladas de UNION. Gran desorden.

  • crear otra vista de una sola finalidad, suponiendo que sus cálculos no requieren datos obtenidos a mitad de camino a través de la consulta principal.

  • Mesas intermedias. Sí ... eso generalmente funciona, y a menudo es una buena opción, ya que pueden indexarse ​​y acelerarse, pero el rendimiento también puede disminuir debido a que las instrucciones UPDATE no son paralelas y no permiten la cascada de fórmulas (reutilización de resultados) para actualizar varios campos dentro del misma declaración. Y a veces preferirías hacer las cosas de una vez.

  • Solicitud de jerarquización. Sí, en cualquier punto puede poner paréntesis en toda su consulta y usarla como una subconsulta sobre la cual puede manipular los datos de origen y los campos calculados por igual. Pero solo puedes hacer esto mucho antes de que se ponga feo. Muy feo.

  • Código de repetición. ¿Cuál es el mayor valor de 3 declaraciones largas (CASE ... ELSE ... END)? ¡Eso será legible!

    • Dile a tus clientes que calculen las cosas por sí mismos.

¿Me he perdido algo? Probablemente, así que siéntete libre de comentar. Pero bueno, CROSS APPLY es como un regalo del cielo en tales situaciones: ¡simplemente agregas un simple CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl y voilà! Su nuevo campo ahora está listo para usar prácticamente como si siempre hubiera estado allí en sus datos de origen.

Los valores introducidos a través de CROSS APPLY pueden ...

  • usarse para crear uno o varios campos calculados sin añadir problemas de rendimiento, complejidad o de legibilidad a la mezcla
  • como con juntas, varios CRUZ posterior APLICA declaraciones se refieren a sí mismos: CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
  • puede utilizar valores introducidos por un CROSS se aplican en condiciones posteriores UNIRSE
  • Como beneficio adicional, existe la función de aspecto con valores de tabla

Dang, hay noth ¡No pueden hacerlo!

+0

Este es un gran +1 de mi parte, ya que me sorprende que no se mencione con más frecuencia. ¿Quizás podría extender este ejemplo para mostrar cómo puede realizar cálculos "procedimentales" en la cadena de valores derivados? Ejemplo: CROSS APPLY (seleccione crossTbl.value * tbl.multiplier como Multiplied) multiTbl - CROSS APPLY (seleccione multiTbl.Multiplied/tbl.DerivativeRatio as Derived) derivedTbl - etc ... – mrmillsy

+0

Más información/ejemplos sobre cómo usar Cross Apply como reemplazo de CASE..ELSE..END? –

+1

@przemo_li APLICAR se puede usar para almacenar el resultado de una declaración de caso (entre otras cosas) para referirse a ella. Una estructura podría ser algo así como: SELECCIONAR CASO cuando subconsulta.Resultado intermedio> 0 ENTONCES "sí" ELSE "no" END FROM someTable APLICACIÓN EXTERIOR (seleccione CASE ... END ... ELSE como intermediateResult) como subconsulta. – mtone

12

La aplicación cruzada también funciona bien con un campo XML. Si desea seleccionar valores de nodo en combinación con otros campos.

Por ejemplo, si usted tiene una tabla que contiene algunos xml

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

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

devolverá un resultado

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

aplicar Cruz lata ser usado para reemplazar ce de donde se necesita una columna de la subconsulta

subconsulta

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

aquí no voy a ser capaz de seleccionar las columnas de la tabla empresa así, el uso de cruz aplica

select P.*,T.CompanyName 
from Person p 
cross apply (
    select * 
    from Company C 
    where p.companyid = c.companyId and c.CompanyName like '%yyy%' 
) T 
95

Considere subconsulta tienes dos tablas.

tabla maestra de

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

Tabla detallada

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 

Hay muchas situaciones en las que necesitamos para reemplazar INNER JOIN con CROSS APPLY.

1. Unir dos tablas basadas en TOP n resultados

considerar si tenemos que seleccionar Id y Name de Master y las últimas dos fechas para cada Id de 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 

La consulta anterior genera el siguiente 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 

Véase, generó resultados para últimos dos fechas con la última fecha de dos Id y luego se unió a estos registros sólo en la consulta externa de Id, lo cual es incorrecto. Para lograr esto, necesitamos 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 

y forma el siguiente 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 

Así es como funciona. La consulta dentro de CROSS APPLY puede hacer referencia a la tabla externa, donde INNER JOIN no puede hacer esto (arroja un error de compilación). Al encontrar las dos últimas fechas, la unión se realiza dentro de CROSS APPLY, es decir, WHERE M.ID=D.ID.

2. Cuando necesitamos la funcionalidad INNER JOIN usando funciones.

CROSS APPLY puede ser utilizado como un reemplazo con INNER JOIN cuando necesitamos obtener resultado de Master mesa y una function.

Y aquí es la función

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

que generó el siguiente 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 

ventaja adicional de CROSS APPLY

APPLY se puede utilizar como un sustituto de UNPIVOT. O bien CROSS APPLY o OUTER APPLY se pueden usar aquí, que son intercambiables.

Considera que tienes la siguiente tabla (llamada 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 

La consulta está por debajo.

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

que trae el 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 
+2

Excelente ejemplo con los registros 2 vs 4 y me ayudó a entender el contexto en el que sería necesario. – trnelson

0

Ésta es quizás una vieja pregunta, pero todavía me encanta el poder de CROSS APPLY para simplificar la re -utilizar la lógica y proporcionar un mecanismo de "encadenamiento" para obtener resultados.

He proporcionado un SQL Fiddle a continuación que muestra un ejemplo simple de cómo puede usar CROSS APPLY para realizar operaciones lógicas complejas en su conjunto de datos sin que las cosas se vuelvan desordenadas. No es difícil extrapolar desde aquí cálculos más complejos.

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

3

Aquí es un artículo que lo explica todo, con su diferencia de rendimiento y el uso de más de UNE.

SQL Server CROSS APPLY and OUTER APPLY over JOINS

Como se sugiere en este artículo, no hay ninguna diferencia de rendimiento entre ellos para unirse a las operaciones normales (interior y cruz).

enter image description here

La diferencia llega uso cuando se tiene que hacer una 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) 

Es decir, cuando se tiene que relacionar con la función. Esto no puede hacerse usando INNER JOIN, que le daría el error "El identificador de varias partes" D.DepartmentID "no se pudo enlazar." Aquí el valor se pasa a la función a medida que se lee cada fila. Me parece genial. :)