PostgreSQL: какой тип данных следует использовать для валюты?

128

Похоже, Moneyтип не рекомендуется, как описано здесь

Мое приложение должно хранить валюту, какой тип данных я должен использовать? Числовой, денежный или плавающий?

мечтатель
источник
7
Если вы прочитали всю ветку, Numeric - ваш лучший выбор.
razpeitia
Для тех, кто работает с несколькими валютами и заботится о хранении кодов валют в дополнение к суммам, вы можете увидеть моделирование валют в базе данных (SO) и ISO 4217 (Википедия). Короткий ответ: вам понадобятся две колонки.
Fabien Snauwaert

Ответы:

90

Числовое значение с принудительной точностью до 2 единиц. Никогда не используйте float или float как тип данных для представления валюты, потому что, если вы это сделаете, люди будут недовольны, если итоговая цифра финансового отчета окажется неверной на + или - несколько долларов.

Тип денег просто оставлен по историческим причинам, насколько я могу судить.

Крис Фармило
источник
9
Не поэтому вы избегаете операций с плавающей запятой. Даже Numeric будет иметь ошибки округления, если вы разделите на все, что не делится на степень десяти, независимо от того, какую точность вы используете. (Точность 2 - в любом случае плохая идея ... проверьте документацию.)
Дорадус
6
Если вы хотите поддерживать произвольные валюты, никогда не делайте шкалу равной 2 (в терминах postgresql точность - это число всех цифр, например, в 121.121 она равна 6). Существуют валюты, такие как бахрейнский динар, где 1000 частей равняются одной единице, а есть валюты, в которых частей нет вообще.
Николай Архипов
@NikolayArhipov хороший замечание, так что максимум на самом делеscale - precision
Конрад
1
numeric(3,2)сможет хранить макс9.99 3-2 = 1
Конрад
5
Не делай этого! Если вы не планируете умножать на 100 перед загрузкой каких-либо значений на другие языки, а затем выполнять математические вычисления с целыми числами, вы получите неверные результаты. Храните вещи в центах (наименьшая денежная единица, с которой вы имеете дело) и избавьте себя от хлопот. Во многих случаях очень плохой ответ.
Avamander 02
111

Ваш источник никоим образом не является официальным. Он датируется 2011 годом, и я даже не узнаю авторов. Если бы тип денег был официально «обескуражен», PostgreSQL сказал бы об этом в руководстве, но это не так .

Чтобы получить более официальный источник , прочтите эту ветку в pgsql-general (только на этой неделе!) , С заявлениями основных разработчиков, включая Д'Арси Дж. М. Кейна (оригинальный автор денежного типа) и Тома Лейна:

Связанный ответ (и комментарии!) Об улучшениях в последних выпусках:

В основном, moneyимеет (очень ограниченное) применение. Postgres Wiki предлагает во многом избежать, за исключением тех узко определенных случаев. Преимущество перед ним numeric- производительность .

decimal- это просто псевдоним для numericPostgres, который широко используется для денежных данных, являясь типом «произвольной точности». Руководство :

Тип numericможет хранить числа с очень большим количеством цифр. Он особенно рекомендуется для хранения денежных сумм и других величин, где требуется точность.

Лично мне нравится хранить валюту как integerпредставляющую центы, если дробные центы никогда не встречаются (в основном, когда деньги имеют смысл). Это более эффективно, чем любой другой из упомянутых вариантов.

Эрвин Брандштеттер
источник
4
В списках рассылки есть несколько обсуждений, которые создают впечатление, что тип денег, по крайней мере, не рекомендуется, например: здесь: 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
11
@a_horse_with_no_name: ваша ссылка ведет к потоку из 2007 года, когда 8.2 была текущей версией, а этот moneyтип фактически устарел. Проблемы были исправлены, и тип был добавлен обратно в более поздних версиях. Лично мне нравится хранить валюту в виде integerцентов.
Эрвин Брандштеттер,
1
Эрвин, возможно, ты правильно думаешь только с точки зрения базы данных. Однако, если вы объедините Postgresql + Java, это совсем не хорошо (по моему опыту). Читая ваш комментарий, я использовал ДЕНЬГИ для большинства полей валюты, и теперь я получаю это исключение Java: « Произошло SQLException: org.postgresql.util.PSQLException: недопустимое значение для типа double: 2,500.00 ». Я погуглил и не нашел хорошего решения, поэтому сейчас я занимаюсь скучной задачей изменить их все на NUMERIC или DECIMAL !!!
MD
1
@MD: Мне жаль это слышать, но я, очевидно, не говорил от Java (чего я не могу). Сообщение об ошибке нечетное. «двойной»? И разделитель тысяч тоже может быть проблемой. Возможно, вы захотите задать новый вопрос по этому поводу.
Эрвин Брандштеттер,
2
@PirateApp: Да, мой личный фаворит. Возможно, вы пропустили последнее предложение моего ответа, сказав именно это.
Эрвин Брандштеттер
69

Ваш выбор:

  1. bigint: хранить сумму в центах. Это то, что используют транзакции EFTPOS.
  2. decimal(12,2): сохранить сумму ровно с двумя десятичными знаками. Это то, что использует большинство программ для главной бухгалтерской книги.
  3. float: ужасная идея - неадекватная точность. Это то, что используют наивные разработчики.

Вариант 2 - самый распространенный и самый простой в работе. Сделайте точность (12 в моем примере, что означает всего 12 цифр) настолько большой или маленькой, насколько вам удобнее.

