2015-09-09 3 views
7

sto usando SQL Server 2012.dichiarazione Causa T-SQL strano comportamento con newid() come fonte di casualità

Se io quanto segue per ottenere un elenco di numeri casuali-ish nel range [1,3 ], funziona perfettamente:

SELECT TOP 100 
    ABS(CHECKSUM(NEWID()))%3 + 1 [value_of_rand] 
FROM sys.objects 

e ottengo cose piacevoli come questa (tutte tra 1 e 3).

3 
2 
2 
2 
1 
....etc. 

Ma se Ho poi messo l'uscita di quella stessa funzione-random-valore incatenato in una dichiarazione caso, a quanto pare non produce solo i valori 1,2,3.

SELECT TOP 100 
    CASE (ABS(CHECKSUM(NEWID()))%3 + 1) 
     WHEN 1 
      THEN 'one' 
     WHEN 2 
      THEN 'two' 
     WHEN 3 
      THEN 'three' 
     ELSE 
      'that is strange' 
    END [value_of_case] 
FROM sys.objects 

Produce:

three 
that is strange 
that is strange 
one 
two 
...etc 

che cosa sto facendo male qui?

+0

Potrebbe essere che in sede di presentazione del numero è arrotondamento, ma quando la valutazione non è? Prova a metterlo in una variabile e fai il debugging/buttando il valore dove hai "che è strano". – Dane

+0

Se lo assegno a una variabile e poi faccio l'istruzione case contro quella variabile, funziona bene, nessuna stranezza. Tuttavia, penso che il tuo metodo di debugging non sia possibile. Viene visualizzato un errore che dice "Un'istruzione SELECT che assegna un valore a una variabile non deve essere combinata con le operazioni di recupero dei dati." se provo a fare qualcosa di simile: DECLARE @a int; seleziona @a = (ABS (checksum (newid()))% 3 + 1), CASE @a \t QUANDO 1 ALLORA 'uno' QUANDO 2 POI 'due' \t QUANDO 3 POI 'tre' \t ELSE 'strano:' + cast (@a come varchar (max)) END – Anssssss

+0

si può sempre rimuovere l'intero caso 'WHEN 3' e mettere il' tre' in 'ELSE' –

risposta

7

tuo:

SELECT TOP 100 
    CASE (ABS(CHECKSUM(NEWID()))%3 + 1) 
     WHEN 1 
      THEN 'one' 
     WHEN 2 
      THEN 'two' 
     WHEN 3 
      THEN 'three' 
     ELSE 
      'that is strange' 
    END [value_of_case] 
FROM sys.objects 

effettivamente eseguite:

SELECT TOP 100 
    CASE 
     WHEN (ABS(CHECKSUM(NEWID()))%3 + 1) = 1 THEN 'one' 
     WHEN (ABS(CHECKSUM(NEWID()))%3 + 1) = 2 THEN 'two' 
     WHEN (ABS(CHECKSUM(NEWID()))%3 + 1) = 3 THEN 'three' 
     ELSE 'that is strange' 
    END [value_of_case] 
FROM sys.objects; 

Fondamentalmente la tua espressione è non-deterministico e ogni volta che viene valutata in modo da poter finire con ELSE clause. Quindi non c'è bug o cattura, basta usarlo con espressione variabile ed è un comportamento perfettamente normale.

Questa è la stessa classe come COALESCE syntactic-sugar

L'espressione COALESCE è una scorciatoia sintattica del CASE espressione. Cioè, il codice COALESCE (espressione1, ... n) viene riscritto da Query Optimizer come la seguente espressione CASE:

CASO

QUANDO (espressione1 IS NOT NULL) THEN espressione1

QUANDO (expression2 NON È NULL) THEN espressione2

...

ELSE expressionN

END

