使用INT和单破折号(-)时的奇怪CAST行为

6
我刚刚回答了这个问题:Concate Primary Keys in SQL
在那里,我遇到了一个奇怪的行为:
SELECT 5 + '-' +  8 

返回13

SELECT CAST('-' AS INT) 

返回0,这就解释了以上的问题...

但是:为什么一个单破折号会被隐式转换为零?

顺便说一下:一个单加号或一个(多个)空字符串同样如此...

参见将破折号(-)转换为十进制数,但指出,将其转换为十进制数不会产生这些结果...


可能是因为“-”是减号。 - Denis Reznik
1
Int比char具有更高的优先级,因此SQL Server在此处隐式地将“-”转换为int。https://msdn.microsoft.com/zh-cn/library/ms190309.aspx - Denis Reznik
@DenisReznik,但这真的是一个答案吗?是的,当然有更高的优先级。但问题不是“为什么SQL Server尝试添加这些值?”而是“为什么单个-(或单个+)可以隐式转换为INT?” - Shnugo
1
我的猜测是它只是内部转换代码的副作用,因为当CAST('-5' AS INT)是合法的时候,为了避免执行额外的代码来检查 '-' 是否是转换的唯一部分并使转换无效,所以他们只是传递它。对于 + 也是同样的道理。 - Allan S. Hansen
1
这个推理也适用于 cast(' 6' AS int) 是合法的,因此为了避免检查是否只有空格,他们只允许转换通过,因为空格是一个合法字符。然而,我没有任何来源来支持它 - 它只是似乎是一个逻辑上的“快捷方式”来优化性能。 - Allan S. Hansen
显示剩余5条评论
4个回答

5

这与正负数有关。我通过检查ISNUMERIC得出了这个结论:

SELECT ISNUMERIC('-12')  // Result: 1
SELECT ISNUMERIC('+12')  // Result: 1
SELECT ISNUMERIC('.12')  // Result: 1 (because "." can be cast to MONEY)

所以在你的情况下,SELECT CAST('-' AS INT)得到的是一个“负零”。即使SELECT CAST('-.' AS MONEY)也是合法的。

因为 INT 类型只允许使用整数,不能在 INT 值中使用任何 . - diiN__________
好的,我尝试了这些 SELECT CAST('.' AS DECIMAL(10,4)),但是不起作用,而 SELECT CAST('.' AS MONEY) 确实可以。我认为你走在正确的道路上... - Shnugo
@Shnugo 我认为 SELECT CAST('.' AS DECIMAL(10,4)) 是期望一个数字,因为你精确地定义了目标类型。'.0''0.' 没有问题。 - diiN__________
我同意...这并不完全直观,特别是像原始案例这样的情况,但至少它是相当一致的。我支持! - Shnugo

5
我是一名有用的助手,可以翻译文本。

由于内容过长/复杂,无法在评论中回答,因此我将其作为答案编写。请注意-我没有官方来源,因此不能确认我的逻辑是否被实施。(但我认为这很有意义 :))

但假设您正在编写一个转换函数,需要执行以下操作。

因此,您需要验证一个字符串-例如CAST('-50' AS INT);
然后逐个字符处理:

`-` is a valid part of the conversion, so move to next character.  
`5` is a valid part of the conversion, so move to next character.  
`0` is a valid part of the conversion, so move to next character.  
Done.

假设有字符串 CAST('-' AS INT);

`-` is a valid part of the conversion, so move to next character.  
Done.

现在 - 你可以进行额外的检查来使-无效,因为它不能单独使用,但这需要额外的代码。

+和空格类似。以及货币和MONEY或期间和money也是如此:

SELECT CAST('$' AS MONEY)
SELECT CAST('.' AS MONEY)

这两个字符都是有效的 - 但实际上只能与实际数字一起使用。但它们可以被解析 - 这表明这是有意为之的,并且转换速度似乎很合理。


如@diiN_的回答所述,我同意...这并不完全直观,特别是在像原始问题这样的情况下,但至少它是相当一致的。我给你+1!我接受了你的回答,因为你的评论是第一个指向这个方向的。 - Shnugo

