为啥这个 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 的转换,导致转换失败。

有人知道这是为什么吗?

【问题讨论】:

到目前为止,没有一个答案可以解释ISNULLCOALESCE 之间的区别。第一个严格返回第一个参数的数据类型,第二个查看所有参数。例如见DECLARE @X VARCHAR(3) SELECT ISNULL(@X,'Longer Than 3 characters'), COALESCE(@X,'Longer Than 3 characters') 该问题简短地提到了COALESCE,但最后一段的摘要问题只是关于ISNULL。但感谢您的好评。 @Szymon - 是的,你是对的。一定是刚刚浏览了一下,看到ISNULLCOALESCE 都提到并假设这是测试用例之一。 【参考方案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

【讨论】:

NUMERICDECIMAL 是同义词。问题是5005 不适合NUMERIC(4,1)/DECIMAL(4,1) @Martin - 感谢您的澄清。因此,当以这种方式使用具有特定值的派生表/CTE 时,最好提供显式强制转换以确保您的列具有可行/足够的大小(与在临时表上指定列数据类型时的方式相同,表变量表还是普通表?) 这可能是一个更好的做法,但我通常不这样做,除非我特别需要与默认值不同的东西。如果所有内容周围都有CAST,阅读起来会很混乱。

以上是关于为啥这个 SQL 查询失败的主要内容,如果未能解决你的问题,请参考以下文章

为啥我在 PHP 中的 SQL 查询失败?

为啥这个查询失败了?

为啥这个 Django 模型上的这个非常基本的查询会失败?

为啥这个 JPA 查询在 H2 数据库上失败?

为啥我的查询因 SQLCODE = -420 而失败?

为啥反引号在 PDO 查询中失败? [复制]