【问题标题】:Should you choose the MONEY or DECIMAL(x,y) datatypes in SQL Server?您应该在 SQL Server 中选择 MONEY 还是 DECIMAL(x,y) 数据类型?
【发布时间】:2010-10-09 15:04:33
【问题描述】:

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

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

我应该继续使用 money 数据类型,还是改用小数有好处?钱是要输入的字符更少,但这不是一个正当的理由:)

【问题讨论】:

  • DECIMAL(19, 4) 是一个流行的选择检查this也检查here世界货币格式来决定使用多少小数位,希望有所帮助。
  • 我想知道为什么货币数据类型有 4 位小数......而不是 2。即 100 美分 1 美元所以只需要 2 位小数?为了存储少于 9999.99 美元的金额记录,我打算使用十进制(6,2)的数据类型。我不关心除法或乘法计算,只关心存储和求和..
  • 有些货币被分成小于数百个的部分,即巴林第纳尔被分成1000个fils
  • 有充分的理由将金钱除以金钱。我发现这个帖子是因为我有“钱/钱”的情况并且得到的答案不准确。计算是为了确定一美元与另一美元的比率。它似乎总是被截断到小数点后 4 位,所以我得到 0.0109,我正在寻找 0.0110(正确四舍五入的答案是 0.01095125。

标签: sql-server types


【解决方案1】:

永远不要用钱。不精确,纯属垃圾;始终使用小数/数字。

运行这个看看我的意思:

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

【讨论】:

  • “从不”是一个强词。 “金钱”对于将结果转换为该类型以以文化敏感的方式显示给用户很有用,但您说得对,将其用于计算本身非常糟糕。
  • 你的例子没有意义,因为没有人会乘以两个货币类型的对象。如果你想证明你的观点,你需要将钱乘以小数与小数乘以小数进行比较。
  • .. 但是为什么钱 * 钱没有钱的精确度仍然令人费解。
  • @Learning:它确实有钱的精确度。但是,您最终仍会遇到随时间累积的舍入误差。十进制类型不使用二进制算术:它保证它得到与纸上相同的以 10 为底的结果。
  • moneymoney 的乘法和除法不谈,这个“插图”是操纵性的。 money 具有固定且非常有限的精度,这是一个有据可查的事实。在这种情况下,应该先乘,然后除。在这个例子中改变运算符的顺序,你会得到相同的结果。 money 本质上是一个 64 位的 int,如果要处理 int,则在除法之前先乘以。
【解决方案2】:

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

得到正确的结果:

moneyresult numericresult
--------------------- ----------------- ----------
2949.8525 2949.8525

money 很好,只要您不需要超过 4 个十进制数字,并且确保您的标量(不代表金钱)是 decimals。

【讨论】:

  • 一张一美元的钞票可以给你多少 1 美分硬币?解决这个问题需要钱/钱。
  • @Learning 在这种情况下,结果不是钱,而是浮动。 (基本维度分析。)
  • @Learning:你问数据库一美元有多少美分?无论如何,这将返回正确的结果。他的问题是,money/money 只能精确到四位数(当时是 0.2949),然后乘以 10000 就变成了 2949.0000。
  • @mson 他是对的,不要像你那样根据一行评论进行个人批评,这没有帮助。如果他不除以 0.01 美元而是 0.01,那么结果是 100 美元而不是 100 美元。一美元有 100 美分,而不是 100 美分。单位很重要!绝对有一个很大的地方可以潜水,甚至可以乘以金钱。
  • 如果我想花 1000 美元购买价格为 36 美元的股票,这不是 money / money 的合法用法吗?答案是没有附加美元符号的整个单位,这是正确的!这是一个完全合理的场景,表明由于像这样奇怪的边缘情况,money 不是一个好的数据类型,因为结果总是被截断以适应money,而不是遵循正常的精度规则和规模。如果要计算一组money 值的标准差怎么办?是的,Sqrt(Sum(money * money))(简化版)确实有意义。
【解决方案3】:

我刚刚看到这篇博文:Money vs. Decimal in SQL Server

这基本上是说金钱有精度问题......

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份进行计算的应用场景,但是这个例子确实暴露了一些限制。

【讨论】:

  • 这不是“问题”,而是“按规范”:«moneysmallmoney 数据类型精确到它们所代表的货币单位的十分之一。»正如msdn.microsoft.com/en-us/library/ms179882.aspx 中所说,所以任何说“这是垃圾”的人都不知道他在说什么。
【解决方案4】:

我们刚刚遇到了一个非常相似的问题,我现在非常赞成 +1,因为我除了在顶级演示中之外从不使用 Money。由于历史原因,我们有多个表(实际上是销售凭证和销售发票),每个表都包含一个或多个 Money 字段,我们需要执行按比例计算以计算出总发票 Tax 与每个表相关的比例销售凭证上的一行。我们的计算是

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

这会导致现实世界的货币/货币计算导致除法部分的比例错误,然后乘以不正确的增值税比例。当这些值随后被添加时,我们最终得到增值税比例的总和,这些总和不等于发票总价值。如果括号中的任何一个值都是小数(我将要转换其中一个),那么增值税比例将是正确的。

当括号最初不存在时,这曾经起作用,我猜是因为涉及的值更大,它有效地模拟了更高的比例。我们添加了括号,因为它首先进行乘法运算,这在极少数情况下会破坏可用于计算的精度,但现在这导致了这个更常见的错误。

【讨论】:

  • 但这只是因为一个无知的开发人员忽略了增值税计算规则和记录在案的货币精度限制。
  • @TomTom 但您关于可能滥用此数据类型的观点是支持完全避免使用它的论据。
  • @Nathan 不。我要指出的是,作为从不使用它的理由而给出的论点基本上是不称职的开发人员,所以这个论点是虚假的。
  • @TomTom 遗憾的是,有许多不称职的开发人员(以及许多很棒的开发人员),对我来说,除非我能保证将来如何查询这些数据并且没有人会去要犯这里描述的那种错误,最好谨慎行事并使用十进制类型。也就是说,在阅读了所有这些答案后,我可以看到有一些特定的用例,其中金钱将是最佳使用类型,除非有一个非常好的用例,否则我不会使用它(例如,列只会永远通过 SUM 聚合,批量加载大量财务数据)。
  • @TomTom 这不仅仅是精度限制。内部表示也可能导致问题。金钱是抓住无知开发者的便利类型,而不是开发人员滥用的好类型。增值税的例子,不管计算它的规则如何,当他们将具体应用于一般时,显然应该吓跑非无知的开发人员。
【解决方案5】:

我知道 WayneM 曾表示他知道金钱是特定于 SQL Server 的。然而,他问是否有任何理由使用十进制的钱,反之亦然,我认为仍然应该说明一个明显的原因,那就是使用十进制意味着如果你不得不改变你的 DBMS,那就少了一件担心的事- 这可能会发生。

让您的系统尽可能灵活!

【讨论】:

  • 可能,但理论上您可以在另一个 DBMS 中声明一个名为 Money 的域(支持声明域)。
  • 作为目前正在将 SQL Server 数据库转换为 Amazon Redshift 的人,我可以保证这是一个真正的问题。尽量避免为特定数据库平台定制的数据类型,除非有非常充分的商业理由使用它们。
  • @Nathn 与 PostgreSQL 或 SQL Server 相比,Redshift 的类型系统较弱,例如没有 date 类型,我会说你会总是有这样的问题。你应该说“当数据库已经提供了更好、更符合标准的类型并警告不推荐使用的类型时,不要使用不推荐使用的类型”。
  • @PanagiotisKanavos - 金钱没有被弃用
  • @MartinSmith 看到这一点感到震惊,因为它实际上无法像 2017 年的 BTC 那样处理货币价值。或者区分英镑和欧元的金额——即使它们走向平价:P。无论如何,迁移到 Redshift 会带来很多的痛苦
【解决方案6】:

如果你不知道自己在做什么,一切都是危险的

即使是高精度的十进制类型也无法挽救这一天:

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

1.000000


money 类型是整数

smallmoneydecimal(10,4) 的文本表示可能看起来很相似,但这并不意味着它们可以互换。当您看到存储为 varchar(10) 的日期时,您会畏缩吗?这是同一件事。

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

关于 decimalmoney,选择适合您需求的任何内容。 money 类型之所以存在,是因为将记帐值存储为单位的 1/10000 的整数倍是很常见的。此外,如果您要处理的不是简单的加减法,而是实际的金钱和计算,您不应该在数据库级别这样做!使用支持 Banker's Rounding (IEEE 754) 的库在应用程序级别进行

【讨论】:

  • 你能解释一下这个例子中发生了什么吗?
  • 比例溢出。在小范围内,numeric(a,b) * numeric(c,d) 产生 numeric(a-b+c-d+1, max(b,d))。但是,如果 (a+b+c+d)>38,SQL 会限制比例,从小数侧抢夺精度以填充整数侧,从而导致舍入误差。
  • 所有数值计算都容易因缩放而损失精度:改为compute select 1000000 * @ num1 * @ num2
  • Anon 的示例是特定于版本和数据库的。但要小心一点是有效的。在 sqlanywhere 1.1 版中,该示例确实正确给出了 0.600000。 (我知道我们在这里谈论的是 ms sql)。关于其他数据库中缺少货币类型的另一点,有一些方法将小数或数字称为货币,例如创建域。
  • 我认为这是最有启发性的答案,因为您不仅解释了传播的错误,还解释了幕后真正发生的事情,以及为什么 MONEY 首先存在。我不确定为什么要花 4 年时间才能得到这个答案,但也许是因为过于关注计算“问题”,而不是金钱与小数的原因和方式。
【解决方案7】:

嗯,我喜欢MONEY!它比DECIMAL 便宜一个字节,并且计算执行得更快,因为(在幕后)加法和减法运算本质上是整数运算。 @SQLMenace 的示例(对不知情的人来说是一个很好的警告)同样适用于INTegers,其结果将为零。但这不是不使用整数的理由——在适当的时候

因此,当您处理的是MONEY 并根据其遵循的数学规则(与INTeger 相同)使用它时,使用MONEY 是完全“安全”且合适的。

如果 SQL Server 将MONEY 的除法和乘法提升为DECIMALs(或FLOATs?)会更好吗?但他们没有选择这样做;他们也没有选择在划分时将INTegers 提升为FLOATs。

MONEY 没有精度问题; DECIMALs 在计算过程中使用更大的中间类型只是使用该类型的一个“特性”(我实际上不确定该“特性”扩展了多远)。

要回答具体问题,“令人信服的理由”?好吧,如果你想在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 被截断——与整数相同。)

