2011-12-15 18 views
8

Antecedentes: Yo estaba tratando de conseguir algunos valores aleatorios 'hex', mientras que la creación de datos ficticios y se acercó con esta construcción:Extraño comportamiento de Case Construction

SELECT TOP 100 
    result = (CASE ABS(Binary_Checksum(NewID())) % 16 
       WHEN -1 THEN 'hello' 
       WHEN 0 THEN '0' 
       WHEN 1 THEN '1' 
       WHEN 2 THEN '2' 
       WHEN 3 THEN '3' 
       WHEN 4 THEN '4' 
       WHEN 5 THEN '5' 
       WHEN 6 THEN '6' 
       WHEN 7 THEN '7' 
       WHEN 8 THEN '8' 
       WHEN 9 THEN '9' 
       WHEN 10 THEN 'a' 
       WHEN 11 THEN 'b' 
       WHEN 12 THEN 'c' 
       WHEN 13 THEN 'd' 
       WHEN 14 THEN 'e' 
       WHEN 15 THEN 'f' 
       ELSE 'huh' END) 
      FROM sys.objects 

Cuando se ejecuta esto en mi instancia de SQL Server 2008 R2, Tengo bastantes registros 'huh':

result 
------ 
huh 
3 
huh 
huh 
6 
8 
6 

Realmente no entiendo por qué. lo que se espera que suceda es:

  • para cada registro NewID() viene con un nuevo valor aleatoria
  • Binary_Checksum() calcula un entero en base a dicho valor
  • ABS() hace que el valor positivo
  • % 16 devuelve el resto de ese valor positivo si se dividiría entre 16, que entonces sería un valor entre 0 y 15
  • th e CASE construcción convierte el valor a un personaje relevante
  • Puesto que hay WHEN s para cada valor entre 0 y 15, la ELSE no deberían necesitar este

o al menos, eso es lo que yo creo que debería ocurrir ... pero es evidente que algo va mal en el camino ...

al hacer la misma cosa en un enfoque de dos pasos (a través de temp-tabla), se han ido los de huh ...

SELECT TOP 100 x = ABS(Binary_Checksum(NewID())) % 16, 
       result = 'hello' 
    INTO #test 
    FROM sys.objects 

UPDATE #test 
    SET result = (CASE x WHEN 0 THEN '0' WHEN 1 THEN '1' WHEN 2 THEN '2' WHEN 3 THEN '3' 
         WHEN 4 THEN '4' WHEN 5 THEN '5' WHEN 6 THEN '6' WHEN 7 THEN '7' 
         WHEN 8 THEN '8' WHEN 9 THEN '9' WHEN 10 THEN 'a' WHEN 11 THEN 'b' 
         WHEN 12 THEN 'c' WHEN 13 THEN 'd' WHEN 14 THEN 'e' WHEN 15 THEN 'f' 
         ELSE 'huh' END) 

SELECT * FROM #test 

¿Alguien que entiende esto? Por lo que puedo decir, debería dar el mismo resultado (es de hecho copiar y pegar) independientemente de que lo haga directamente o mediante una tabla temporal ... Pero obviamente algo va mal si lo hago en una sola declaración.

PD: No necesito una "solución" para esto, ya tengo una solución alternativa (ver a continuación), simplemente estoy esperando que alguien me explique por qué hace lo que hace.

Solución:

SELECT TOP 100 result = SubString('abcdef', 1 + (ABS(Binary_Checksum(NewID())) % 16), 1) 
    FROM sys.objects 
+0

PD: si alguien puede probar esto en diferentes versiones de SQL-Server que podrían ser interesantes también ... – deroby

+0

Su solución parece más sucinta que la original ... – Russell

+0

Thx para explicar esto chicos ... yendo con Damiens respuesta ya que es un poco más detallado para cualquiera que pueda tropezar con esto. (y fue 1 minuto más rápido en la parte superior = P) – deroby

Respuesta

3

creo que, contrariamente a la descripción de la simple CASE expression, que en realidad re-evalúa input_expression para cada input_expression = when_expression comparación (esto normalmente sería seguro, a menos que, como en este caso, hay una función no determinista en input_expression)

por lo tanto, lo que sucede es que mantiene la generación de diferentes números aleatorios entre 0 y 15 para cada comparación, y los huh s salen si, después de 16 evaluaciones/comparaciones, nunca ge ed un número coincidente.


