Resultados inesperados con números aleatorios y tipos de unión.

16

Tengo un script simple que obtiene cuatro números aleatorios (1 a 4) y luego se une para obtener el número correspondiente de database_id. Cuando ejecuto el script con un LEFT JOIN, obtengo cuatro filas cada vez (el resultado esperado). Sin embargo, cuando lo ejecuto con un INNER JOIN, obtengo un número variable de filas, a veces dos, a veces ocho.

Lógicamente, no debería haber ninguna diferencia porque sé que existen filas con database_ids 1-4 en sys.databases. Y debido a que estamos seleccionando de la tabla de números aleatorios con cuatro filas (en lugar de unirlas), nunca debería haber más de cuatro filas devueltas.

Esto sucede tanto en SQL Server 2012 como en 2014. ¿Qué está causando que INNER JOIN devuelva un número variable de filas?

/* Works as expected -- always four rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
LEFT JOIN sys.databases d ON rando.RandomNumber = d.database_id;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id;

/* Also returns a varying number of rows */

WITH rando AS (
  SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
  FROM sys.databases WHERE database_id <= 4
)

SELECT r.RandomNumber, d.database_id
FROM rando AS r
INNER JOIN sys.databases d ON r.RandomNumber = d.database_id;
Doug Lane
fuente
3
Otra forma de obtener siempre 4 filas: SELECT TOP (4) d.database_id FROM sys.databases AS d CROSS JOIN (VALUES (1),(2),(3),(4)) AS multi (i) WHERE d.database_id <= 4 ORDER BY CHECKSUM(NEWID()) ;supongo que funciona bien porque no hay una unión en el valor de la función no determinista.
ypercubeᵀᴹ

Respuestas:

9

Al agregar el SELECT adicional, empuja la evaluación escalar de cálculo más profundamente en el plan y le da el predicado de unión, el escalar de cálculo en la parte superior luego hace referencia al anterior.

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT ( SELECT 1 + ABS(CHECKSUM(NEWID())) % (4)) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id

|--Compute Scalar(DEFINE:([Expr1071]=[Expr1070]))

|--Compute Scalar(DEFINE:([Expr1070]=(1)+abs(checksum(newid()))%(4)))

Todavía estoy investigando por qué espera tan tarde para hacerlo, pero actualmente leyendo esta publicación de Paul White ( https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html ) . ¿Quizás tiene algo que ver con el hecho de que NEWID no es determinista?

John Q Martin
fuente
12

Esto podría dar una idea hasta que una de las personas más inteligentes del sitio intervenga.

Puse los resultados aleatorios en una tabla temporal y constantemente obtengo 4 resultados independientemente del tipo de combinación.

/* Works as expected -- always four rows */

DECLARE @Rando table
(
    RandomNumber int
);

INSERT INTO
    @Rando
(
    RandomNumber
)
-- This generates 4 random numbers from 1 to 4, endpoints inclusive
SELECT
    1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
FROM
    sys.databases
WHERE
    database_id <= 4;

SELECT
    *
FROM
    @Rando AS R;

SELECT
    rando.RandomNumber
,   d.database_id
FROM 
    @Rando AS rando
    LEFT JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
    @Rando AS rando
    INNER JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;

/* Also returns a varying number of rows */

WITH rando AS 
(
    SELECT * FROM @Rando AS rando
)
SELECT r.RandomNumber, d.database_id
FROM 
    rando AS r
    INNER JOIN 
        sys.databases d 
        ON r.RandomNumber = d.database_id
ORDER BY 1,2;

Si comparo planes de consulta entre su segunda consulta y la variación con una variable de tabla, puedo ver que hay una diferencia definitiva entre las dos. La X roja es No Join Predicatemuy extraña para mi cerebro desarrollador de cavernícolas

ingrese la descripción de la imagen aquí

Si elimino el bit aleatorio de la consulta a una constante 1 % (4), mi plan se ve mejor, pero se eliminó Compute Scalar, lo que me llevó a mirar más de cerca

ingrese la descripción de la imagen aquí

Está calculando la expresión para el número aleatorio después de la unión. Ya sea que eso sea esperado, aún dejo a los asistentes internos en el sitio, pero al menos es por eso que obtienes resultados variables en tu unión.

2014

Para aquellos que juegan en casa, los planes de consulta anteriores se generaron a partir de una instancia de 2008 R2. Los planes de 2014 se ven diferentes, pero la operación Compute Scalar permanece después de la unión.

Este es el plan de consulta para un 2014 usando la expresión constante

ingrese la descripción de la imagen aquí

Este es el plan de consulta para una instancia de 2014 utilizando la expresión newid.

ingrese la descripción de la imagen aquí

Esto aparentemente es por diseño, conecte el problema aquí. Gracias a @paulWhite por saber que existía.

billinkc
fuente
1
Correcto, exactamente, eso es lo que está sucediendo, pero definitivamente no se espera. Los resultados no coinciden con el T-SQL que se está pasando y, por lo tanto, con la pregunta.
Brent Ozar
Incluso reemplazar el número aleatorio con un 1 estático le da al operador de unión sin predicado de unión
James Anderson
Parece que estás en algo. Incluso el uso de OPCIÓN (ORDEN DE FUERZA) no cambia el comportamiento: el número aleatorio todavía se calcula al final ...
Jeremiah Peschka
Al eliminar el sys.databases TVF, lo siguiente produce el mismo plan: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremiah Peschka
Esto suena como un problema de precedencia del operador
James Anderson