PostgreSQL:哪种数据类型应该用于货币?

day*_*mer 110 sql postgresql database-design

好像Money类型如所描述的不鼓励这里

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

Erw*_*ter 99

你的消息来源绝不是正式的.它可以追溯到2011年,我甚至不承认作者.如果货币类型"气馁",PostgreSQL会在手册中这么说 - 但事实并非如此.

对于更官方的来源,请阅读pgsql-general中的这个帖子(从本周开始!),核心开发人员的声明,包括D'Arcy JM Cain(钱类型的原始作者)和Tom Lane:

基本上,money它有(有限)用途.优势numeric在于性能.

decimal只是numericPostgres 的别名.

有关最新版本改进的相关答案(和评论!):

就个人而言,我喜欢将货币存储为integer代表Cents.这比任何其他提到的选项更有效.

  • @a_horse_with_no_name:您的链接指向2007年的一个主题,当时8.2是当前版本,并且`money`类型实际上已被弃用.问题已修复,类型已在以后的版本中添加回来.就个人而言,我喜欢将货币存储为代表Cents的"整数". (10认同)
  • 邮件列表上有几个讨论确实给人的印象是至少不建议使用金钱类型,例如:http://postgresql.nabble.com/Money-type-todos-td1964190.html#a1964192 plus to公平地说:版本8.2的手册**确实**称之为弃用:http://www.postgresql.org/docs/8.2/static/datatype-money.html (4认同)
  • @Kevin:在许多用例中,该列仅代表实际货币价值 - 其中最小可能单位是美分。如果您需要分数美分,则无法使用“整数”,请查看“数字”。或者您移动 5 位小数,而不是仅移动 2 位,代表毫分 - 或您需要的实际比例。任何涵盖您的用例的内容。 (3认同)
  • Erwin,仅从数据库角度来看,您的想法可能是正确的。然而,如果你将 Postgresql + Java 结合起来,那就一点都不好(根据我的经验)。阅读您的评论,我在大多数货币字段中使用了 MONEY,现在我得到了这个 Java 异常:“**发生 SQLException:org.postgresql.util.PSQLException:双精度类型的错误值:2,500.00**”。我用谷歌搜索并没有找到好的解决方案,所以我现在正在将它们全部更改为数字或十进制这一无聊的任务! (2认同)
  • 如果您存储为整数美分,您如何处理小数美分的价格,例如汽油价格? (2认同)
  • @PirateApp:是的,我个人的最爱.你可能错过了我答案的最后一句话,只是这样说. (2认同)

Chr*_*loe 77

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

据我所知,资金类型只是因为历史原因而留下的.

  • 这不是你避免浮点的原因.即使数字除以任何不分为10的幂,但无论使用何种精度,都会产生舍入误差.(无论如何,2的精度是一个坏主意...检查文档.) (6认同)
  • 如果要支持任意货币,请不要使小数位数等于2(以PostgreSQL术语来说,精度是所有数字的数字,例如121.121中的数字等于6)。有货币,例如巴林第纳尔,其中1000个子单位等于一个单位,并且有些货币根本没有子单位。 (5认同)
  • `numeric(3,2)` 将能够存储最大 `9.99` `3-2 = 1` (2认同)
  • 不要这样做!除非您打算在将任何值加载到其他语言之前再乘以100,然后再对整数进行数学运算,否则最终将得到错误的结果。用美分(您要处理的最小货币单位)存储事物,并为自己节省一些麻烦。在许多情况下,答案很差。 (2认同)
  • 补充一下@NikolayArhipov 所说的内容,以此为例:1 伊朗里亚尔等于 0.000030 美元。如果您使用的小数位少于 5 位,则 1 IRR 在转换后将四舍五入为 0 美元。我知道我们在这里分里亚尔,但我认为在处理金钱时永远不会太安全。我会使用 6 个小数位。 (2认同)

Boh*_*ian 59

你的选择是:

  1. integer:以美分存储金额.这是EFTPOS交易使用的.
  2. decimal(12,2):存储金额正好两位小数.这是大多数总帐软件使用的.
  3. float:糟糕的主意 - 准确性不足.这是天真的开发人员使用的.

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