Esto no generar huh s:

SELECT TOP 100 
    result = (CASE ABS(Binary_Checksum(Value)) % 16 
      WHEN -1 THEN 'hello' 
      WHEN 0 THEN '0' 
      WHEN 1 THEN '1' 
      WHEN 2 THEN '2' 
      WHEN 3 THEN '3' 
      WHEN 4 THEN '4' 
      WHEN 5 THEN '5' 
      WHEN 6 THEN '6' 
      WHEN 7 THEN '7' 
      WHEN 8 THEN '8' 
      WHEN 9 THEN '9' 
      WHEN 10 THEN 'a' 
      WHEN 11 THEN 'b' 
      WHEN 12 THEN 'c' 
      WHEN 13 THEN 'd' 
      WHEN 14 THEN 'e' 
      WHEN 15 THEN 'f' 
      ELSE 'huh' END) 
      FROM (select NewID() as Value,* from sys.objects) so 
+0

Parece. Tiene sentido una vez que ustedes lo explican, pero yo no lo habría pensado por mi cuenta. Gracias. – deroby

+1

"Esto no genera huhs" en su prueba, pero ¿puede señalar cualquier documento que diga que SQL Server garantiza que solo lo evaluará una vez? –

+2

Debería ser: 'result = (CASE ABS (Binary_Checksum (Value))% 16' – Johan

8

El escalar de cómputo en el plan tiene la siguiente fórmula

[Expr1038] = Scalar operador (CASO CUANDO abs (binary_checksum (newid()))% (16) = (- 1) THEN 'hello' ELEN CASE CUANDO abs (binary_checksum (newid()))% (16) = (0) THEN '0' OTRA CUENTA CUANDO abs (binary_checksum (new id()))% (16) = (1) ENTONCES '1' CASO CUANDO abs (binary_checksum (newid()))% (16) = (2) ENTONCES '2' CASO SI abs (binary_checksum (newid()))% (16) = (3) ENTONCES '3' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (4) ENTONCES '4' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (5) ENTONCES '5' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (6) ENTONCES '6' CASO SI abs (binary_checksum (newid()))% (16) = (7) ENTONCES '7' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (8) ENTONCES '8' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (9) ENTONCES '9' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (10) ENTONCES 'a' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (11) THEN 'b' ELSE CASE CUANDO abs (binary_checksum (newid()))% (16) = (12) ENTONCES 'c' OTRA CASO CUANDO abs (binary_checksum (newid()))% (16) = (13) ENTONCES 'd' CASO CUANDO abs (binary_checksum (newid()))% (16) = (14) ENTONCES 'e' MÁS CASO CUANDO abs (binary_checksum (newid()))% (16) = (15) THEN 'f' ELSE 'huh 'FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN)

el número aleatorio se repetidamente re-evaluado en lugar de ser evaluada una vez y se mantiene constante a lo largo de cada rama de la declaración CASE.

El (fijo) solución propuesta en la respuesta de Damien funciona para mí

SELECT TOP 100 
    result = (CASE ABS(Binary_Checksum(Value)) % 16 
      WHEN -1 THEN 'hello' 
      /*...*/ 
      ELSE 'huh' END) 
      FROM (select NewID() as Value,* from sys.objects) so 

Debido a que el plan cuenta con 2 operadores de computación escalares. El primero de ellos con la definición

[Expr1038] = Scalar Operator(newid()) 

Plan

Luego de que la expresión constante Expr1038 se alimenta en la expresión CASE. Sin embargo, no estoy seguro de que este comportamiento esté absolutamente garantizado. Puede estar sujeto a los caprichos del optimizador.

+0

LOL, eso lo explicaría de hecho. Entonces internamente un CASO x CUANDO 1 LUEGO a CUANDO 2 LUEGO b .... se convierte en CASO CUANDO x = 1 LUEGO a CUANDO x = 2 LUEGO b etc ... Tiene sentido. – deroby

+0

@deroby - Sí. Bastante. No estoy seguro de que haya una alternativa 100% garantizada para evitar esto. –

+0

Supongo que el uso de CTE, CROSS APPLY o CROSS JOIN podría evitar este problema; pero como alguien ya señaló, la solución alternativa parece más limpia =) – deroby