您应该在SQL Server中选择MONEY还是DECIMAL(x,y)数据类型?


442

我很好奇money数据类型和类似的东西之间是否存在真正的区别decimal(19,4)(我相信这是金钱在内部使用的东西)。

我知道这money是特定于SQL Server的。我想知道是否有令人信服的理由选择一个而不是另一个?大多数SQL Server示例(例如AdventureWorks数据库)使用的money不是decimal价格信息。

我应该继续使用money数据类型,还是使用十进制有好处?钱是较少键入的字符,但这不是正当的理由:)


12
DECIMAL(19, 4) 是一个流行的选择,请在检查并在此处检查世界货币格式,以确定要使用的小数位数,希望有所帮助。
shaijut

我想知道为什么money数据类型具有4个小数位..而不是2.即1美元等于100美分,所以只需要2个小数位?为了存储少于9999.99美元的金额记录,我将使用数据类型为decimal(6,2)。我不关心除法或乘法运算,而只是存储和求和..
艾伦F

Answers:


326

绝对不要用钱。它不是精确的,它是纯垃圾。始终使用十进制/数字。

运行此命令以了解我的意思:

DECLARE
    @mon1 MONEY,
    @mon2 MONEY,
    @mon3 MONEY,
    @mon4 MONEY,
    @num1 DECIMAL(19,4),
    @num2 DECIMAL(19,4),
    @num3 DECIMAL(19,4),
    @num4 DECIMAL(19,4)

    SELECT
    @mon1 = 100, @mon2 = 339, @mon3 = 10000,
    @num1 = 100, @num2 = 339, @num3 = 10000

    SET @mon4 = @mon1/@mon2*@mon3
    SET @num4 = @num1/@num2*@num3

    SELECT @mon4 AS moneyresult,
    @num4 AS numericresult

输出:2949.0000 2949.8525

对于一些说您不会用钱除钱的人:

这是我计算相关性的查询之一,将其更改为金钱会得出错误的结果。

select t1.index_id,t2.index_id,(avg(t1.monret*t2.monret)
    -(avg(t1.monret) * avg(t2.monret)))
            /((sqrt(avg(square(t1.monret)) - square(avg(t1.monret))))
            *(sqrt(avg(square(t2.monret)) - square(avg(t2.monret))))),
current_timestamp,@MaxDate
            from Table1 t1  join Table1 t2  on t1.Date = traDate
            group by t1.index_id,t2.index_id

61
“从不”是一个有力的词。“ Money”对于将结果转换为该类型以便以对文化敏感的方式显示给用户非常有用,但是您没错,将其用于计算本身是非常不好的。
Joel Coehoorn

36
您的示例没有意义,因为没有人会相乘两个money类型的对象。如果要证明自己的观点,则需要比较将货币乘以小数与将十进制乘以小数。
布赖恩

27
..但是,为什么金钱*金钱没有金钱的精确性仍然令人困惑。
学习

4
@Learning:确实有一定的金钱精度。但是,最终仍然会产生四舍五入的错误,这些错误会随着时间的推移而累积。十进制类型不使用二进制算术:它保证获得与在纸上执行的结果相同的基数10。
Joel Coehoorn,

55
乘法和除法的money以上money之外,这个“插图”是操纵。有记录的事实money具有固定且非常有限的精度。在这种情况下,应首先相乘,然后相除。在此示例中,更改运算符的顺序将得到相同的结果。a money本质上是64位int,如果要处理int,则在相除之前会相乘。
Andriy M

273

SQLMenace说钱不准确。但是,您不会将金钱乘以/除以金钱!3美元乘50美分多少钱?150美分?您将货币乘以/除以标量,标量应为小数。

DECLARE
@mon1 MONEY,
@mon4 MONEY,
@num1 DECIMAL(19,4),
@num2 DECIMAL(19,4),
@num3 DECIMAL(19,4),
@num4 DECIMAL(19,4)

SELECT
@mon1 = 100,
@num1 = 100, @num2 = 339, @num3 = 10000

SET @mon4 = @mon1/@num2*@num3
SET @num4 = @num1/@num2*@num3

SELECT @mon4 AS moneyresult,
@num4 AS numericresult

结果正确:

货币结果数值结果
--------------------- ----------------------------- ----------
2949.8525 2949.8525

money只要您不需要多于4个十进制数字,并且您确保您的标量(不代表金钱)为decimals,就可以了。


57
一美元钞票能为您带来多少个1美分的硬币?一个答案需要金钱。
学习

48
@在那种情况下,学习的结果不是金钱,而是浮游物。(基本尺寸分析。)
理查德