请注意,如果您将作为计算结果的多个事务(例如涉及汇率)聚合为具有业务含义的单个值,则精度应该更高,以提供准确的宏值; 考虑使用类似的东西decimal(18, 8),总和是准确的,并且各个值可以四舍五入到精确度以便显示.

  • 如果您正在进行任何类型的反向税收计算或外汇交易,您至少需要4个小数位,否则您将丢失数据.所以`numeric(15,4)`或`numeric(15,6)`是个好主意. (8认同)
  • 第四个选项-使用字符串并在宿主语言中使用等效的非有损十进制类型。 (3认同)
  • 怎么样的缩放整数,如果它存储为10000045与1000倍缩放因子肯定存储10000.045肯定会受伤? (2认同)

小智 20

我把所有的货币领域都保留为:

numeric(15,6)

拥有那么多小数位似乎太过分了,但是如果你有多少机会处理多种货币,那么转换就需要那么精确.无论我呈现的是什么用户,我总是以美元存储.这样,考虑到当天的转换率,我可以随时转换为任何其他货币.

如果你从不做任何东西,只有一种货币,最糟糕的是你浪费了一些空间来存储一些零.

  • 由于缺少截断,因此存在错误结果的风险。如果非零值无意泄漏到剩余的小数位(例如,包含0.333333美元的价格字段),那么您可能会遇到以下情况:系统显示某人购买了3件商品,每件价格$ 0.33,总计为$ 1.00,而不是$ 0.99。 (4认同)
  • 固定点并在适当的地方截断。一旦达到“可存储”的货币价值,例如提供给客户的价格,它就应该采用适当的度量标准,在大多数情况下,在标准零售环境中以整分计。如果您有不同的业务需求(例如每单位大批量商品的价格),可能会有不同的准确度设置,但您必须将*表示与存储* 一起对待 - 如果您用 x 小数显示货币数字(或反之亦然)反之,例如整千),那么您还必须以*该*准确度存储它,不少于但也不能更多。 (3认同)
  • Peteris,这是一个糟糕的情况。如果价格字段的值为 0.333333,而该字段的值应为 0.33,则这是一个应用程序错误。如前所述,存储更高的准确性对于货币转换非常有用。 (2认同)

Max*_*ges 13

使用BigInt存储的货币为代表的最小货币单位的货币价值正整数(如100美分存储$ 1.00 100存储¥100(日元,零小数的货币),这是条纹做什么-一个全球电子商务最重要的金融服务公司。

  • @NarutoSempai 您提供的链接参考了发票中的定价;不是 Stripe 在其 API 中表示货币值的方式。 (5认同)
  • _这就是 Stripe 所做的_您有此声明的来源吗? (4认同)

Man*_*ham 8

使用存储为的64位整数 bigint

我建议使用微型美元(或类似的主要货币)。微型意味着百万分之一,所以1个微型美元= 0.000001美元。

  • 足够的精度来处理零点几分。
  • 适用于非常小的单位价格(例如广告展示次数或API费用)。
  • 易于使用并且与每种语言兼容。
  • 与字符串或数字相比,用于存储的数据大小更小。
  • 通过计算易于保持准确性,并在最终输出中应用舍入。

  • 这是最正确的答案,任何合理的软件都会处理现有的最小货币单位。对整数进行所有数学运算意味着您不必处理任何特定于语言的浮点修饰。 (5认同)
  • 啊,对了,我错过了关于存储的部分。谢谢!关于“使用简单且与每种语言兼容”,不幸的是 JavaScript 支持高达 9007199254740991 的整数,该整数比 `bigint` 的最大值小 1000 倍以上。有https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt,但它的支持有限(目前)和警告(例如,你不能将它乘以浮点数进行货币转换时很容易)。鉴于您可以使用微美元存储在 JS 整数中的最大值为 90 亿美元,这对于大多数情况来说可能仍然不错。 (3认同)
  • 为什么在另一个答案中建议使用“numeric(15,6)”? (2认同)
  • 我挖得更深一点。如果您想支持加密货币,那么“bigint”可能还不够。比特币提供 8 个小数点精度,门罗币为 12 个小数点,以太坊为 18 个小数点。事实上,[以太坊 VM 内部使用 `uint256`](https://github.com/ethereum/wiki/wiki/Design-Rationale),这将是 4 个 `bigint `s (未签名,因此您可能只使用 `numeric(72,18)`)。话虽如此,对于某些应用程序,可能没有必要存储如此精确的值(例如,如果您不对它们进行任何数学运算),因此我现在将坚持使用“bigint”。最坏的情况就是数据库迁移的目的:) (2认同)