为啥这个 SQL 查询失败
Posted
技术标签:
【中文标题】为啥这个 SQL 查询失败【英文标题】:Why does this SQL query fail为什么这个 SQL 查询失败 【发布时间】:2014-02-21 00:33:50 【问题描述】:以下查询因算术溢出错误而意外失败。
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT NULL
) as t2
“将 int 转换为数据类型 numeric 的算术溢出错误。”
奇怪的是,如果修改查询以删除 NULL 并将其替换为与 null 合并 (5005) 中相同的值,则它可以正常运行
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT 5005
) as t2
此外,完全省略 SELECT NULL 行允许查询运行而不会出现问题
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val
) as t2
如果 IsNull 函数中的 coalesce 值更改为一个整数,该整数小到足以在子查询中转换为小数而不加宽,则查询运行
select IsNull(t2.val, 500)
from(
SELECT 336.6 as val UNION ALL
SELECT NULL
) as t2
在 SQL Server 2005 和 SQL Server 2008 中都对此进行了测试。
通常将整数与小数结合起来是无缝的,SQL Server 会将整数和小数都转换为足够大的小数类型以容纳两者。但是由于某种原因,运行一个查询同时发生了来自 UNION 和 IsNull 的转换,导致转换失败。
有人知道这是为什么吗?
【问题讨论】:
到目前为止,没有一个答案可以解释ISNULL
和COALESCE
之间的区别。第一个严格返回第一个参数的数据类型,第二个查看所有参数。例如见DECLARE @X VARCHAR(3) SELECT ISNULL(@X,'Longer Than 3 characters'), COALESCE(@X,'Longer Than 3 characters')
该问题简短地提到了COALESCE
,但最后一段的摘要问题只是关于ISNULL
。但感谢您的好评。
@Szymon - 是的,你是对的。一定是刚刚浏览了一下,看到ISNULL
和COALESCE
都提到并假设这是测试用例之一。
【参考方案1】:
尝试这样做
select * into t2
from(
SELECT 336.6 as val
UNION ALL
SELECT NULL
) as x
如果您现在查看列,您会看到一个数字精度为 4 且小数位数为 1 的数字
select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T2'
SQL 根据保持 336.6 的最小数值精度做出该决定。现在,当您要求它将 NULL 转换为 5005 时,您是在说,将任何 NULL 值转换为一个太大而无法容纳精度为 4 且比例为 1 的数字。错误消息表明 5005 不会t 适合 Numeric(4,1)
这将起作用,因为该表现在将生成一个更大的数字字段,因为 SQL 需要容纳 5005。使用下面的 T2 的新内容创建表,字段类型应该转到 Numeric(5,1) 允许5005 适合。
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT 5005
) as t2
当您在内部查询中运行没有 NULL 的语句时,SQL 永远不会计算 5005,因此它不会达到需要将 5005 放入 numeric(4,1) 字段的条件。
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val
) as t2
【讨论】:
不知道,但想让 OP 看看 SQL 创建的表到底是什么样子的...... 我期望的结果是 UNION 创建一个十进制 4,1 和 IsNull 以按需扩大输出。它没有,但显然合并可以!不知道这个区别【参考方案2】:我认为问题在于,当 SQL Server 解析联合时,它会决定一个小数类型,该类型仅大到足以容纳 333.6(即decimal(4,1)
)。尝试将 5005 放入其中会导致溢出。
您可以自己指定小数的精度:
select IsNull(t2.val, 5005)
from(
SELECT CONVERT(DECIMAL(5,1), 336.6) as val UNION ALL
SELECT NULL
) as t2
【讨论】:
【参考方案3】:我相信这是因为您的 336.6 值被推断为 NUMERIC 数据类型。如果您想更具体,则将其显式转换为 DECIMAL;
SELECT IsNull(t2.val, CAST(5005 AS DECIMAL))
FROM (
SELECT CAST(336.6 AS DECIMAL) AS val
UNION ALL
SELECT NULL
) AS t2
【讨论】:
NUMERIC
和 DECIMAL
是同义词。问题是5005
不适合NUMERIC(4,1)
/DECIMAL(4,1)
@Martin - 感谢您的澄清。因此,当以这种方式使用具有特定值的派生表/CTE 时,最好提供显式强制转换以确保您的列具有可行/足够的大小(与在临时表上指定列数据类型时的方式相同,表变量表还是普通表?)
这可能是一个更好的做法,但我通常不这样做,除非我特别需要与默认值不同的东西。如果所有内容周围都有CAST
,阅读起来会很混乱。以上是关于为啥这个 SQL 查询失败的主要内容,如果未能解决你的问题,请参考以下文章