【问题标题】:How does this CASE expression reach the ELSE clause?这个 CASE 表达式如何到达 ELSE 子句?
【发布时间】:2014-02-20 20:14:38
【问题描述】:

我需要将一些测试数据加载到我的帐户表的渠道字段中。 Channel 可以是 10 个不同的值之一,所以我想我会使用 CASE 表达式和 ABS(CHECKSUM(NewId())) % 10 随机分配一个值,如下所示:

SELECT 
    id,
    name,
    Channel = 
      CASE ABS(CHECKSUM(NewId())) % 10
        WHEN 0 THEN 'Baby Only'
        WHEN 1 THEN 'Club'
        WHEN 2 THEN 'Drug'
        WHEN 3 THEN 'Food'
        WHEN 4 THEN 'Internet'
        WHEN 5 THEN 'Liquidators'
        WHEN 6 THEN 'Mass'
        WHEN 7 THEN 'Military'
        WHEN 8 THEN 'Other'
        WHEN 9 THEN 'Speciality'
        ELSE '*NONE*'            -- How is this ever getting reached?
      END
FROM 
    retailshelf_nil...account A

由于我使用的是模 10,我认为唯一可能的值应该是 0-9。但是当我运行上面的代码时,我发现ELSE 子句确实已经到达,并且我的数据在某些记录上显示为“NONE”,如下所示:

id                  name    Channel
001L000000KpgFqIAJ  Acct1   *NONE*
001L000000KpgFrIAJ  Acct2   Mass
001L000000KpgFsIAJ  Acct3   Club
001L000000KpgFtIAJ  Acct4   *NONE*
001L000000KpgFuIAJ  Acct5   Baby Only
001L000000KpgFvIAJ  Acct6   *NONE*
001L000000KpgFwIAJ  Acct7   Mass

谁能解释一下我犯了什么逻辑错误导致 ELSE 子句可以到达?

当我运行一个简单的测试来生成随机数时:

SELECT
    RadomNum = ABS(CHECKSUM(NewId())) % 10
FROM 
    retailshelf_nil...account A
ORDER BY 
    1

生成的所有数字都如预期的那样从 0 到 9,那么第一个 SQL 有什么不同?

是否有解决方法来确保永远不会到达ELSE

【问题讨论】:

  • ABS(CHECKSUM(NewId())) 返回 NULL 时,将执行 ELSE。这将在 NewId() 返回 NULL 时发生。
  • @DwB,感谢您的回复,但为什么 NEWID() 会返回 NULL?
  • 如果你只是在寻找一个随机值并且你得到的值不是其他的,为什么不直接将其他值汇总到你的其他值之一中,否则也会是“专业”作为 9,这肯定会搞砸偏差,但这可能无关紧要。
  • There is an answer that explains exactly why this happens - NEWID() 在 CASE 表达式的每个分支中重新计算,您的计算可能恰好是 1 0 0 0 0 0 0 0 0 0,例如,对于某些行,这会导致其他的。
  • @PaulStock 我的思路是错误的。 newid() 永远不应返回 null。

标签: sql sql-server-2008 tsql


【解决方案1】:

查询的书面形式扩展为:

Channel = 
      CASE
        WHEN ABS(CHECKSUM(NewId())) % 10 = 0 THEN 'Baby Only'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 1 THEN 'Club'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 2 THEN 'Drug'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 3 THEN 'Food'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 4 THEN 'Internet'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 5 THEN 'Liquidators'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 6 THEN 'Mass'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 7 THEN 'Military'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 8 THEN 'Other'
        WHEN ABS(CHECKSUM(NewId())) % 10 = 9 THEN 'Speciality'
        ELSE '*NONE*'            -- How is this ever getting reached?
      END

每个测试都使用NEWID 的新值。

【讨论】:

  • 那就解释吧
  • 我知道这一定是与我搞砸的 CASE 语句的工作方式相关的基本问题。
  • 嗯,这是一个相当大的启示,但是......为什么? (没关系,我只是在夸夸其谈。)
  • 知道我们现在所知道的,有趣的是应该达到*NONE* 的概率是(1-0.1)^10。或者大约 35% 的时间。我不会猜到的。
  • 是的,概率在1/e 附近,其中e 是著名的常数。一个相关的问题是:“如果你有 10 顶帽子随机分配给 10 个人,那么没有人得到自己的帽子的概率是多少?”