Ciò significa che i valori di ingresso (expression1, expression2, expressionN, ecc) saranno valutati più volte. Inoltre, in conformità allo standard SQL, un'espressione di valore che contiene una sottoquery è considerata non deterministica e la sottoquery viene valutata due volte . In entrambi i casi, è possibile restituire risultati diversi tra la prima valutazione e le successive valutazioni .

EDIT:

Soluzione: SqlFiddle

SELECT TOP 100 
    CASE t.col 
     WHEN 1  THEN 'one' 
     WHEN 2  THEN 'two' 
     WHEN 3  THEN 'three' 
     ELSE  'that is strange' 
    END [value_of_case] 
FROM sys.objects 
CROSS APPLY (SELECT ABS(CHECKSUM(NEWID()))%3 + 1) AS t(col) 
+0

@Assssss Vedere la mia risposta aggiornata, c'è la soluzione – lad2025

+0

Guardando la pagina per [CASE] (https://msdn.microsoft.com/en-us/library/ms181765.aspx) si dice, in ** Semplice espressione CASE : ** "Valuta * input_expression *, quindi, nell'ordine specificato, valuta * input_expression * = * when_expression * per ogni clausola WHEN." Potrebbe essere un errore di documentazione e la tua spiegazione sembra essere corretta, ma hai visto che la query viene effettivamente riscritta in quel modo una volta inviata? Solo curioso. –

+0

@srutzky C'è un errore di connessione su 'COALESCE' che viene scartato come CASO QUANDO, questo è lo stesso caso. 'COALESCE' ha un avvertimento nella documentazione,' CASE' non :(Vedi https://connect.microsoft.com/SQLServer/feedback/details/546437/ – lad2025

1

Non posso dirti perché, è davvero strano, ma posso darti una soluzione. Selezionare i valori casuali in una CTE prima di tentare di usarli

;with rndsrc(value_of_rand) as 
(
SELECT TOP 100 
    ABS(CHECKSUM(NEWID()))%3 + 1 
FROM sys.objects 
) 
SELECT TOP 100 
CASE value_of_rand 
    WHEN 1 
     THEN 'one' 
    WHEN 2 
     THEN 'two' 
    WHEN 3 
     THEN 'three' 
    ELSE 
     'that is strange' 
END [value_of_case] 
from rndsrc 

Non più "che è strano"

+0

Questo funziona, ma non sono sicuro di quanto sia affidabile. L'ottimizzatore può spostare le cose intorno come meglio crede e potrebbe riordinarlo in futuro, tuttavia, sospetto che finché si mantiene il 'TOP 100' con l'espressione in CTE o sottoquery, probabilmente rimarrà stabile. – RBarryYoung

+0

@Jamiec Questo è il motivo per cui http://stackoverflow.com/a/32485383/5070879 – lad2025

2

Penso che il problema riscontrato è che (ABS(CHECKSUM(NEWID()))%3 + 1) non è un valore, è un espressione e SQL ha la possibilità di rivederlo ogni volta che lo desidera. Puoi provare varie cose sintattiche come rimuovere la parentesi aggiuntiva o un CTE. Questo potrebbe farla andare via (per ora), ma potrebbe non farlo, dal momento che logicamente sembra la stessa richiesta all'ottimizzatore.

Penso che l'unico modo sicuro per interrompere questa operazione sarebbe quello di salvarlo prima (su una tabella temporanea o reale) e quindi utilizzare una seconda query per fare riferimento ai valori salvati.

+0

Ma come potrebbe quell'espressione produrre un valore che causa il colpo al ramo ELSE dell'istruzione CASE? – Anssssss

+1

@Assssss Perché un'espressione 'CASE' * * non è come un'istruzione C' switch', l'espressione potrebbe essere rivalutata in ogni test condizionale 'CASE' e fornire valori diversi. – RBarryYoung

+0

Non capisco. Stai dicendo che (ABS (CHECKSUM (NEWID()))% 3 + 1) può produrre qualcosa di diverso da 1,2 o 3? – Anssssss