【讨论】:

  • MONEYlarge DECIMAL 少一个字节,精度高达 19 位。然而,大多数现实世界的货币计算(高达 999 万美元)可以放入 DECIMAL(9, 2),它只需要五个字节。您可以节省大小,减少对舍入错误的担心,并且使您的代码更具可移植性。
  • 虽然@JonofAllTrades 是正确的,但您也可以通过简单地使用包含便士或美分的整数来恢复整数性能,并保存编码小数点位置的额外字节,该字节必须是小数相加时检查。
  • “因此,当您处理的是 MONEY 并根据其遵循的数学规则使用它时,使用 MONEY 是完全“安全”且合适的”-> 但是,另请参阅 Anon 的回答:“如果你不知道自己在做什么,一切都是危险的”。您永远无法预测人们最终将如何查询您正在创建的系统,最好尽可能避免滥用的可能性。恕我直言,存储方面的微小收益不值得。
  • 尝试存储一个比特币值。它有8位小数
【解决方案8】:

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

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

十进制结果 > 1.000000

MoneyResult > 0.9999

FloatResult > 1

只需测试它并做出决定。

【讨论】:

  • 我们非常希望听到(即阅读)您的结论。
  • @PeterMortensen 我认为如果我想在 Money 和 Decimal 类型之间具有完整性和准确性,我的决定应该是 Decimal 之一。
  • 考虑使用上述实际结果更新您的答案。那么你的结论对任何阅读的人来说都是显而易见的:-)
  • @Ageax 将结果添加到答案中。
