CROSS APPLY produce unión externa


17

En respuesta al recuento SQL distinto de la partición, Erik Darling publicó este código para evitarlo por la falta de COUNT(DISTINCT) OVER ():

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY (   SELECT COUNT(DISTINCT mt2.Col_B) AS dc
                FROM   #MyTable AS mt2
                WHERE  mt2.Col_A = mt.Col_A
                -- GROUP BY mt2.Col_A 
            ) AS ca;

La consulta usa CROSS APPLY(no OUTER APPLY), entonces ¿por qué hay una combinación externa en el plan de ejecución en lugar de una combinación interna ?

ingrese la descripción de la imagen aquí

Además, ¿por qué eliminar el comentario del grupo por cláusula resulta en una unión interna?

ingrese la descripción de la imagen aquí

No creo que los datos sean importantes, pero copiando de lo proporcionado por kevinwhat en la otra pregunta:

create table #MyTable (
Col_A varchar(5),
Col_B int
)

insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)

insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)

Respuestas:


23

Resumen

SQL Server utiliza la combinación correcta (interna o externa) y agrega proyecciones cuando sea necesario para respetar toda la semántica de la consulta original al realizar traducciones internas entre aplicar y unir .

Las diferencias en los planes pueden explicarse por la semántica diferente de los agregados con y sin un grupo por cláusula en SQL Server.


Detalles

Unirse vs Aplicar

Tendremos que poder distinguir entre una solicitud y una unión :

  • Aplicar

    La entrada interna (inferior) de la aplicación se ejecuta para cada fila de la entrada externa (superior), con uno o más valores de parámetros del lado interno proporcionados por la fila externa actual. El resultado general de la aplicación es la combinación (unión de todos) de todas las filas producidas por las ejecuciones parametrizadas del lado interno. La presencia de parámetros significa aplicar a veces se denomina unión correlacionada.

    El operador Nested Loops siempre implementa una solicitud en los planes de ejecución . El operador tendrá una propiedad de referencias externas en lugar de unir predicados. Las referencias externas son los parámetros pasados ​​del lado externo al interno en cada iteración del bucle.

  • Unirse

    Una unión evalúa su predicado de unión en el operador de unión. La unión generalmente puede ser implementada por operadores de Hash Match , Merge o Nested Loops en SQL Server.

    Cuando se elige Nested Loops , se puede distinguir de una aplicación por la falta de referencias externas (y generalmente la presencia de un predicado de unión). La entrada interna de una unión nunca hace referencia a valores de la entrada externa: el lado interno todavía se ejecuta una vez para cada fila externa, pero las ejecuciones del lado interno no dependen de ningún valor de la fila externa actual.

Para obtener más detalles, consulte mi publicación Aplicar frente a Nested Loops Join .

... ¿por qué hay una combinación externa en el plan de ejecución en lugar de una combinación interna ?

La combinación externa surge cuando el optimizador transforma una aplicación a una combinación (usando una regla llamada ApplyHandler) para ver si puede encontrar un plan más económico basado en la combinación. Se requiere que la unión sea una unión externa para la corrección cuando la aplicación contiene un agregado escalar . No se garantizaría que una unión interna produzca los mismos resultados que la aplicación original como veremos.

Agregados escalares y vectoriales

  • Un agregado sin una GROUP BYcláusula correspondiente es un agregado escalar .
  • Un agregado con una GROUP BYcláusula correspondiente es un agregado vectorial .

En SQL Server, un agregado escalar siempre producirá una fila, incluso si no tiene filas para agregar. Por ejemplo, el COUNTagregado escalar sin filas es cero. Un conjunto de vectores COUNT sin filas es el conjunto vacío (sin filas).

Las siguientes consultas sobre juguetes ilustran la diferencia. También puede leer más sobre los agregados escalares y vectoriales en mi artículo Diversión con agregados escalares y vectoriales .

-- Produces a single zero value
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1;

-- Produces no rows
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1 GROUP BY ();

demostración de violín db <>

Transformar solicitar unirse

Mencioné antes que la unión debe ser una unión externa para la corrección cuando la aplicación original contiene un agregado escalar . Para mostrar por qué este es el caso en detalle, utilizaré un ejemplo simplificado de la consulta de preguntas:

DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);

INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);

SELECT * FROM @A AS A
CROSS APPLY (SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A) AS CA;

El resultado correcto para la columna ces cero , porque COUNT_BIGes un agregado escalar . Al traducir esta consulta de solicitud al formulario de unión, SQL Server genera una alternativa interna que se vería similar a la siguiente si se expresara en T-SQL:

SELECT A.*, c = COALESCE(J1.c, 0)
FROM @A AS A
LEFT JOIN
(
    SELECT B.A, c = COUNT_BIG(*) 
    FROM @B AS B
    GROUP BY B.A
) AS J1
    ON J1.A = A.A;

Para reescribir la aplicación como una unión no correlacionada, tenemos que introducir una GROUP BYen la tabla derivada (de lo contrario, no podría haber una Acolumna para unir). La unión tiene que ser una unión externa para que cada fila de la tabla @Acontinúe produciendo una fila en la salida. La combinación izquierda producirá una NULLcolumna for ccuando el predicado de combinación no se evalúe como verdadero. Eso NULLnecesita ser traducido a cero COALESCEpara completar una transformación correcta de aplicar .

