存储货币值应该使用什么数据类型?

6
我已经阅读过,应该使用货币,但在今天快节奏的世界中,也许这已经过时了。

那我应该使用什么?

5个回答

13

不,货币仍然应该有效。


1
如果开发人员向“money”列插入一堆值,例如VALUES (12.34), (10.79), (18.43),然后选择它们,他将看到这些值与输入的值相同,而不是12.3400, 10.7900, 18.4300。这是一个严重的问题,因为它会让他认为“money”数据类型只存储2个小数位。然后,他执行INSERT MyTable (PurchaseAmount) SELECT Shares * PricePerShare,而没有截断为2个小数位。糟糕!此外,“money”可以被“money”除(我想花费1000美元购买每股36美元的股票)和乘(我想计算标准偏差)。 - ErikE
@ErikIe,抱歉,任何不理解数据库中存储的值和应用程序中格式化值之间区别的开发人员都不应该涉足这个领域... 试试 Declare @m Money = 123.45 Select str(@m, 8, 4) 或者更好的方法是,试试 Declare @m Money = 123.45 Select str(@m, 12, 9)。格式化后的值与存储的值几乎没有关系... 讨论“存储”的小数位数甚至没有意义... 如果您将值2保存在类型为Numeric(12,11)的字段中,您真的认为它没有存储十一个零吗? - Charles Bretana
数据类型应该与存储的值有很大关系,而money却隐藏了这一点。我个人没有理解问题,但事实是许多软件开发公司充满了那些并不真正了解数据库的开发人员,即使如此,他们仍然是优秀的开发人员。money是一种可怕的数据类型,因为它隐藏了掩码后面的精度和比例。你提到的numeric(12,11)的例子并不好,因为开发人员(虽然在SQL Server方面缺乏经验,但通常很聪明)可以看到有11个小数位,这与money不同。 - ErikE
2
数据类型与存储的数据有密切关系,但与格式化显示数据无关。如果你认为它没有在名称中指定精度就很糟糕,那么浮点数、实数、十进制数(未指定精度)和数字、整数、小整数和微整数也同样糟糕。它们也没有指定其大小和范围。整数应该被命名为Int32吗?datetime和smallDatetime呢?它们应该被命名为datetime200ms和datetime2sec吗? - Charles Bretana
1
有些语言(例如C#)确实会在数据类型的名称中添加类似的内容。但这只是一种方便 - 没有其他意义。关于数据库实现,有很多值得抱怨的事情,比起确保数据类型的名称传达规范,更值得关注的事情有很多。开发人员应该知道很多东西,有很多细节需要了解,我们当然不能期望每个人都知道所有这些细节,但至少要知道数据类型的定义是什么,或者如何查找相关信息,这应该是最基本的。 - Charles Bretana
显示剩余6条评论

13

Papuccino,

我不建议使用money和smallmoney类型,除非你确定你只需要进行加减法运算。如果你可能会处理汇率、百分比等问题,使用这些类型会导致真正的问题。

这里只是举了一个小例子来展示在涉及除法时使用money、decimal和float之间的差异。可能会出现差异更明显的例子。

declare @m1 money, @m2 money, @m3 money
declare @d1 decimal(19,4), @d2 decimal(19,4), @d3 decimal(19,4)
declare @f1 float, @f2 float, @f3 float;
set @m1 = 1.00;
set @m2 = 345.00;
set @m3 = @m1/@m2;
set @d1 = 1.00;
set @d2 = 345.00;
set @d3 = @d1/@d2;
set @f1 = 1.00;
set @f2 = 345.00;
set @f3 = @f1/@f2;
select @m3, @d3, @f3;

结果:0.0028 0.0029 0.00289855072463768

根据所处行业,可能存在指南或规定来帮助您决定正确的数据类型。没有唯一正确的答案。

附加说明:

您是正确的,money/money 不应该是金钱,但是 SQL Server(莫名其妙地)产生了两个金钱值商的金额结果,这是错误的。尽管它毫无意义,但正如下面的示例所示,您将得到这样的结果:

declare @m1 money, @m2 money;
declare @d1 decimal(19,4), @d2 decimal(19,4);
set @m1 = 1.00;
set @m2 = 345.00;
set @d1 = 1.00;
set @d2 = 345.00;
select @m1/@m2, @d1/@d2

结果:0.0028 0.0028985507246376811

类型为money的结果0.0028比正确结果低了3-4%。

当然,有许多情况需要对货币值进行除法运算。使用money类型的危险在于商是错误的类型(并且答案与正确答案相差甚远)。需要除以货币的问题示例:

假设你兑换320元人民币,银行给你47.3美元。你得到的汇率是多少?

假设你投资23美元,一年后价值31美元。你的回报率是多少?

这两个计算都需要除以货币值。


3
我认为这个例子是虚假的...没有真正存在需要将货币除以货币并得到货币作为输出的函数。这是毫无意义的...就像说3个梨/2个梨=1.5个梨一样。这是错误的。单位很重要。 - Charles Bretana
1
如果你在数据库层面进行货币转换,那么在 int、float 和 decimal 类型之间做出选择是你最不用担心的问题。 - Anon
说“不应该使用money因为存在除法问题”就像说“不应该使用int因为存在除法问题”。实际上,select 9 / 10得到的是0,这是非常错误的!然而我们一直在使用int,你只需要理解什么是整数除法。Money在除法过程中不改变精度,就像int一样。因此,小数点后四位之后的数字会丢失,就像在int中小于零的小数点后的数字一样。 - GSerg
Decimal,另一方面,在除法期间使用复杂规则来改变其精度。 根据这些规则,decimal(19,4)/ decimal(19,4)decimal(38,19),它将结果存储在第19位小数点上,然后在存储在decimal(19,4)中时会四舍五入 - 另一个关于转换精度的decimal规则。 因此,没有什么可说money存在问题而decimal不存在问题。 它们只是不同的。 - GSerg

2
为什么货币会过时呢?它可以超过900万亿,是联邦政府预算的数百倍——你可能需要存储什么样的资金量?(我猜也许津巴布韦元最终成为了一个问题,但他们一直通过增加数十亿和数万亿来重置它,去年4月终于被暂停;现在津巴布韦使用美元或其他外国货币进行支付和会计)。

2
虽然最大值很重要,但我认为十进制精度同样重要。我相信美国国税局建议将货币计算到小数点后4位,而货币类型正好可以做到这一点。 - Peter M
没错,这只占十万分之一的部分——但我无法相信原帖作者会认为那会过时(被什么过时了...?),所以我猜他们担心的是大量数据(如果津巴布韦元没有经常重置,九百万亿的津巴布韦元也买不到牙签;-)。 - Alex Martelli
2
它超过了900万亿,是联邦政府预算的数百倍...但与联邦债务相比如何呢? :P - Chris Lutz
接近100倍之大--见http://www.brillig.com/debt_clock/ -- 你的意思是什么?!-) - Alex Martelli

1

我同意把钱除以钱是无意义的。但是把钱除以天数是真实的。如果你正在将少量的钱按天数分配,你需要注意这种现象。我将钱转换为浮点数,进行计算,最终将结果存储到货币数据类型中。希望这能帮到你。


1

令我惊讶的是之前没有人提到过。

money 占用 8 字节。 decimal 占用 5、9、13 或 17 字节,具体取决于精度(精度不是小数点后的位数,而是将存储的最大十进制数字总位数,包括小数点左右两侧)。因此,要模仿 money 支持的值范围(-922,337,203,685,477.5808 至 922,337,203,685,477.5807),需要使用 decimal(19,4)

+-------------------+---------------+
| decimal precision | Storage bytes |
+-------------------+---------------+
| 1 - 9             |             5 |
| 10-19             |             9 |
| 20-28             |            13 |
| 29-38             |            17 |
+-------------------+---------------+

如果将9个字节存储而不是8个似乎并不是一件大事,你应该记住money是一种本地处理器类型,如64位bigint,但decimal不是。这意味着对数十亿个money值求和比求和decimal值更快。执行其他计算例如除法将会有更大的差异。
在我的SQL Server 2014 Express虚拟机上,我运行了这个简单的测试。dbo.Numbers是一个带有1至10,000值的intNumber的10,000行表。
CREATE TABLE [dbo].[Numbers](
    [Number] [int] NOT NULL,
CONSTRAINT [PK_Numbers] PRIMARY KEY CLUSTERED 
(
    [Number] ASC
))

我在SQL Sentry Plan Explorer中运行了这个:

DECLARE @VarM money = 1234.5678;

SELECT
    AVG(@VarM / N1.Number)
FROM 
    dbo.Numbers AS N1
    CROSS JOIN dbo.Numbers AS N2
;


DECLARE @VarD decimal(19,4) = 1234.5678;

SELECT
    AVG(@VarD / N1.Number)
FROM 
    dbo.Numbers AS N1
    CROSS JOIN dbo.Numbers AS N2
;

执行计划对于这两个查询是相同的(当然,Number 会有不同的隐式转换:转为 moneydecimal),但运行时间却是15秒和40秒。这对我来说是非常明显的。

money vs decimal


当然,您需要知道money只有4个小数位。如果进行计算,您需要了解类型(以及它们的优先级,即什么会被隐式转换为什么),并确保中间结果是适当的类型,如果需要,通过将操作数强制转换为正确的类型来实现。这个警告适用于任何类型的计算,不仅仅是money。当您除以两个int值时,应该知道结果是int,而不会惊讶于4 / 5 = 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接