【解决方案9】:

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

小数在内部以 10 为基数表示,因此小数点的位置也基于以 10 为基数的数字。这使得它的小数部分准确地代表了它的价值,就像金钱一样。不同的是,decimal 的中间值可以保持高达 38 位的精度。

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

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

从我的 POV 来看,我希望发生在数字上的事情能够直接发生,而不必考虑太多。如果所有计算都将转换为十进制,那么对我来说,我只想使用十进制。我会保存钱字段用于显示目的。

在尺寸方面,我没有看到足以改变主意的差异。 Money 占用 4 - 8 个字节,而十进制可以是 5、9、13 和 17。这 9 个字节可以覆盖 8 个字节的 money 可以覆盖的整个范围。索引方式(比较和搜索应该是可比较的)。

【讨论】:

  • 感谢您的支持。我正在看这个,虽然我明白了我想说的话,但我也可以看到它非常令人困惑。也许我稍后会用一些位与十进制的例子来重写它。
【解决方案10】:

作为对其他答案的总体主旨的反驳。在SQLCAT's Guide to Relational Engine

中查看金钱的诸多好处...数据类型!

我要特别指出以下几点

在处理客户实施时,我们发现了一些有趣的东西 有关货币数据类型的性能数字。例如,当 Analysis Services 被设置为货币数据类型(从双精度)到 与 SQL Server 货币数据类型匹配,有 13% 的改进 处理速度(行/秒)。在 SQL 中获得更快的性能 服务器集成服务 (SSIS) 在 30 岁以下加载 1.18 TB 分钟,如 SSIS 2008 中所述 - 世界纪录的 ETL 性能,它是 观察到更改大小为 5 的四个小数 (9,2) 列 TPC-H LINEITEM 表中的字节到钱(8 字节)改进的批量 插入速度提高 20% ... 性能提升的原因是因为 SQL Server 的表格数据流 (TDS) 协议,它 具有以紧凑的二进制形式传输数据并尽可能靠近内部存储的关键设计原则 SQL Server 的格式。根据经验,这是在 SSIS 2008 中观察到的 - 世界纪录 ETL 性能测试使用 核率;当数据类型从十进制转换为货币时,协议显着下降。这使得 尽可能高效地传输数据。复杂的数据类型需要额外的解析和 CPU 周期来处理,而不是 固定宽度类型。

