我最近在ETL流程中遇到了一个奇怪的案例,对我而言结果似乎不可预测。我读过Difference between numeric, float and decimal in SQL Server,但我认为这不是溢出或十进制精度问题。
场景:
SQL Server 2008 SP3中的源表“ test”,列a声明为数字(38,6)。
首先将结果转换为实数,然后转换为int。如果直接从数字转换为整数,则不会发生此问题。
结果:
SELECT a,CAST(a as real) as real_a,CAST(CAST(a as real) as int) as int_a FROM test;
在SQL Server 2017( sql fiddle )中运行的同一实验给出了以下内容: http://sqlfiddle.com/#!18/45aca/2
a: 778881838.81
real_a: 778881860
int_a: 778881856
我可以(模糊地)理解..19E + 08的情况,但是为什么双重转换情况下+18有所不同?这个数字对我来说似乎完全是任意的。
答案 0 :(得分:2)
好的,首先,SQL Server 2017中real_a
的结果为 not 778881860
。确切地说,它是778881856
,就像在SQL Server 2008中一样。 client 如何显示此浮点值是另一回事-Management Studio向我展示了7.788819E+08
, sqlcmd
产生7.7888186E+8
,显然SQL Fiddle完全使用了另一个库(我个人会遇到一个问题,因为它会掩盖大量数字!)
该值不是任意的。 REAL
是单精度浮点类型,不能完全表示778881838.81
。最接近的可表示值是778881856
,因此是您的结果(下一个较低的可表示值是778881792
)。无需强制转换为INT
,您可以使用
SELECT STR(CONVERT(REAL, CONVERT(NUMERIC(38, 6), 778881838.810000)), 40, 16)
778881856.0000000000000000
您对“ double”一词的使用使我认为您将FLOAT
与它混淆了,FLOAT
是双精度浮点类型。 SELECT STR(CONVERT(FLOAT, CONVERT(NUMERIC(38, 6), 778881838.810000)), 40, 16)
778881838.8099999400000000
也不能完全代表此值,但它更接近:
INT
将此值转换为778881838
会产生(截断的)NUMERIC
。 (此截断是documented,在转换为ROUND
时不会发生;如果您更愿意使用778881839
,则需要先{{1}}进行转换。)>
答案 1 :(得分:1)
其他想要在本地进行测试的人的简单示例:
DECLARE @test numeric (38,6)='778881838.810000'
SELECT @test as [Original],CAST(@test as real) as real_a,CAST(CAST(@test as real) as int) as int_a;
Original real_a int_a
778881838.810000 7.788819E+08 778881856
您可能需要Microsoft的人员来说明SQL引擎内部的工作方式(当然还要知道他们为什么做出此决定),但是我会在理由上作文章:
如果在第一次强制转换时以科学计数法输出,然后需要将其转换为整数,则将整数设置为将导致该科学计数法的最小值。它以6而不是5结尾,因为舍入5并不能始终对所有情况进行舍入(例如Alternating tie-breaking)。
但是,无论出于何种原因,如果精度很重要,则应该显式转换为具有定义精度的数字数据类型。
答案 2 :(得分:0)
当您想从float或real转换为字符数据时,使用STR字符串函数通常比CAST()更有用。这是因为STR支持对格式进行更多控制。有关更多信息,请参见STR(Transact-SQL)和函数(Transact-SQL)。
请找到以下链接
STR example 使用以下查询:-
SELECT a,STR(a ,38,6) as real_a,CAST(CAST(a as real) as int) as int_a FROM test;
如果发现任何问题,请告诉我。