当您使用 +、-、*、/ 或 % 算术运算符执行
int、smallint、tinyint 或 bigint 的隐式或显式转换
浮点数、实数、小数或数字数据类型的常量值,
SQL Server 在计算数据类型时应用的规则和
表达式结果的精度取决于是否
查询是否自动参数化。
因此,查询中的类似表达式有时会产生
不同的结果。当查询未自动参数化时,常量
值首先转换为数字,其精度刚刚大
足以保存常量的值,然后再转换为
指定的数据类型。例如,常数值 1 转换为
numeric (1, 0),常量值250转换为numeric (3, 0)。
当查询被自动参数化时,常量值总是
在转换为最终数据之前转换为numeric (10, 0)
类型。当涉及 / 运算符时,不仅可以将结果类型的
相似查询之间的精度不同,但结果值可以
也不同。例如,自动参数化的结果值
包含表达式 SELECT CAST (1.0 / 7 AS float) 的查询
将不同于不是相同查询的结果值
自动参数化,因为自动参数化查询的结果
将被截断以适应 numeric (10, 0) 数据类型。
注意:
numeric (10, 0) 等价于INT。
在上面的示例中,当被除数和除数都是整数时,类型被视为INT,例如INT / INT = INT
另一方面,如果其中一个类型被强制为“正确的”NUMERIC 类型,则表达式被视为NUMERIC( 10, 0 ) / NUMERIC( 10, 0 ) = NUMERIC( 21, 11 )。有关如何计算结果类型的说明,请参阅:Precision, scale, and Length (Transact-SQL)。
例子:
EXEC sp_describe_first_result_set N'SELECT 1 as a, 7 as b, 1 / 7 AS Result'
EXEC sp_describe_first_result_set N'SELECT 1 as a, CONVERT( NUMERIC( 10, 0 ), 7 ) as b, CONVERT( INT, 1 ) / CONVERT( NUMERIC( 10, 0 ), 7 ) AS a'
注意: NUMERIC 数据类型只有固定的小数位数(刻度)来存储小数。当除法产生具有(无限)长小数部分的结果时,这一点变得很重要,例如1 / 3 必须被截断以适应类型。
自动参数化
来自Microsoft White Paper:
... 只有那些参数值不影响的 SQL 语句
查询计划选择是自动参数化的。
SQL Server 的 LPE(语言处理和执行)组件
自动参数化 SQL 语句。当 QP(查询处理器)组件
意识到文字常量的值不会影响查询计划
选择,它声明 LPE 的自动参数化尝试是“安全的”,并且
自动参数化进行;否则,自动参数化是
声明为“不安全”并被中止。
如果查询处理器认为查询“不安全”,查询仍会执行,但缓存的计划仅适用于该特定查询。
上述文章详细描述了不符合自动参数化条件的语句类型。
SQLTeam 文章提供了一个很好的总结:
- 单表 - 无联接
- 没有 IN 子句
- 没有联合
- 没有选择进入
- 没有查询提示
- 没有 DISTINCT 或 TOP
- 没有全文、链接服务器或表变量
- 没有子查询
- 无分组方式
- WHERE 子句中没有
- 没有功能
- 没有使用 FROM 子句的 DELETE 或 UPDATE
- 参数值不会影响计划
OP 案例
结果的差异归结为12是否被自动参数化并被视为INT/NUMERIC( 10, 0 ),因此被视为NUMERIC( 2, 0 )。这将直接影响舍入前结果的精度(小数位数):decimal(19,16) 或decimal(11,8)。
输入参数:
-- Note: on my machine "parameterization" option does not have any effect on below example
SELECT CONVERT( decimal (5, 3), 4.250 ) AS a, -- the type is explicitly defined in the table
0.01 AS b -- always becomes NUMERIC( 2, 2 )
12 AS c -- will either become NUMERIC( 2, 0 ) or NUMERIC( 10, 0 ) / INT
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal (5, 3), 4.250 ) AS a, 0.01 AS b, 12 AS c'
在上述情况下,它被视为INT。
您可以“强制”将其视为NUMERIC( 2, 0 ):
-- Note: on my machine "parameterization" option does not have any effect on below example
SELECT 0.01 AS b, ( 12 * 0.01 ) AS c
EXEC sp_describe_first_result_set N'SELECT ( 12 * 0.01 ) AS c'
-- Result: 0.12 numeric(5,2)
商品数据类型计算公式:p1 + p2 + 1, s1 + s2。
找出起始类型求解:5 = x + 2 + 1, 2 = y + 2 得到2, 0 即NUMERIC( 2, 0 )
结果的输出类型如下:
-- 12 is NUMERIC( 10, 0 ) / INT
SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(10, 0), 12 )
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(10, 0), 12 )'
-- Result: 0.0035416666666666 decimal(19,16) -> rounding to 9 decimal places: 0.003541667
-- 12 is NUMERIC( 2, 0 )
SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(2, 0), 12 )
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 ) / CONVERT( decimal(2, 0), 12 )'
-- Result: 0.00354166 decimal(11,8) -> rounding to 9 decimal places: 0.003541660
要了解如何计算结果类型,请参阅Precision, scale, and Length (Transact-SQL)。
按照Dale Burnett 文章中描述的步骤,获取了 OPs 示例中每个语句的查询计划。检查每个语句的ParameterizedPlanHandle 属性的查询计划。结果如下,可能的原因(参见上面的自动参数化部分)没有自动参数化:
- 普通表:自动参数化。请注意 XML 计划中的以下内容:
ParameterizedText="(@1 numeric(2,2),@2 int)SELECT round(CONVERT([decimal](15,9),[val]*@1/@2),(9)) [val] FROM [value] [pr]"
- 内联:没有自动参数化。原因:子查询。请注意,CTE 也不会自动参数化。
- 没有表:没有自动参数化。原因:不确定。可能太琐碎了。
- 表变量:没有自动参数化。原因:表变量
- Tempt Table:没有自动参数化。原因:不确定。没有明确提及临时表。
- “一起”:没有自动参数化。原因:
UNION
解决方案
将您的文字和/或中间结果转换为所需的类型以避免意外,例如
SELECT CONVERT( decimal( 12, 7 ), CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 )) / CONVERT( decimal(2, 0), 12 )
EXEC sp_describe_first_result_set N'SELECT CONVERT( decimal( 12, 7 ), CONVERT( decimal (5, 3), 4.250 ) * CONVERT( decimal (2, 2), 0.01 )) / CONVERT( decimal(2, 0), 12 )'
-- Result: 0.0035416666 decimal(15,10) -> rounding to 9 decimal places: 0.003541660
总结
这个问题是一个复杂的案例:Division of 2 numbers using CAST function in SQL server 2008R2。复杂性源于 SQL Server 可能在不同场景中使用不同的数据类型。
好读