Resultados inesperados com números aleatórios e tipos de junção

16

Eu tenho um script simples que obtém quatro números aleatórios (1 a 4) e depois se junta novamente para obter o número database_id correspondente. Quando executo o script com LEFT JOIN, recebo quatro linhas sempre (o resultado esperado). No entanto, quando o executo com um INNER JOIN, recebo um número variável de linhas - às vezes duas, às vezes oito.

Logicamente, não deve haver nenhuma diferença, porque eu sei que existem linhas com database_ids 1-4 no sys.databases. E como estamos selecionando da tabela de números aleatórios com quatro linhas (em vez de ingressar nela), nunca deve haver mais do que quatro linhas retornadas.

Isso acontece no SQL Server 2012 e 2014. O que está fazendo com que o INNER JOIN retorne vários números de linhas?

/* 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
fonte
3
Outra maneira de obter sempre 4 linhas: 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()) ;acho que funciona bem porque não há junção no valor da função não determinística.
ypercubeᵀᴹ

Respostas:

9

Ao adicionar o SELECT adicional, ele aprimora a avaliação escalar de computação no plano e fornece o predicado de junção; o escalar de computação na parte superior faz referência à 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)))

Ainda procurando por que demora tanto para fazê-lo, mas atualmente lendo este post de Paul White ( https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html ) . Talvez tenha algo a ver com o fato de o NEWID não ser determinístico?

John Q Martin
fonte
12

Isso pode dar algumas dicas, até que uma das pessoas mais inteligentes do site faça a diferença.

Coloco os resultados aleatórios em uma tabela temporária e recebo consistentemente 4 resultados, independentemente do tipo de associação.

/* 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;

Se eu comparar planos de consulta entre sua segunda consulta e a variação com uma variável de tabela, posso ver que há uma diferença definida entre as duas. O X vermelho é No Join Predicatetão estranho para o meu cérebro desenvolvedor de homens das cavernas

insira a descrição da imagem aqui

Se eu eliminar o bit aleatório da consulta para uma constante 1 % (4), meu plano parecerá melhor, mas o Escalar de computação foi eliminado, o que me levou a olhar mais de perto

insira a descrição da imagem aqui

Está computando a expressão para o número aleatório após a junção. Se isso é esperado, ainda deixo para os assistentes internos do site, mas pelo menos é por isso que você está obtendo resultados variáveis ​​em sua associação.

2014

Para aqueles que tocam em casa, os planos de consulta acima foram gerados a partir de uma instância do 2008 R2. Os planos de 2014 parecem diferentes, mas a operação de computação escalar permanece após a associação.

Este é o plano de consulta para um 2014 usando a expressão constante

insira a descrição da imagem aqui

Este é o plano de consulta para uma instância de 2014 usando a expressão newid.

insira a descrição da imagem aqui

Aparentemente, isso ocorre por design, problema do Connect aqui. Agradecemos a @paulWhite por saber que existia.

billinkc
fonte
11
Certo, exatamente - é isso que está acontecendo, mas definitivamente não é esperado. Os resultados não correspondem ao T-SQL que está sendo passado e, portanto, à questão.
Brent Ozar
Mesmo substituindo o número aleatório com uma estática 1 dá ao operador juntar-se sem predicado
James Anderson
Parece que você está no caminho certo. Mesmo usando OPÇÃO (FORCE ORDER) não altera o comportamento - o número aleatório ainda está última computadorizada ...
Jeremias Peschka
Remoção do TVF sys.databases, o seguinte produz o mesmo plano: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremias Peschka
Isso soa como um problema de precedência do operador
James Anderson