La demostración a continuación muestra cómo COALESCEse requieren tanto la combinación externa como la combinación para producir los mismos resultados utilizando la combinación como la consulta de aplicación original :

demostración de violín db <>

Con el GROUP BY

... ¿por qué descomentar el grupo por la cláusula resulta en una unión interna?

Continuando con el ejemplo simplificado, pero agregando un GROUP BY:

DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);

INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);

-- Original
SELECT * FROM @A AS A
CROSS APPLY 
(SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A GROUP BY B.A) AS CA;

El COUNT_BIGes ahora un vector agregado, por lo que el resultado correcto para un conjunto de entrada vacío ya no es cero, es ninguna fila en absoluto . En otras palabras, ejecutar las declaraciones anteriores no produce ningún resultado.

Esta semántica es mucho más fácil de cumplir cuando se traduce de aplicar a unir , ya CROSS APPLYque rechaza naturalmente cualquier fila externa que no genere filas laterales internas. Por lo tanto, ahora podemos usar de forma segura una combinación interna, sin proyección de expresión adicional:

-- Rewrite
SELECT A.*, J1.c 
FROM @A AS A
JOIN
(
    SELECT B.A, c = COUNT_BIG(*) 
    FROM @B AS B
    GROUP BY B.A
) AS J1
    ON J1.A = A.A;

La demostración a continuación muestra que la reescritura de unión interna produce los mismos resultados que la aplicación original con agregado vectorial:

demostración de violín db <>

El optimizador elige una combinación interna de combinación con la tabla pequeña porque encuentra un plan de combinación barato rápidamente ( se encontró un plan lo suficientemente bueno). El optimizador basado en el costo puede continuar para reescribir la unión de nuevo a una solicitud, tal vez encontrar un plan de solicitud más barato, como lo hará aquí si se usa una unión de bucle o una sugerencia de fuerza de búsqueda, pero no vale la pena el esfuerzo en este caso.

Notas

Los ejemplos simplificados usan diferentes tablas con diferentes contenidos para mostrar las diferencias semánticas más claramente.

Se podría argumentar que el optimizador debería ser capaz de razonar acerca de que una autounión no sea capaz de generar filas no coincidentes (no unidas), pero hoy no contiene esa lógica. No se garantiza que acceder a la misma tabla varias veces en una consulta produzca los mismos resultados en general de todos modos, dependiendo del nivel de aislamiento y la actividad concurrente.

El optimizador se preocupa por estas semánticas y casos extremos para que no tenga que hacerlo.


Bonificación: Plan de aplicación interna

SQL Server puede producir un plan de aplicación interno (¡no un plan de combinación interno !) Para la consulta de ejemplo, simplemente elige no hacerlo por razones de costo. El costo del plan de combinación externa que se muestra en la pregunta es de 0.02898 unidades en la instancia de SQL Server 2017 de mi computadora portátil.

Puede forzar un plan de aplicación (unión correlacionada) utilizando el indicador de traza 9114 no documentado y no admitido (que deshabilita, ApplyHandleretc.) solo como ilustración:

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY 
(
    SELECT COUNT_BIG(DISTINCT mt2.Col_B) AS dc
    FROM   #MyTable AS mt2
    WHERE  mt2.Col_A = mt.Col_A 
    --GROUP BY mt2.Col_A
) AS ca
OPTION (QUERYTRACEON 9114);

Esto produce un plan de bucles anidados de aplicación con un carrete de índice diferido. El costo total estimado es 0.0463983 (más alto que el plan seleccionado):

Plan de aplicación de Index Spool

Tenga en cuenta que el plan de ejecución que utiliza bucles anidados aplica produce resultados correctos utilizando la semántica de "unión interna" independientemente de la presencia de la GROUP BYcláusula.

En el mundo real, normalmente tendríamos un índice para admitir una búsqueda en el lado interno de la solicitud para alentar a SQL Server a elegir esta opción de forma natural, por ejemplo:

CREATE INDEX i ON #MyTable (Col_A, Col_B);

demostración de violín db <>


-3

La aplicación cruzada es una operación lógica en los datos. Al decidir cómo obtener esos datos, SQL Server elige el operador físico apropiado para obtener los datos que desea.

No hay un operador de aplicación física y SQL Server lo traduce en el operador de unión apropiado y con suerte eficiente.

Puede encontrar una lista de los operadores físicos en el siguiente enlace.

https://docs.microsoft.com/en-us/sql/relational-databases/showplan-logical-and-physical-operators-reference?view=sql-server-2017

El optimizador de consultas crea un plan de consultas como un árbol que consta de operadores lógicos. Después de que el optimizador de consultas crea el plan, el optimizador de consultas elige el operador físico más eficiente para cada operador lógico. El optimizador de consultas utiliza un enfoque basado en el costo para determinar qué operador físico implementará un operador lógico.

Por lo general, una operación lógica puede ser implementada por múltiples operadores físicos. Sin embargo, en casos excepcionales, un operador físico también puede implementar múltiples operaciones lógicas.

editar / Parece que entendí mal tu pregunta. El servidor SQL normalmente elegirá el operador más apropiado. Su consulta no necesita devolver valores para todas las combinaciones de ambas tablas, que es cuando se usaría una combinación cruzada. Basta con calcular el valor que desea para cada fila, que es lo que se hace aquí.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.