ChatGPT解决这个技术问题 Extra ChatGPT

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

我很好奇 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。

P
Peter Mortensen

你永远不应该用钱。不精确,纯属垃圾;始终使用十进制/数字。

运行这个看看我的意思:

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

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

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

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


一张一美元的钞票可以给你多少 1 美分硬币?对此的答案需要钱/钱。
@Learning 在这种情况下,结果不是钱,而是浮动。 (基本尺寸分析。)
@Learning:你问数据库一美元有多少美分?无论如何,这将返回正确的结果。他的问题是钱/钱只精确到四位数(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))(简化)确实有意义。
C
Community

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

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

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 类型是整数

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

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

使您的系统尽可能灵活!


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

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

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

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

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

要回答具体问题,“令人信服的理由”?好吧,如果您希望在 x 可以是 DECIMALMONEYSUM(x) 中获得绝对的最大性能,那么 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 位小数
a
animuson

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

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

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

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


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

作为对其他答案的总体主旨的反驳。请参阅 金钱的诸多好处...数据类型! SQLCAT's Guide to Relational Engine

具体来说,我会指出以下几点

在处理客户实现时,我们发现了一些关于货币数据类型的有趣的性能数据。例如,当 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的Tabular Data Stream(TDS)协议,该协议具有关键设计原则以紧凑的二进制形式传输数据,并尽可能接近 SQL Server 的内部存储格式。根据经验,这在 SSIS 2008 中观察到 - 使用 Kernrate 的世界纪录 ETL 性能测试;当数据类型从十进制转换为货币时,协议显着下降。这使得数据传输尽可能高效。与固定宽度类型相比,复杂数据类型需要额外的解析和 CPU 周期来处理。

所以这个问题的答案是“这取决于”。您需要对某些算术运算更加小心以保持精度,但您可能会发现性能方面的考虑让这很值得。


那你将如何存储比特币?
@PanagiotisKanavos - 我不知道。我从来没有研究过比特币,对它几乎一无所知!
链接不起作用。这是 PDF 的链接download.microsoft.com/download/0/F/B/…
P
Peter Mortensen

我想对 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 只有四位数(在逗号之后)的精度,所以在进行除法等运算之前必须对其进行转换……但话又说回来,金钱不需要那么精确,它的意思是用作金钱,而不仅仅是一个号码...

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


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

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

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

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

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

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

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

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


G
Gerard ONeill

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

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

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

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

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

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


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

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

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

金钱结果 > 0.9999

浮点结果 > 1

只需测试它并做出决定。


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

我刚刚看到这篇博文: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 中所说,任何说“这是垃圾”的人都不知道他在说什么。