【解决方案2】:

将为每个 WHEN 子句计算一个新的“随机”数字 - 您可以改为使用派生表:

SELECT ID, Name, 
    Channel = 
      CASE Rand
        WHEN 0 THEN 'Baby Only'
        WHEN 1 THEN 'Club'
        WHEN 2 THEN 'Drug'
        WHEN 3 THEN 'Food'
        WHEN 4 THEN 'Internet'
        WHEN 5 THEN 'Liquidators'
        WHEN 6 THEN 'Mass'
        WHEN 7 THEN 'Military'
        WHEN 8 THEN 'Other'
        WHEN 9 THEN 'Speciality'
        ELSE '*NONE*'            -- How is this ever getting reached?
      END
FROM
(   SELECT 
         id,
         name,
         ABS(CHECKSUM(NewId())) % 10 Rand
    FROM   
        retailshelf_nil...account A
) zzz;

或 CROSS APPLY 子查询:

SELECT A.ID, A.Name, 
    Channel = 
      CASE zzz.Rand
        WHEN 0 THEN 'Baby Only'
        WHEN 1 THEN 'Club'
        WHEN 2 THEN 'Drug'
        WHEN 3 THEN 'Food'
        WHEN 4 THEN 'Internet'
        WHEN 5 THEN 'Liquidators'
        WHEN 6 THEN 'Mass'
        WHEN 7 THEN 'Military'
        WHEN 8 THEN 'Other'
        WHEN 9 THEN 'Speciality'
        ELSE '*NONE*'            -- How is this ever getting reached?
      END
FROM
    retailshelf_nil...account A
CROSS APPLY
(   SELECT 
        ABS(CHECKSUM(NewId())) % 10
) zzz (Rand);

这样NewID() 每条记录只调用一次。

类似的场景已解决here

T-SQL documentation 解释了这种现象(虽然它适用于 Sybase,但显然仍适用于 SQL Server):

引用rand 函数、getdate 函数的表达式, 以此类推,每次求值时产生不同的值。这个 在某些情况下使用这些表达式可能会产生意想不到的结果 案例表达式。例如,SQL 标准指定了这种情况 具有以下形式的表达式:

case expression
    when value1 then result1
    when value2 then result2
    when value3 then result3
...
end

等价于以下形式的case表达式:

case expression
    when expression=value1 then result1
    when expression=value2 then result2
    when expression=value3 then result3
...
end

【讨论】:

  • 这不是真的。这仅适用于被视为数字常量的 RAND()。即使只有一次对 NEWID() 的调用,它最终也会得到相同的值。用户体验的是不在 (0 .. 9) 中的值。
  • @CRAFTYDBA:它的价值是什么?您是否尝试过运行该 CASE?
  • @CRAFTYDBA 我已经复制了结果并验证了修复 - 我找到的文档表明它适用于 any 操作,而不仅仅是 RAND()
  • @CRAFTYDBA 也是一个非空值 MOD 10 不能生成 0-9 以外的数字。
  • @CRAFTYDBA 问题是RAND() 只会对整个查询运行一次,而NEWID() 会在 case 表达式扩展时为每个测试用例运行。因此,原始问题中的 CASE 为表中的每一行运行 10 个 NEWID() 实例。很可能没有生成的 UID 将满足 case 语句的条件。 RAND() 的问题在于它运行的频率不够高。 NEWID() 的问题在于它运行得太频繁了。
【解决方案3】:

关于第二个问题,

CHECKSUM(NewId()) 有时会返回否定结果,这与任何case 条件都不匹配。如果一个负数除以任何一个数,结果将是负数。执行以下查询,

declare @v nvarchar(50) = newid()
select CHECKSUM(@v),@v,CHECKSUM(@v) % 10

【讨论】:

  • 这就是为什么我使用ABS 来确保CHECKSUM 的结果总是积极的
  • 但是你的第二个问题还没有结束。所以我添加了这个答案,以便它可能有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多