SQL Server如何确定精度/小数位数?


8

我正在运行SQL Server 2012

SELECT 
   0.15 * 30 / 360,
   0.15 / 360 * 30 

结果:

 0.012500, 
 0.012480

这甚至让我感到困惑:

DECLARE @N INT = 360
DECLARE @I DECIMAL(38,26) = 0.15 * 30 / 360     
DECLARE @C DECIMAL(38,26) = 1000000     

SELECT @C *  @I *  POWER(1 + @I, @N)  / ( POWER(1 + @I, @N) - 1 )
SELECT @C * (@I *  POWER(1 + @I, @N)  / ( POWER(1 + @I, @N) - 1 ) )

第一次选择会给我正确的结果:12644.44022第二个选择会截断结果:12644.00000


1
请参见以下答案:stackoverflow.com/a/51445919/87015,它包含一个示例代码,该示例代码根据输入数据类型和操作确定所需的数据类型。
Salman A

Answers:


13

确定由表达式产生的精度和小数位数是老鼠的窝,我认为没有人能理解每种情况下的确切规则,尤其是在混合使用十进制(或float!)和int时。请参阅gbn的此答案

当然,您可以通过进行更多详细的显式转换来定制表达式以提供所需的内容。这可能是矫kill过正,但是:

SELECT 
   CONVERT(DECIMAL(15,6), CONVERT(DECIMAL(15,6), 0.15) 
   * CONVERT(DECIMAL(15,6), 30) 
   / CONVERT(DECIMAL(15,6), 360)),
   CONVERT(DECIMAL(15,6), CONVERT(DECIMAL(15,6), 0.15) 
   / CONVERT(DECIMAL(15,6), 360) 
   * CONVERT(DECIMAL(15,6), 30));

由于浮点数学运算不正确或精度/小数位数错误,因此没有将任何结果四舍五入。

0.012500    0.012500

6

正如Aaron Bertrand所提到的,表达式很难预测。

如果您敢去那里,可以尝试使用以下代码片段获得一些见识:

DECLARE @number SQL_VARIANT
SELECT @number = 0.15 / 360
SELECT @number
SELECT  
    SQL_VARIANT_PROPERTY(@number, 'BaseType') BaseType,
    SQL_VARIANT_PROPERTY(@number, 'MaxLength') MaxLength,
    SQL_VARIANT_PROPERTY(@number, 'Precision') Precision

结果如下:

------------
0.000416

(1 row(s) affected)

BaseType     MaxLength    Precision
------------ ------------ ----------
numeric      5            6

(1 row(s) affected)

3

尽管已经为该问题添加了出色的答案,但是对于SQL Server中的数据类型转换,还是有一个明确定义的优先顺序。

当运算符组合两个不同数据类型的表达式时,数据类型优先级规则将指定优先级较低的数据类型转换为优先级较高的数据类型。如果该转换不是受支持的隐式转换,则返回错误。当两个操作数表达式具有相同的数据类型时,运算结果将具有该数据类型。

SQL Server对数据类型使用以下优先顺序:

user-defined data types (highest)
sql_variant
xml
datetimeoffset
datetime2
datetime
smalldatetime
date
time
float
real
decimal
money
smallmoney
bigint
int
smallint
tinyint
bit
ntext
text
image
timestamp
uniqueidentifier
nvarchar (including nvarchar(max) )
nchar
varchar (including varchar(max) )
char
varbinary (including varbinary(max) )
binary (lowest)

所以,举例来说,如果你SELECT 0.5 * 1(乘以一个int小数),你得到的转换为十进制值的结果,因为decimal更高的优先级比int数据类型。

有关更多详细信息,请参见http://msdn.microsoft.com/zh-cn/library/ms190309.aspx

说了这么多,实际上SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); 应该返回一个十进制值,因为实际上所有输入都是十进制的。有趣的是,您可以将结果修改SELECT为:

DECLARE @N INT = 360;
DECLARE @I DECIMAL(38,26) = 0.15 * 30 / 360;
DECLARE @C DECIMAL(38,26) = 1000000;

SELECT @C *  @I *  POWER(1 + @I, @N)  / (POWER(1 + @I, @N) - 1);
SELECT @C * (@I *  POWER(1 + @I, @N)  / (POWER(1E0 + @I, @N) - 1));

返回:

在此处输入图片说明

我很茫然,解释如何使任何差异,但显然。我的猜测是函数中的1E0(显式浮点数)POWER(迫使SQL Server在POWER函数的输出类型上做出不同的选择。如果我的假设正确,那将表明POWER函数中可能存在错误,因为文档指出第一个输入为POWER()浮点数或可以隐式转换为浮点数的数字。


2
指向“ 精度”,“比例”和“长度”的链接也是合适的。
ypercubeᵀᴹ

2

您是否熟悉SELECT .. INTO 语法?这是解构这种情况的有用技巧,因为它会动态创建带有给定SELECT列表正确数据类型的表。

您可以将计算分解为组成步骤,并在应用过程中应用SQL Server的优先级规则,以查看定义如何更改。这是您的第一个示例的外观:

use tempdb;

SELECT
   0.15             as a,
   0.15 * 30        as b,
   0.15 * 30 / 360  as c
into #Step1;

select * from #Step1;

select
    c.name,
    t.name,
    c.precision,
    c.scale
from sys.columns as c
inner join sys.types as t
    on t.system_type_id = c.system_type_id
where object_id = object_id('#Step1');

drop table #Step1;

这是输出:

name    name        precision   scale
a       numeric     2           2
b       numeric     5           2
c       numeric     9           6

1
但是,这并不总是有帮助。两个(1+1)2的类型的int,但是这个问题有,他们最终产生一个不同类型的结果的一个例子。
马丁·史密斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.