11
@Learning:您问数据库多少钱在一美元吗?无论如何,那将返回正确的结果。他的问题是金钱/货币只能精确到四位数(0.2949),然后乘以10000则变成2949.0000。
配置器

26
@mson他是正确的,不会像您基于一行评论所做的那样进行个人批评,这没有帮助。如果他不除以0.01美元,而是除以0.01,那么结果就是100美元而不是100。1美元中有100美分,而不是100美分。单位很重要!绝对有一个潜水的地方,也许是金钱乘以金钱。
andrewb 2013年

19
如果我想花1000美元购买价格为36美元的股票,那不是合法的用法money / money吗?答案是没有附加美元符号的整个单位,这是正确的!这是一个非常合理的场景,表明由于类似这样的奇怪情况,money不是很好的数据类型,因为结果总是被截断以适应,money而不是遵循精度和小数位数的常规规则。如果要计算一组money值的标准偏差怎么办?是的,Sqrt(Sum(money * money))(简化)实际上确实有意义。
ErikE 2014年

59

如果您不知道自己在做什么,一切都会很危险

即使是高精度的十进制类型也无法保存一天:

declare @num1 numeric(38,22)
declare @num2 numeric(38,22)
set @num1 = .0000006
set @num2 = 1.0
select @num1 * @num2 * 1000000

1.000000 <-应该为0.6000000


money类型是整数

的文字表述smallmoney,并decimal(10,4)可能看起来一样,但是这并不能让他们互换。当您看到日期存储为时,您会畏缩varchar(10)吗?这是同一回事。

在幕后,money/ smallmoney只是一个bigint/ / int 文本表示形式中的小数点money是视觉绒毛,就像yyyy-mm-dd日期中的破折号一样。SQL实际上并没有在内部存储它们。

关于decimalvs money,选择适合您需求的任何东西。money存在这些类型是因为非常常见的是将计费值存储为单位的1/10000的整数倍。此外,如果您要处理的是实际的金钱和计算,而不是简单的加减运算,则不应在数据库级别执行此操作! 使用支持Banker's Rounding(IEEE 754)的库在应用程序级别执行此操作


1
您能否解释一下此示例中发生的情况?
谢尔盖·波波夫

4
规模溢出。在小比例尺上,numeric(a,b)* numeric(c,d)产生numeric(a-b + c-d + 1,max(b,d))。但是,如果(a + b + c + d)> 38,SQL会限制小数位数,从小数点一面掠夺精度到填充整数一面,从而导致舍入错误。
2014年

所有数值计算都容易因缩放而损失精度:取而代之的是选择select 1000000 * @ num1 * @ num2
米奇·

Anon的示例是特定于版本和数据库的。但要注意一点是有效的。在sqlanywhere 1.1版中,该示例确实给出了0.600000。(我知道我们在这里谈论ms sql)。关于其他数据库中缺少货币类型的另一点,有一些方法可以将十进制或数字称为货币,例如创建域。
gg89 2014年

3
我认为这是最有启发性的答案,因为您不仅解释了所传播的错误,而且还解释了背后的实际情况以及为什么MONEY首先存在。我不确定为什么要花4年的时间才能得到这个答案,但这也许是因为对计算“问题”的关注过多,而不是金钱与小数的原因和方式。
史蒂夫·塞尔

44

我意识到WayneM曾说过他知道金钱是特定于SQL Server的。但是,他在问是否有任何理由要用十进制来代替金钱,反之亦然,我认为还有一个明显的理由应该指出,那就是使用十进制意味着不必更改DBMS就少了一件事情-可能会发生。

使您的系统尽可能灵活!


1
可能但在理论上,您可以在另一个DBMS中声明一个名为Money的域(该域支持域的声明)。
辩论者2015年

作为当前将SQL Server数据库转换为Amazon Redshift的人,我可以保证这是一个真正的问题。除非特殊的商业理由要使用,否则请尽量避免使用特定于数据库平台的数据类型。
内森·格里菲思

@Nathn给出了Redshift与PostgreSQL或SQL Server相比的弱类型系统,例如没有date类型,我想说你总是会遇到这样的问题。您应该说“当数据库已经提供了更好,更符合标准的类型并警告不推荐使用的类型时,请不要使用不推荐使用的类型”。
Panagiotis Kanavos '17年

@PanagiotisKanavos-钱不被弃用
Martin Smith

@MartinSmith对此感到震惊,因为它实际上无法处理2017年的BTC之类的货币价值。或者区分英镑和欧元金额-即使它们趋于平价:P。无论如何,转移到Redshift会带来很多痛苦
Panagiotis Kanavos