1
在这个表达式中
5 + '-'

5INT 类型的字面量,'-'CHAR(1) 类型的字面量。因为将 CHAR 隐式转换为 INT 比反过来高优先级,所以 + 将成为数字加法运算符而不是字符串连接运算符,这很不幸。

'-'转换为INT会产生0,因为-+都是合法的数字符号,并且允许一个没有数字的单个符号存在。为什么?嗯,因为它们是合法的。据我所知,这些规则在任何地方都没有得到记录。事实上,没有一条规则被记录下来。CONVERT(MONEY, ',,,')会产生0,因为,完全被忽略为数字分隔符,即使它们在这里没有分隔任何数字。CONVERT(FLOAT, '+')是不合法的,CONVERT(DECIMAL, '+')也是如此,但CONVERT(INT, '+')却没问题。Books Online只包含对行为的最简要引用。如果你反向工程实现这些行为的代码,无疑会发现一些古老而可疑的解析器快捷方式,现在它们已经保持兼容性。

请注意,我非常确定如果转换代码是今天编写的,微软会确保它会产生一个错误(就像使用,或大多数其他数字类型一样),因为结果是毫无意义的。但我同样确定,在野外存在着依赖于这种转换方式工作的代码的风险太大了。(想象一下所有使用“-”作为“不适用”值的文本文件,目前被导入为0,现在将因出现错误而中断。)


正如Denis Reznik所指出的那样,由于运算符的优先级,数字的求和将会发生,而不是像人们预期的那样字符串的连接。但问题是:为什么一个单破折号会被隐式地转换为零呢?这在某种程度上是可以解释的,但对我来说仍然有些奇怪... - Shnugo
如果你有一个足够天真的解析器,这很容易做到。从0开始。检查第一个字符是否为+-。如果是,适当设置符号标志。现在处理字符串的其余部分。哎呀,没有剩余的部分,所以返回由符号修改的结果:0。(处理字符串的代码知道只允许数字:--不会转换。)编写代码来检查我们是否实际处理了任何数字并在没有处理数字的情况下出错会更加复杂(不是多么复杂,但跟踪它的唯一原因是产生错误)。 - Jeroen Mostert
你说得没错,那正是为什么我接受了艾伦的答案。问题的答案并不是为什么进行数字计算,而仅仅是为什么 SELECT CAST('-' AS INT) 返回零... 性能相关的假设 足够幼稚 的解析器似乎是正确的。+1 给这个 足够幼稚 的解析器。让我微笑了一下... - Shnugo
我怀疑在这种情况下没有人会有意识地或者甚至是隐含地考虑到性能问题,原因有很多。首先,在数据库中,你绝对希望结果在快之前是正确的。其次,如果正确实现,检查几乎不会拖慢任何事情。第三,'-' 不允许用于 FLOATREAL 或者 NUMERIC(这些不需要性能吗?)。更多的是 '-' 由于代码结构的方式而成为有效字符串被简单地忽略了,而且今天没有人想要触碰这段代码,因为他们害怕会破坏东西。(添加了一个相关段落。) - Jeroen Mostert

0

在这种情况下,默认情况下,SQL Server 尝试将表达式中的值转换为具有最高优先级的类型。Int 的优先级高于 char,因此 SQL Server 在此处隐式地将 '-' 转换为 int。这里有一份关于 SQL Server 类型优先级的文档


1
这不是问题的答案,正如之前的评论所述...尝试使用SELECT 5 + '*' + 8,你会遇到一个错误... - Shnugo
是的,'*' 是乘法运算符号,但是 '+' 或 '-' 是数字的符号。在 SQL Server 中,Int 变量可以是正数或负数,也就是说 '+' 或 '-'。 - Denis Reznik
这很明显,但仍然没有答案。我看不出任何理由将单个+或单个-视为数字。可以讨论空字符串,但作为隐式转换,这就太过了...你不这么认为吗? - Shnugo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接