Résultats inattendus avec des nombres aléatoires et des types de jointure

16

J'ai un script simple qui obtient quatre nombres aléatoires (1 à 4), puis se joint à nouveau pour obtenir le numéro de database_id correspondant. Lorsque j'exécute le script avec un LEFT JOIN, j'obtiens à chaque fois quatre lignes (le résultat attendu). Cependant, lorsque je l'exécute avec une INNER JOIN, j'obtiens un nombre variable de lignes - parfois deux, parfois huit.

Logiquement, il ne devrait pas y avoir de différence car je sais que des lignes avec database_ids 1-4 existent dans sys.databases. Et parce que nous sélectionnons dans la table des nombres aléatoires avec quatre lignes (par opposition à la joindre à elle), il ne devrait jamais y avoir plus de quatre lignes retournées.

Cela se produit à la fois dans SQL Server 2012 et 2014. Qu'est-ce qui pousse INNER JOIN à retourner un nombre variable de lignes?

/* 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
la source
3
Une autre façon d'obtenir toujours 4 lignes: 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()) ;je suppose que cela fonctionne bien car il n'y a pas de jointure sur la valeur de la fonction non déterministe.
ypercubeᵀᴹ du

Réponses:

9

En ajoutant le SELECT supplémentaire, il pousse l'évaluation scalaire de calcul plus profondément dans le plan et donne le prédicat de jointure, le scalaire de calcul en haut fait ensuite référence à la précédente.

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

Je cherche toujours à savoir pourquoi il attend si tard pour le faire, mais je lis actuellement cet article de Paul White ( https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html ) . Peut-être que cela a à voir avec le fait que NEWID n'est pas déterministe?

John Q Martin
la source
12

Cela pourrait donner un aperçu jusqu'à ce que l'une des personnes les plus intelligentes du site se lance.

Je mets les résultats aléatoires dans une table temporaire et j'obtiens systématiquement 4 résultats quel que soit le type de jointure.

/* 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 je compare les plans de requête entre votre deuxième requête et la variation avec une variable de table, je peux voir qu'il y a une nette différence entre les deux. Le X rouge est No Join Predicatetellement étrange pour mon développeur de l'homme des cavernes

entrez la description de l'image ici

Si j'élimine le bit aléatoire de la requête à une constante 1 % (4), mon plan semble meilleur mais le calcul scalaire a été éliminé, ce qui m'a amené à regarder de plus près

entrez la description de l'image ici

Il calcule l'expression du nombre aléatoire après la jointure. Que ce soit prévu, je laisse toujours aux assistants internes sur le site, mais au moins c'est pourquoi vous obtenez des résultats variables dans votre jointure.

2014

Pour ceux qui jouent à la maison, les plans de requête ci-dessus ont été générés à partir d'une instance 2008 R2. Les plans de 2014 sont différents, mais l'opération Compute Scalar reste après la jointure.

Ceci est le plan de requête pour un 2014 en utilisant l'expression constante

entrez la description de l'image ici

Il s'agit du plan de requête pour une instance 2014 utilisant l'expression newid.

entrez la description de l'image ici

Apparemment, c'est par conception, problème de connexion ici. Merci à @paulWhite de savoir que cela existait.

billinkc
la source
1
Exactement - c'est ce qui se passe, mais ce n'est certainement pas prévu. Les résultats ne correspondent pas au T-SQL transmis, et donc à la question.
Brent Ozar
Même le remplacement du nombre aléatoire par un 1 statique donne à l'opérateur de jointure sans prédicat de jointure
James Anderson
On dirait que vous êtes sur quelque chose. Même l'utilisation de OPTION (FORCE ORDER) ne change pas le comportement - le nombre aléatoire est toujours calculé en dernier ...
Jeremiah Peschka
En supprimant la TVF sys.databases, ce qui suit produit le même plan: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremiah Peschka
Cela ressemble à un problème de priorité de l'opérateur
James Anderson