32

好吧,我喜欢MONEY!它比便宜1字节DECIMAL,并且计算速度更快,因为(在幕后)加法和减法运算本质上是整数运算。@SQLMenace的示例(对不了解的用户发出警告,这是一个很好的警告)可以同样地应用于INT结果为零的egers。但这没有理由不使用整数-在适当情况下

因此,这是完全“安全”的,适合MONEY在您要处理的事物上MONEY使用并根据其遵循的数学规则(与INTeger一样)使用它。

如果SQL Server 可能MONEYDECIMALs的除法和乘法推广到s(或FLOATs?),会更好,但是他们没有选择这样做。他们也没有选择提拔INT egers提升为FLOATs。

MONEY没有精度问题;那DECIMAL得面对具有计算过程中使用较大的中间类型仅仅是使用该类型的“功能”(我不肯定实际上多远,即“功能”延伸)。

要回答具体问题,有“令人信服的理由”吗?好吧,如果你想在一个绝对最大的性能SUM(x),其中x可能是因为存在DECIMALMONEY,然后MONEY将具有优势。

另外,别忘了它的表亲较小,SMALLMONEY只有4个字节,但它的最大容量为214,748.3647,这对于金钱来说是很小的,因此通常不太适合。

为了证明使用较大的中间类型的意义,如果将中间明确地分配给变量,则会DECIMAL遇到相同的问题:

declare @a decimal(19,4)
declare @b decimal(19,4)
declare @c decimal(19,4)
declare @d decimal(19,4)

select @a = 100, @b = 339, @c = 10000

set @d = @a/@b

set @d = @d*@c

select @d

产生2950.0000(好吧,所以至少要DECIMAL取整而不是MONEY截断,与整数相同。)


21
MONEY大号 少1个字节DECIMAL,最高精度为19位。但是,大多数现实世界中的货币计算(最多999万美元)都可以放入DECIMAL(9, 2),只需要五个字节。您可以保存大小,少担心舍入误差,并且使你的代码的可移植性。

尽管@JonofAllTrades是正确的,但您也可以通过简单地使用包含几美分或美分的整数来获得整数性能,并保存编码小数点位置的多余字节,并且在将小数加在一起时必须进行检查。
dsz 2015年

“因此,当您要处理的是货币时,并根据其遵循的数学规则使用货币,这是绝对'安全'且适当的选择。”->但是,另请参阅阿农的回答:“如果不这样做,一切都是危险的知道你在做什么“。您永远无法预测人们最终将如何查询正在创建的系统,最好是避免在可能的情况下误用的可能性。微小的存储收益不值得恕我直言。
内森·格里菲斯

1
尝试存储比特币值。它有8位小数
Panagiotis Kanavos '17

13

我们刚刚遇到了一个非常相似的问题,除了顶级演示文稿之外,我现在几乎是永远不使用Money的+1。由于历史原因,我们有多个表(实际上是一张销售凭单和一张销售发票),每个表都包含一个或多个Money字段,并且我们需要执行按比例计算,以计算出与每个发票相关的总发票税额是多少行在销售凭证上。我们的计算是

vat proportion = total invoice vat x (voucher line value / total invoice value)

这导致现实世界中的金钱/金钱计算,这会导致除法部分的比例误差,然后乘以错误的增值税比例。随后添加这些值时,我们得出的增值税比例之和不等于发票总值。如果括号中的任何一个值都是小数点(我将这样强制转换其中一个),则增值税比例将是正确的。

我猜想,当最初没有括号时,它就可以工作,因为涉及的值较大,所以它实际上是在模拟更高的比例尺。我们添加了括号,因为它首先进行了乘法运算,这在极少数情况下降低了可用于计算的精度,但是现在这引起了这个更为常见的错误。


8
但这之所以如此,是因为无知的开发人员忽略了增值税计算规则和已记录的货币精度限制。
TomTom 2014年

1
@TomTom,但您要指出的是滥用此数据类型的可能性,这是一个主张完全避免使用它的论点。
内森·格里菲斯

@Nathan否。我指出,作为永远不使用它的理由而给出的论点基本上是无能的开发人员,因此论点是虚假的。
TomTom

1
@TomTom可悲的是,有许多不称职的开发人员(以及许多出色的开发人员),对我而言,除非我可以保证将来会如何查询这些数据,而且没有人会犯这里描述的那种错误,最好还是谨慎一点,并使用十进制类型。就是说,阅读完所有这些答案后,我看到有一些特定的用例,其中金钱是使用的最佳类型,除非有很好的用例,否则我不会使用它(例如,列只会用到由SUM汇总,批量加载大量财务数据)。
内森·格里菲斯