所以问题的答案是“视情况而定”。您需要更加小心地处理某些算术运算以保持精度,但您可能会发现性能方面的考虑值得这样做。

【讨论】:

  • 那么你将如何存储比特币
  • @PanagiotisKanavos - 我不知道。我从来没有研究过比特币,对它几乎一无所知!
  • 链接无效。这是 PDF 的链接download.microsoft.com/download/0/F/B/…
【解决方案11】:

我想对 MONEY 与 NUMERICAL 给出不同的看法,主要基于我自己的专业知识和经验...我的观点是 MONEY,因为我已经使用它很长时间了,但从未真正使用过 NUMERICAL很多...

金钱专家:

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

    MONEY 只要用于它本来的用途(作为金钱),它就会更快。多快?我对 100 万条数据的简单 SUM 测试表明,MONEY 为 275 毫秒,NUMERIC 为 517 毫秒……这几乎快了一倍……为什么要进行 SUM 测试?看下一个专业

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

金钱骗局:

  • 精度有限。 MONEY 只有四位数(在逗号之后)的精度,所以在进行除法等操作之前必须对其进行转换......但又一次money 不需要那么精确,并且是用来作为货币使用的,不只是一个数字...

但是...很大,但是这里甚至是您的应用程序都涉及真实货币,但不要在很多 SUM 操作中使用它,例如在会计中。如果您使用大量除法和乘法,那么您不应该使用 MONEY...

【讨论】:

  • 如果你要说钱比十进制快,你需要告诉我们什么 rdbms,在什么硬件上,运行什么操作系统,用什么特定数据运行什么特定查询'再谈。此外,如果它不完全准确,我不会用它进行任何财务操作,因为税务人员会很不高兴收回坏数字。例如,如果您处理美元,您会关心千分之一。如果钱不这样做,它就无法使用。
  • @HaakonLøtveit 整个问答线程都是关于 SQL Server 数据类型“金钱”的,所以我认为他们不需要在答案中指定这一点。在某些情况下(例如加载数据),金钱的使用速度可能比十进制更快,有关更多详细信息,请参阅 Martin Smith 的答案。我同意这个答案的大部分内容,因为某些用例可能使 Money 成为比小数更有效的选择,但是除非有非常令人信服的使用案例,否则我认为应该避免使用它。
  • 比特币有 8 位小数。您甚至不能将其存储在 money 列中
  • @HaakonLøtveit .. 好吧,我想说的是,在架构级别上,原生类型总是在性能方面胜过非原生类型,而原生类型取决于架构,例如 CPU是整数类型,在 FPU 上是浮点数,在 GPU 上是浮点数数组等等..
【解决方案12】:

之前的所有帖子都带来了有效的观点,但有些没有准确回答问题。

问题是:既然我们已经知道金钱是一种不太精确的数据类型,而且如果用于复杂的计算会导致错误,为什么有人会更喜欢它?

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2019-10-09
    • 2021-06-06
    • 1970-01-01
    • 2014-04-16
    • 1970-01-01
    • 1970-01-01
    • 2011-05-17
    相关资源
    最近更新 更多