CASE结构的奇怪行为

时间:2011-12-15 09:48:17

标签: sql-server sql-server-2008 tsql

背景:我在创建虚拟数据时尝试获取一些随机的“十六进制”值,并提出了这种结构:

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 

在我的SQL Server 2008 R2实例上运行时,我收到了很多'huh'记录:

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

我真的不明白为什么。 我期望发生的是:

  • 为每条记录NewID()提供了一个新的随机
  • Binary_Checksum()根据上述值计算一个int
  • ABS()使值为正
  • % 16如果将除以16,则返回该正值的余数,然后该值将介于0和15之间
  • CASE构造将值转换为相关字符
  • 由于0到15之间的每个值都有WHEN,因此永远不需要ELSE

或者至少,这是我认为应该发生的事情......但显然在路上出了问题......

当采用两步法(通过临时表)做同样的事情时,哼哼... ...

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

任何明白这一点的人?据我所知,它应该给出相同的结果(它确实是复制粘贴),不管我是直接做什么还是通过临时表...但是如果我在一个语句中这样做,显然会出现问题。< / p> PS:我不需要'修复',我已经有一个解决方法(见下文),我只是希望有人可以解释我为什么这样做。

解决方法:

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

2 个答案:

答案 0 :(得分:8)

计划中的计算标量具有以下公式

  

[Expr1038] =标量运算符(CASE WHEN   abs(binary_checksum(newid()))%(16)=( - 1)THEN'hello'ELSE CASE WHEN   abs(binary_checksum(newid()))%(16)=(0)那么'0'是的情况下   abs(binary_checksum(newid()))%(16)=(1)那么'1'是的情况下   abs(binary_checksum(newid()))%(16)=(2)THEN'2'ELE CASE WHEN   abs(binary_checksum(newid()))%(16)=(3)那么'3'是的情况下   abs(binary_checksum(newid()))%(16)=(4)那么'4'是的情况下   abs(binary_checksum(newid()))%(16)=(5)那么'5'是的情况下   abs(binary_checksum(newid()))%(16)=(6)那么'6'是的情况下   abs(binary_checksum(newid()))%(16)=(7)那么'7'是的情况下   abs(binary_checksum(newid()))%(16)=(8)那么'8'是的情况下   abs(binary_checksum(newid()))%(16)=(9)那么'9'是的情况下   abs(binary_checksum(newid()))%(16)=(10)那么'a'过的情况下   abs(binary_checksum(newid()))%(16)=(11)那么''''''''''   abs(binary_checksum(newid()))%(16)=(12)THEN'c'ELE CASE WHEN   abs(binary_checksum(newid()))%(16)=(13)然后'''''''   abs(binary_checksum(newid()))%(16)=(14)那么'''是的情况下   abs(binary_checksum(newid()))%(16)=(15)那么'f'ELSE'huh'END END END   END END END END END END END END END END END END END END END END)

重复评估随机数而不是评估一次,并在CASE语句的每个分支中保持不变。

Damien的答案中提出的(固定的)解决方案对我有用

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

因为该计划有2个计算标量运算符。第一个定义

[Expr1038] = Scalar Operator(newid())

Plan

然后将常量表达式Expr1038输入CASE表达式。我不确定这种行为是绝对保证的。它可能受到优化者的一时兴起。

答案 1 :(得分:3)

我认为,与simple CASE expression的描述相反,它实际上为每个input_expression比较重新评估input_expression = when_expression(这通常是安全的,除非,如此例如,input_expression

中存在非确定性函数

因此,会发生的事情是,每次比较时,它会在0到15之间生成不同的随机数,如果在16次评估/比较之后,它从未生成匹配的数字,则huh会出现。


这不会生成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