【问题标题】:PostgreSQL: Which Datatype should be used for Currency?PostgreSQL:货币应该使用哪种数据类型?
【发布时间】:2013-03-21 12:42:16
【问题描述】:

似乎不鼓励使用Money 类型,如here 所述

我的应用程序需要存储货币,我应该使用哪种数据类型?数字、金钱还是浮点数?

【问题讨论】:

  • 如果你已经阅读了整篇文章,那么数字是最好的选择。
  • 对于使用多种货币并关心在金额之外存储货币代码的任何人,您可能希望查看Currency modeling in database (SO) 和ISO 4217 (Wikipedia)。简短的回答是您需要两列。

标签: sql postgresql database-design


【解决方案1】:

您的来源绝不是官方的。它可以追溯到 2011 年,我什至不认识作者。如果货币类型被官方“劝阻”,PostgreSQL 会在手册中这样说 - which it doesn't

如需更官方的来源,请阅读 this thread in pgsql-general (from just this week!),其中包含核心开发人员的声明,包括 D'Arcy J.M. Cain(货币类型的原作者)和 Tom Lane:

有关最近版本改进的相关答案(和 cmets!):

基本上,money 有其(非常有限的)用途。 Postgres Wiki 建议在很大程度上避免它,除了那些狭义的情况。与numeric 相比的优势在于性能

decimal 只是 Postgres 中numeric 的别名,广泛用于货币数据,是一种“任意精度”类型。 The manual:

numeric 类型可以存储非常多位数的数字。 特别推荐用于存储货币金额和其他 需要精确的数量。

就个人而言,我喜欢将货币存储为integer,如果小数美分永远不会出现(基本上在货币有意义的地方),则代表美分。这比上述任何其他选项都更有效。

【讨论】:

  • 在邮件列表中有几个讨论确实给人的印象是至少不推荐货币类型,例如:这里:postgresql.nabble.com/Money-type-todos-td1964190.html#a1964192 加上公平地说:8.2 版的手册 确实称它已弃用:postgresql.org/docs/8.2/static/datatype-money.html
  • @a_horse_with_no_name:您的链接指向 2007 年的一个线程,当时 8.2 也是当前版本,money 类型实际上已被弃用。问题已得到修复,并且该类型已在以后的版本中重新添加。我个人喜欢将货币存储为integer 代表美分。
  • Erwin,仅从数据库的角度来看,您的想法可能是正确的。但是,如果将 Postgresql + Java 结合起来,它就一点都不好(根据我的经验)。阅读您的评论后,我在大多数货币字段中都使用了 MONEY,现在我得到了这个 Java 异常:“发生了 SQLException:org.postgresql.util.PSQLException:double 类型的值错误:2,500.00”。我用谷歌搜索并没有找到好的解决方案,所以我现在正在将它们全部更改为 NUMERIC 或 DECIMAL 的无聊任务!!!
  • 如果您存储为整数美分,您如何处理带有小数美分的价格,例如汽油价格?
  • @PirateApp:是的,我个人最喜欢的。你可能错过了我回答的最后一句话,就是这么说的。
【解决方案2】:

Numeric 强制为 2 个单位精度。永远不要使用浮点数或类似浮点数的数据类型来表示货币,因为如果你这样做了,当财务报告的底线数字不正确 + 或 - 几美元时,人们会不高兴。

据我所知,货币类型只是出于历史原因而保留。

以此为例:1 伊朗里亚尔等于 0.000030 美元。如果您使用少于 5 个小数位,则 1 IRR 将在转换后四舍五入为 0 美元。我知道我们在这里拆分里亚尔,但我认为在处理金钱时你永远不会太安全。

【讨论】:

  • 这不是你避免使用浮点数的原因。如果你除以任何不除以十的幂,即使数字也会有舍入误差,无论你使用什么精度。 (无论如何,精度为 2 是个坏主意……查看文档。)
  • 如果你想支持任意货币,永远不要让比例等于 2(在 postgresql 术语中,精度是所有数字的数字,例如在 121.121 中它等于 6)。有货币,比如巴林第纳尔,1000 个子单位等于一个单位,也有根本没有子单位的货币。
  • numeric(3,2) 将能够存储 max 9.99 3-2 = 1
  • 不要这样做!除非您计划在将任何值加载到其他语言之前乘以 100,然后用整数进行数学运算,否则您最终会得到错误的结果。以美分(您正在处理的最小货币单位)存储东西并为自己省去一些麻烦。在许多情况下,答案很糟糕。
  • 补充@NikolayArhipov 所说的内容,举个例子:1 伊朗里亚尔等于 0.000030 美元。如果您使用少于 5 个小数位,则 1 IRR 将在转换后四舍五入为 0 美元。我知道我们在这里分割里亚尔,但我认为在处理金钱时你永远不会太安全。我会使用 6 个小数位。
【解决方案3】:

您的选择是:

  1. bigint :以美分存储金额。这就是 EFTPOS 交易使用的方式。
  2. decimal(12,2) :将金额存储在小数点后两位。这是大多数总账软件使用的。
  3. float:糟糕的想法-准确性不足。这是天真的开发人员使用的。

选项 2 是最常见且最容易使用的。将精度(在我的示例中为 12,表示总共 12 位)尽可能大或小,以最适合您。

请注意,如果您将计算结果(例如涉及汇率)的多个交易汇总为具有商业意义的单个值,则精度应该更高以提供准确的宏观值;考虑使用 decimal(18, 8) 之类的东西,这样总和是准确的,并且各个值可以四舍五入到分精度以进行显示。

