Unerwartete Ergebnisse mit Zufallszahlen und Verknüpfungstypen

16

Ich habe ein einfaches Skript, das vier Zufallszahlen (1 bis 4) abruft und sich dann wieder zusammenfügt, um die passende database_id-Nummer zu erhalten. Wenn ich das Skript mit LEFT JOIN ausführe, erhalte ich jedes Mal vier Zeilen zurück (das erwartete Ergebnis). Wenn ich es jedoch mit einem INNER JOIN ausführe, erhalte ich eine unterschiedliche Anzahl von Zeilen - manchmal zwei, manchmal acht.

Logischerweise sollte es keinen Unterschied geben, da ich weiß, dass Zeilen mit den Datenbank-IDs 1 bis 4 in sys.databases vorhanden sind. Und da wir aus der Zufallszahltabelle mit vier Zeilen auswählen (im Gegensatz zum Beitritt), sollten niemals mehr als vier Zeilen zurückgegeben werden.

Dies tritt sowohl in SQL Server 2012 als auch in 2014 auf. Warum gibt INNER JOIN eine unterschiedliche Anzahl von Zeilen zurück?

/* 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
quelle
3
Ein anderer Weg, um immer 4 Zeilen zu erhalten: 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()) ;Ich denke, es funktioniert gut, weil es keinen Join für den Wert der nicht deterministischen Funktion gibt.
ypercubeᵀᴹ

Antworten:

9

Durch Hinzufügen des zusätzlichen SELECT wird die Bewertung des Berechnungsskalars tiefer in den Plan eingefügt und das Join-Prädikat angegeben. Der Berechnungsskalar oben verweist dann auf das frühere Prädikat.

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

Ich beschäftige mich immer noch mit dem Grund, warum es so spät ist, aber ich lese gerade diesen Beitrag von Paul White ( https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html ) . Vielleicht hat es etwas damit zu tun, dass NEWID nicht deterministisch ist?

John Q Martin
quelle
12

Dies könnte einige Einblicke geben, bis einer der klügeren Leute auf der Website eingreift.

Ich füge die zufälligen Ergebnisse in eine temporäre Tabelle ein und erhalte konstant 4 Ergebnisse, unabhängig vom Join-Typ.

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

Wenn ich Abfragepläne zwischen Ihrer zweiten Abfrage und der Variation mit einer Tabellenvariablen vergleiche, kann ich einen deutlichen Unterschied zwischen den beiden feststellen. Das rote X ist No Join Predicateso, dass es meinem Höhlenmenschen-Entwicklerhirn wirklich seltsam vorkommt

Bildbeschreibung hier eingeben

Wenn ich das zufällige Bit der Abfrage in einer Konstante 1 % (4)eliminiere, sieht mein Plan besser aus, aber der Berechnungsskalar wurde eliminiert, sodass ich genauer hinschaue

Bildbeschreibung hier eingeben

Es berechnet den Ausdruck für die Zufallszahl nach dem Join. Ob das erwartet wird, überlasse ich immer noch den internen Assistenten auf der Site, aber zumindest aus diesem Grund erhalten Sie variable Ergebnisse in Ihrem Join.

2014

Für diejenigen, die zu Hause mitspielen, wurden die obigen Abfragepläne aus einer 2008 R2-Instanz generiert. Die Pläne für 2014 sehen anders aus, aber der Vorgang "Skalar berechnen" bleibt nach dem Beitritt bestehen.

Dies ist der Abfrageplan für 2014 unter Verwendung des konstanten Ausdrucks

Bildbeschreibung hier eingeben

Dies ist der Abfrageplan für eine 2014-Instanz, die den Ausdruck newid verwendet.

Bildbeschreibung hier eingeben

Dies ist anscheinend beabsichtigt, Connect Problem hier. Vielen Dank an @paulWhite für das Wissen, dass es das gab.

billinkc
quelle
1
Genau das ist es, was passiert, aber es wird definitiv nicht erwartet. Die Ergebnisse stimmen nicht mit dem übergebenen T-SQL und damit der Frage überein.
Brent Ozar
Selbst wenn die Zufallszahl durch eine statische 1 ersetzt wird, hat der Join-Operator kein Join-Prädikat
James Anderson,
Es sieht so aus, als ob du auf etwas stehst. Auch mit OPTION (FORCE ORDER) ändert sich das Verhalten nicht - die Zufallszahl wird immer noch als letztes berechnet ...
Jeremiah Peschka
Wenn Sie die Datei sys.databases TVF entfernen, erhalten Sie den gleichen Plan: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremiah Peschka
Dies klingt wie ein Operator-Vorrang-Problem
James Anderson