Sql server 以newid()为随机源的T-SQL Case语句奇怪行为

Sql server 以newid()为随机源的T-SQL Case语句奇怪行为,sql-server,tsql,Sql Server,Tsql,我正在使用SQL Server 2012 如果我执行以下操作以获得[1,3]范围内的随机ish数字列表,则效果很好: SELECT TOP 100 ABS(CHECKSUM(NEWID()))%3 + 1 [value_of_rand] FROM sys.objects 我得到这样的好东西(都在1到3之间) 但是如果我把同一个链式随机值函数的输出放到CASE语句中,它显然不会只生成值1,2,3 SELECT TOP 100 CASE (ABS(CHECKSUM(NEWID(

我正在使用SQL Server 2012

如果我执行以下操作以获得[1,3]范围内的随机ish数字列表,则效果很好:

SELECT TOP 100 
    ABS(CHECKSUM(NEWID()))%3 + 1 [value_of_rand]
FROM sys.objects
我得到这样的好东西(都在1到3之间)

但是如果我把同一个链式随机值函数的输出放到CASE语句中,它显然不会只生成值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
它输出:

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

我做错了什么?

我不能告诉你为什么,这确实很奇怪,但我可以给你一个解决办法。在尝试使用之前,将随机值选择到cte中

;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

不再是“这很奇怪”

我认为您在这里遇到的问题是
(ABS(CHECKSUM(NEWID())%3+1)
不是一个值,它是一个表达式,SQL可以随时重新计算它。您可以尝试各种语法方法,例如删除额外的括号或CTE。这可能会使它消失(目前),但可能不会,因为从逻辑上讲,它看起来与优化器的请求相同

我认为唯一可靠且受支持的停止此操作的方法是先将其保存(保存到临时表或真实表),然后使用第二个查询引用保存的值。

您的:

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
实际执行:

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;
基本上,您的表达式是不确定的,并且每次都进行计算,因此您可以使用
ELSE子句
。所以没有bug或陷阱,只需将其与变量表达式一起使用,这是完全正常的行为

这是同一个班级

合并表达式是这种情况下的一种语法捷径 表情。也就是说,代码COALESCE(表达式1,…n)被重写 由查询优化器按照以下CASE表达式执行:

案例

当(expression1不为NULL)时,则expression1

当(expression2不为NULL)时,则expression2

其他表达

结束

这意味着输入值(表达式1、表达式2、, expressionN等)将进行多次评估。还有,在 符合SQL标准,一个包含 子查询被认为是不确定的,子查询将被计算 两次。在这两种情况下,两个数据库之间可以返回不同的结果 第一次评估和后续评估

编辑:

解决方案:

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)

是不是在表示数字时,它是四舍五入的,而在计算数字时,它不是四舍五入的?试着把它放在一个变量中,然后调试/抛出你有“那很奇怪”的值。如果我把它赋给一个变量,然后对该变量执行case语句,它工作得很好,没有奇怪。然而,我认为你的调试方法是不可能的。我得到一个错误,说“一个为变量赋值的SELECT语句不能与数据检索操作相结合”;选择@a=(ABS(checksum(newid())%3+1),CASE@a当1时,然后选择'one'当2时,然后选择'two'当3时,然后选择'three'ELSE'奇怪:'+cast(@a作为varchar(max))end当3大小写时,您总是可以删除整个
,并将
三个
放在
ELSE
中,但是该表达式怎么可能产生一个值,导致case语句的ELSE分支被命中呢?@ansssss因为
大小写
表达式与C
switch
语句不同,表达式可以在每个
情况下重新计算
条件测试并得出不同的值..我不明白。你是说(ABS(CHECKSUM(NEWID())%3+1)可以产生除1、2或3以外的结果吗?@anssss不,我是说在
WHEN 1
分支上,它可能计算为3,然后在
WHEN 2
分支上,它可能计算为1。好的,我明白你现在的意思了。LAD205在视觉上解释得更好。这确实有效,但我不确定它有多可靠。优化器可以在它认为合适的时候移动东西,并且可以在将来重新排序,但是,我怀疑,只要在CTE或子查询中使用表达式保留
TOP 100
,它可能会保持稳定。查看页面,在Simple CASE expression下显示:对输入表达式求值,然后按照指定的顺序对每个when子句的输入表达式=when表达式求值。“这可能是一个文档错误,您的解释似乎是正确的,但您是否看到,一旦提交,查询实际上正在以这种方式重写?只是好奇。@srutzky有一个关于
COALESCE
的onconnect错误,它被作为CASE展开,而这是相同的情况
COALESCE
在文档中有警告,
CASE
没有:(请参见我查看的连接项,它是用于
COALESCE
,而不是
CASE
。此外,它只说了与
COALESCE
文档所说的相同的事情(您在答案中链接的内容).我的意思是:有没有任何实际证据表明该表达式正在按照您的建议重写?我这样问是因为
CASE
的文档表明,在简单的CASE场景中(就是这样),输入_表达式只计算一次。我知道该行为符合您的解释,但最好有更明确的;-)。哇!很好的解释。我在MSDN页面上为CASE添加了一个链接……可能不会持续很久,但也许它会让人免于我之前的困惑。@srutzky是的,医生确实这么说,但是。。。优化器发生了变化。SQL优化器有很大的回旋余地(比我所知道的任何其他语言都要灵活)。顺便说一句,优化器在这方面的限制/边界是什么,我们从来没有明确定义过。对于标量表达式,据我所知,没有办法确定它在做什么,因为查询计划实际上没有下降
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)