@TomTom不仅仅是精度限制。内部表示形式也会引起问题。金钱是一种方便类型,可以抓住无知的开发人员,而不是开发人员滥用的出色类型。增值税示例,无论计算规则有多大,在将特定内容应用于通用时,显然应该吓退那些无知的开发人员。
Gerard ONeill '18

12

作为对其他答案总推论的对策。请参阅金钱的许多好处…数据类型!在《SQLCAT关系引擎指南》中

我具体指出以下几点

在客户实施方面,我们发现了一些有关money数据类型的有趣性能指标。例如,将Analysis Services设置为货币数据类型(从两倍)以匹配SQL Server货币数据类型时,处理速度(行/秒)提高了13%。为了在SQL Server Integration Services(SSIS)中获得更快的性能以在30分钟内加载1.18 TB(如SSIS 2008-世界记录ETL性能中所述),观察到更改了四个十进制(9,2)列,其大小为TPC-H LINEITEM表中的5个字节变为金钱(8个字节),使批量插入速度提高了20%...性能提高的原因是由于SQL Server的表格数据流(TDS)协议,它具有关键设计原则,可以以紧凑的二进制格式传输数据,并尽可能接近SQL Server的内部存储格式。根据经验,在SSIS 2008(使用Kernrate的世界纪录ETL性能测试)中观察到了这一点。当数据类型从十进制转换为金钱时,协议显着下降。这使得数据传输尽可能高效。复杂数据类型比固定宽度类型需要更多的解析和CPU周期来处理。

因此,问题的答案是“取决于”。您需要对某些算术运算更加谨慎,以保持精度,但是您可能会发现,出于性能考虑,这值得这样做。


那你将如何存储比特币
Panagiotis Kanavos

@PanagiotisKanavos-我不知道。我从未研究过比特币,对此一无所知!
马丁·史密斯

6

我想根据我自己的专业知识和经验给出不同的货币与数值观点...我的观点是货币,因为我已经使用货币很长时间了,并且从未真正使用过数值。 。

专业版:

  • 本机数据类型。它使用与CPU寄存器(32或64位)相同的本机数据类型(整数),因此计算不需要不必要的开销,因此它更小更快。 ... MONEY需要8个字节和NUMERICAL(19,4 )需要9个字节(大12.5%)...

    只要用于货币(作为货币),货币就会更快。多快?我SUM对100万个数据的简单测试显示,MONEY为275毫秒,NUMERIC 517毫秒...这几乎快了一倍 ...为什么进行SUM测试?查看下一个专业

  • 最好的钱。金钱最适合用于存储金钱和进行操作,例如会计。一个SUM操作完成后,一个报表可以运行数百万个加法(SUM)和几个乘法。对于非常大的会计应用程序,它的速度几乎快一倍,并且非常重要...
  • 货币精度低。现实生活中的金钱并不需要非常精确。我的意思是,很多人可能会关心1美分,但是0.01美分呢?实际上,在我国,银行不再关心美分(小数点后的数字)。我不知道美国银行或其他国家...

金钱骗局:

  • 精度有限。MONEY的精度只有4位(逗号之后),因此在进行除法运算之前必须先对其进行转换...但是然后money不必再那么精确了,它也可以用作货币,而不仅仅是数...

但是...很大,但是您的应用程序甚至涉及真实货币,但不要在诸如会计之类的许多SUM操作中使用它。如果您使用大量除法和乘法运算,则不应使用MONEY ...


1
如果您要说钱比十进制要快,那么您需要告诉我们诸如rdbms,在什么硬件上,在什么OS上运行,在什么特定数据上运行什么特定查询的事情。此外,如果它不完全准确,则我不会对其进行任何财务操作,因为税务员会很不高兴拿回坏账。例如,如果您使用美元交易,则需要支付千分之一的费用。如果没有钱,那就没用了。
HaakonLøtveit17年

3
@HaakonLøtveit整个问答环节都涉及SQL Server数据类型“ money”,因此我认为他们不需要在答案中指定此内容。在某些情况下(例如,加载数据),金钱可以比十进制更快地使用,请参阅马丁史密斯的答案以获取更多详细信息。我同意大多数答案,因为某些使用案例可能使Money比十进制更有效的选择,但是,除非有非常令人信服的案例使用,否则我认为应该避免使用。
内森·格里菲斯

1
比特币有8位小数。您甚至无法将其存储在money列中
Panagiotis Kanavos