Обратите внимание: если вы объединяете несколько транзакций, которые были результатом расчета (например, с использованием обменного курса), в одно значение, имеющее бизнес-значение, точность должна быть выше, чтобы обеспечить точное значение макроса; рассмотрите возможность использования чего-то подобного, decimal(18, 8)чтобы сумма была точной, а отдельные значения можно было округлить с точностью до центов для отображения.

Богемный
источник
10
Если вы работаете с любым видом обратного расчета налога или обмена иностранной валюты, вам нужно как минимум 4 десятичных знака, иначе вы потеряете данные. Так numeric(15,4)или numeric(15,6)это хорошая идея.
Петрус Терон
3
Есть четвертый вариант - использовать String и использовать эквивалентный десятичный тип без потерь на языке хоста.
ioquatix
2
как насчет масштабированного целого числа, конечно, сохранение 10000.045 не повредит, если оно будет сохранено как 10000045 с коэффициентом масштабирования 1000x?
PirateApp
26

Я сохраняю все свои денежные поля как:

numeric(15,6)

Кажется чрезмерным иметь такое количество десятичных знаков, но если есть хоть малейшая вероятность, что вам придется иметь дело с несколькими валютами, вам понадобится такая высокая точность для преобразования. Независимо от того, что я представляю пользователю, я всегда храню в долларах США. Таким образом, я могу легко конвертировать в любую другую валюту, учитывая курс конвертации на текущий день.

Если вы никогда не делаете ничего, кроме одной валюты, хуже всего будет то, что вы потратили немного места на хранение нескольких нулей.

Майкл Коллетт
источник
6
Это может привести к неверным результатам из-за отсутствия усечения. Если ненулевые значения непреднамеренно попадают в оставшиеся десятичные разряды, например, в поле цены, содержащее 0,333333 доллара, то может возникнуть ситуация, когда система покажет результат покупки 3 предметов по 0,33 доллара каждый, на общую сумму до 1,00 доллара вместо 0,99 доллара.
Peteris
1
Пертерис, что ты посоветуешь взамен? Независимо от того, насколько точным будет это округление, может возникнуть проблема. Я просто не нашел лучшего способа, даже если он не идеален.
Майкл Коллетт
3
Фиксированная точка и усечение, где это необходимо. Как только вы достигнете «сохраняемой» денежной ценности, например цены, предлагаемой покупателю, она должна быть в соответствующих показателях, которые в большинстве случаев будут выражаться в центах в стандартной розничной среде. Если у вас другие бизнес-потребности (например, цена крупносерийных товаров за единицу), может быть другая настройка точности, но вы должны рассматривать презентацию вместе с хранилищем - если вы отображаете денежное число с десятичными знаками x (или наоборот, например, целыми тысячами), то вы также должны хранить его с такой же точностью, не меньше, но и не больше.
Петерис
Для многих сайтов, связанных с розничной торговлей, это может работать. В основном проекте, с которым я работаю, одна сторона может нуждаться в одинаковой стоимости в одной валюте, с клиентом в другой валюте, для поставщика еще в третьей.
Майкл Коллетт,
19

Используйте 64-битное целое число, сохраненное как bigint

Я рекомендую использовать микродоллары (или аналогичную основную валюту). Микро означает 1 миллионную, поэтому 1 микродоллар = 0,000001 доллара.

  • Прост в использовании и совместим со всеми языками.
  • Достаточная точность для обработки долей цента.
  • Работает при очень низкой цене за единицу (например, за показы рекламы или плату за API).
  • Меньший размер данных для хранения, чем строки или числа.
  • Легко поддерживать точность посредством вычислений и применять округление на конечном выходе.
Мани Гандхам
источник
1
Это наиболее правильный ответ, и любое разумное программное обеспечение имеет дело с самой маленькой денежной единицей в руках. Выполнение всех вычислений над целыми числами означает, что вам не нужно иметь дело с каким-либо языковым искажением float.
Avamander 02
1
Буду использовать и это. Спасибо
Почему это numeric(15,6)предлагается в другом ответе?
Юлиуш Гонера,
@JuliuszGonera По причинам, указанным в ответе. Целые числа меньше и поддерживаются повсюду, что позволяет избежать всех проблем с математическим усечением. Он в основном использует числовые значения, но смещает десятичные дроби, чтобы получить целое число, которое гораздо более совместимо.
Мани Гандхэм
1
Ах, да, я пропустил часть о хранении. Спасибо! Что касается «Простота использования и совместимость со всеми языками», к сожалению, JavaScript поддерживает целые числа до 9007199254740991, что более чем в 1000 раз меньше максимального значения bigint. Существует developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... но он поставляется с ограниченной поддержкой (на данный момент) и предостережениями (например, вы не можете легко умножить его на поплавок при конвертации валюты) , Учитывая, что максимум, который вы можете сохранить в целочисленном JS с использованием микродолларов, составляет 9 миллиардов долларов, это, вероятно, все еще хорошо для большинства случаев.
Юлиуш Гонера,
4

Используется BigIntдля хранения валюты в виде положительного целого числа, представляющего денежное выражение в наименьшей денежной единице (например, 100 центов для хранения 1 доллара США или 100 для хранения 100 иен (японская иена, валюта с нулевым десятичным числом). Это то, что делает Stripe - один самые важные компании финансовых услуг для глобальной электронной коммерции.

Макс Ходжес
источник