【讨论】:

  • 如果您使用任何类型的反向税收计算或外汇,您至少需要 4 位小数,否则您将丢失数据。所以numeric(15,4)numeric(15,6) 是个好主意。
  • 还有第四个选项——即使用字符串并在宿主语言中使用等效的无损十进制类型。
  • 一个缩放的整数怎么样,如果存储为 10000045 并具有 1000 倍的缩放因子,存储 10000.045 肯定不会有什么坏处?
【解决方案4】:

使用存储为 bigint 的 64 位整数

我建议使用微型美元(或类似的主要货币)。微表示百万分之一,因此 1 微美元 = 0.000001 美元。

  • 使用简单,兼容各种语言。
  • 足够精确,可以处理一分钱。
  • 适用于非常小的单位定价(例如广告展示次数或 API 费用)。
  • 存储的数据量比字符串或数字小。
  • 通过计算轻松保持准确性并在最终输出中应用舍入。

【讨论】:

  • 这是最正确的答案,任何合理的软件都处理手头最小的货币单位。对整数进行所有数学运算意味着您不必处理任何特定于语言的浮点修饰。
  • 为什么在另一个答案中建议numeric(15,6)
  • @JuliuszGonera 出于答案中列出的原因。整数更小并且到处都支持,并且避免了所有的数学截断问题。它基本上使用数字,但移动小数,所以你有一个更兼容的整数。
  • 啊,对了,我错过了关于存储的部分。谢谢!关于“易于使用并与每种语言兼容”不幸的是,JavaScript 支持高达 9007199254740991 的整数,这比 bigint 的最大值小 1000 倍以上。有developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…,但它提供有限的支持(目前)和警告(例如,在进行货币转换时,你不能轻易地将它乘以浮点数)。考虑到您可以使用微美元存储在 JS 整数中的最大值为 90 亿美元,这对于大多数情况来说可能仍然不错。
  • 我挖得更深了。如果您想支持加密货币,那么bigint 也可能还不够。比特币提供 8 个小数点的精度,门罗币 12,以太坊 18。实际上 Ethereum VM internally uses uint256 将是 4 bigints(未签名,所以你可能只使用 numeric(72,18))。话虽如此,对于某些应用程序,可能没有必要存储如此精确的值(例如,如果您不对它们进行任何数学运算),所以我现在将坚持使用 bigint。最坏的情况就是数据库迁移的用途:)
【解决方案5】:

我将所有货币字段保留为:

numeric(15,6)

有这么多小数位似乎有点过分,但如果您有可能需要处理多种货币,那么您将需要如此高的精度来进行转换。无论我向用户展示什么,我总是以美元存储。考虑到当天的兑换率,这样我就可以轻松地兑换成任何其他货币。

如果你只做一种货币,最糟糕的是你浪费了一点空间来存储一些零。

【讨论】:

  • 由于缺少截断,这有产生错误结果的风险。如果非零值无意中泄漏到剩余的小数位,例如,包含 0.333333 美元的价格字段,那么您可能会遇到这样的情况,即系统显示某人以每件 0.33 美元购买 3 件商品的结果,总计高达 1.00 美元而不是 0.99 美元。
  • Perteris,那么您有什么建议呢?无论您在此舍入时的精度如何,都可能成为问题。我只是没有找到更好的方法,即使这个方法并不理想。
  • 定点并在适当的地方截断。一旦您达到“可存储”的货币价值,例如提供给客户的价格应该采用适当的度量标准,在大多数情况下,标准零售环境中的度量标准是整美分。如果您有不同的业务需求(例如每件大批量商品的价格),则可能会有不同的准确度设置,但您必须将 展示与存储一并处理 - 如果您将货币编号与x 小数(反之亦然,例如以千为单位),那么您还必须以 那个 的准确度存储它,不能少也不能多。
  • 适用于许多可能有效的零售相关网站。我参与的主要项目可能有一方需要以一种货币查看相同的成本,而客户需要以另一种货币查看相同的成本,而供应商则需要第三次。
  • Peteris,这是一个废话,废话的场景。如果您允许价格字段的值是 0.333333,而它应该是 0.33,这是一个应用程序错误。如前所述,存储更高的准确性对于货币转换非常有用。
【解决方案6】:

使用BigInt 将货币存储为一个正整数,表示以最小货币单位表示的货币价值(例如,100 美分存储 1.00 美元或 100 存储 ¥100(日元,零十进制货币)。这是Stripe 是做什么的——全球电子商务最重要的金融服务公司之一。

来源:参见“零十进制货币”https://stripe.com/docs/currencies

【讨论】:

  • Stripe 就是这样做的你有这个声明的来源吗?
  • @IvayloToskov "零十进制货币 所有 API 请求都希望以货币的最小单位提供金额。例如,要收取 10 美元,请提供 1000(即 1000 美分)的金额值。对于零十进制货币,仍以整数形式提供金额,但不乘以 100。例如,要收取 500 日元,请提供金额值 500。消息来源:stripe.com/docs/currencies
  • @NarutoSempai 您提供的链接参考发票中的定价;而不是 Stripe 在其 API 中如何表示货币值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-31
  • 1970-01-01
  • 2012-11-07
  • 1970-01-01
  • 2011-07-02
  • 1970-01-01
  • 2011-06-06
相关资源
最近更新 更多