4

先前的所有帖子都提供了有效的观点,但是有些帖子无法准确回答问题。

问题是:当我们已经知道钱是一种不太精确的数据类型并且如果在复杂的计算中使用会导致错误,为什么有人会喜欢钱?

当您不进行复杂的计算并且可以将这种精度用于其他需求时,便会使用金钱。

例如,当您不必进行这些计算时,需要从有效的货币文本字符串中导入数据。此自动转换仅适用于MONEY数据类型:

SELECT CONVERT(MONEY, '$1,000.68')

我知道您可以制作自己的导入例程。但是有时您不想使用全球特定语言环境格式重新创建导入例程。

另一个示例,当您不必进行这些计算(您只需要存储一个值)并且需要保存1个字节(金钱占用8个字节,而十进制(19,4)占用9个字节)时。在某些应用程序中(快速CPU,大RAM,慢速IO),例如仅读取大量数据,这也可能更快。


2

当您需要对值进行乘/除时,您不应该使用金钱。货币的存储方式与存储整数的方式相同,而十进制存储为小数点和十进制数字。这意味着金钱在大多数情况下会降低准确性,而十进制只有在转换回原始比例时才会这样做。货币是定点的,因此其规模在计算过程中不会改变。但是,因为当它以十进制字符串形式打印时是固定点(与以2为基数的字符串中的固定位置相对),所以精确地表示了小数位数为4的值。因此,对于加法和减法,钱是可以的。

小数点在内部以10为基数表示,因此小数点的位置也以10为基数。就像金钱一样,这使得它的小数部分准确地代表了它的价值。区别在于十进制的中间值最多可以保持38位精度。

对于浮点数,该值以二进制形式存储,就好像它是整数一样,而小数点(或二进制,ahem)点的位置相对于表示该数字的位。因为它是二进制小数点,所以以10为基数的数字在小数点后立即失去精度。用这种方式无法精确表示1/5或0.2。钱和小数都不受此限制。

将货币转换为十进制,执行计算,然后将结果值存储回money字段或变量中,这很容易。

从我的观点来看,我希望发生在数字上的事情不必花太多时间去思考。如果所有计算都将转换为十进制,那么对我来说,我只想使用十进制。我将把钱字段保存起来以供显示。

从大小上看,我没有太大的改变可以改变主意。Money占用4-8个字节,而十进制可以是5、9、13和17。这9个字节可以覆盖8个bytes货币可以覆盖的整个范围。按索引编制(比较和搜索应具有可比性)。


感谢您的支持。我正在看这个,当我明白我想说的话时,我也看到它很混乱。也许以后再用一些小数示例来重写它。
Gerard ONeill '18

2

我找到了在准确性主题中使用小数而不是金钱的原因。

DECLARE @dOne   DECIMAL(19,4),
        @dThree DECIMAL(19,4),
        @mOne   MONEY,
        @mThree MONEY,
        @fOne   FLOAT,
        @fThree FLOAT

 SELECT @dOne   = 1,
        @dThree = 3,    
        @mOne   = 1,
        @mThree = 3,    
        @fOne   = 1,
        @fThree = 3

 SELECT (@dOne/@dThree)*@dThree AS DecimalResult,
        (@mOne/@mThree)*@mThree AS MoneyResult,
        (@fOne/@fThree)*@fThree AS FloatResult

DecimalResult> 1.000000

理财结果> 0.9999

FloatResult> 1

只需对其进行测试并做出决定。


2
我们非常想听听(读到)您的结论。
Peter Mortensen '18

@PeterMortensen我想如果我想在Money和Decimal类型之间具有完整性和准确性,我的决定应该是Decimal。
QMaster

1
考虑使用上述实际结果更新您的答案。然后,您的结论对于所有阅读:-)的人来说将立即显而易见
-Agreax

1
@Ageax将结果添加到答案中。
QMaster

1

我刚刚看到此博客文章:SQL Server中的Money vs.Decimal

基本上说钱有一个精确的问题...

declare @m money
declare @d decimal(9,2)

set @m = 19.34
set @d = 19.34

select (@m/1000)*1000
select (@d/1000)*1000

对于money类型,您将得到19.30而不是19.34。我不确定是否存在将钱分成1000个部分进行计算的应用场景,但是此示例确实暴露了一些限制。


15
这不是“问题”,而是“按规格”:“ moneysmallmoney数据类型精确到它们代表的货币单位的千分之一。” 如msdn.microsoft.com/en-us/library/ms179882.aspx中所述,因此任何说“这是垃圾”的人都不知道他在说什么。
Albireo
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.