Risultati imprevisti con numeri casuali e tipi di join


16

Ho un semplice script che ottiene quattro numeri casuali (da 1 a 4) e poi si unisce per ottenere il numero database_id corrispondente. Quando eseguo lo script con un JOIN SINISTRO, ottengo quattro righe indietro ogni volta (il risultato previsto). Tuttavia, quando lo eseguo con un INNER JOIN, ottengo un numero variabile di righe - a volte due, a volte otto.

Logicamente, non dovrebbe esserci alcuna differenza perché so che esistono file con database_ids 1-4 nei database sys.d. E poiché stiamo selezionando dalla tabella dei numeri casuali con quattro righe (anziché unirci ad essa), non dovrebbero mai esserci più di quattro righe restituite.

Ciò accade in SQL Server 2012 e 2014. Cosa sta causando INNER JOIN per restituire un numero variabile di righe?

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

3
Un altro modo per ottenere sempre 4 righe: 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()) ;immagino che funzioni bene perché non esiste un join sul valore della funzione non deterministica.
ypercubeᵀᴹ

Risposte:


9

Aggiungendo il tasto SELECT aggiuntivo spinge la valutazione scalare di calcolo più in profondità nel piano e fornisce il predicato di join, lo scalare di calcolo in alto quindi fa riferimento a quello precedente.

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

Sto ancora cercando il motivo per cui aspetta così tardi per farlo, ma attualmente sto leggendo questo post di Paul White ( https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html ) . Forse ha qualcosa a che fare con il fatto che NEWID non è deterministico?


12

Ciò potrebbe fornire alcune informazioni fino a quando una delle persone più intelligenti sul sito interviene.

Metto i risultati casuali in una tabella temporanea e ottengo costantemente 4 risultati indipendentemente dal tipo di join.

/* 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 confronto i piani di query tra la tua seconda query e la variazione con una variabile di tabella, vedo che c'è una differenza netta tra i due. La X rossa è No Join Predicatecosì strana per il mio cervello sviluppatore di cavernicoli

inserisci qui la descrizione dell'immagine

Se elimino il bit casuale della query su una costante 1 % (4), il mio piano sembra migliore ma lo scalare di calcolo è stato eliminato in modo da farmi guardare più da vicino

inserisci qui la descrizione dell'immagine

Sta calcolando l'espressione per il numero casuale dopo il join. Se è previsto, lascio ancora alle procedure guidate interne sul sito, ma almeno è per questo che stai ottenendo risultati variabili nel tuo join.

2014

Per quelli che giocano a casa, i piani di query sopra riportati sono stati generati da un'istanza di 2008 R2. I piani del 2014 sembrano diversi ma l'operazione di calcolo scalare rimane dopo il join.

Questo è il piano di query per un 2014 che utilizza l'espressione costante

inserisci qui la descrizione dell'immagine

Questo è il piano di query per un'istanza 2014 che utilizza l'espressione newid.

inserisci qui la descrizione dell'immagine

Questo a quanto pare è di progettazione, problema Connect qui. Grazie a @paulWhite per aver saputo che esisteva.


1
Esatto, esattamente - è quello che sta succedendo, ma sicuramente non è previsto. I risultati non corrispondono al T-SQL che viene passato e quindi alla domanda.
Brent Ozar,

Anche la sostituzione del numero casuale con un statico 1 fornisce all'operatore di join senza predicato di join
James Anderson,

Sembra che tu stia succedendo qualcosa. Anche usando OPTION (FORCE ORDER) non cambia il comportamento - il numero casuale viene ancora calcolato per ultimo ...
Jeremiah Peschka

Rimuovendo il database sys.data TVF, quanto segue produce lo stesso piano: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremiah Peschka

Sembra un problema di precedenza dell'operatore
James Anderson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.