PostgreSQL: Que tipo de dados deve ser usado para a moeda?

128

Parece que o Moneytipo é desencorajado conforme descrito aqui

Meu aplicativo precisa armazenar moeda, que tipo de dados devo usar? Numérico, Dinheiro ou FLUTUANTE?

sonhador
fonte
7
Se você leu o tópico inteiro, Numeric é o caminho a percorrer.
razpeitia
Para quem trabalha com várias moedas e se preocupa com o armazenamento de códigos de moeda, além dos valores, consulte Modelagem de moeda no banco de dados (SO) e ISO 4217 (Wikipedia). A resposta curta é que você precisará de duas colunas.
Fabien Snauwaert
Não use dinheiro
a_horse_with_no_name 23/06

Respostas:

90

Numérico com precisão forçada de 2 unidades. Nunca use float ou float como tipo de dados para representar a moeda, pois, se o fizer, as pessoas ficarão descontentes quando o resultado final do relatório financeiro estiver incorreto em + ou - alguns dólares.

O tipo de dinheiro é deixado apenas por razões históricas, pelo que sei.

Chris Farmiloe
fonte
9
Não é por isso que você evita o ponto flutuante. Mesmo o Numeric terá erros de arredondamento se você dividir por algo que não se divide em dez, independentemente da precisão usada. (Precisão de 2 é uma má idéia de qualquer maneira ... verifique os docs.)
Doradus
6
Se você deseja suportar moedas arbitrárias, nunca faça uma escala igual a 2 (em termos do postgresql, a precisão é um número de todos os dígitos, por exemplo, em 121.121 é igual a 6). Existem moedas, como o Dinar do Bahrain, em que 1000 subunidades são iguais a uma unidade e existem moedas que não possuem subunidades.
Nikolay Arhipov 24/08/19
@NikolayArhipov bom ponto, então o máximo é realmente #scale - precision
Konrad
1
numeric(3,2)poderá armazenar max9.99 3-2 = 1
Konrad
5
Não faça isso! A menos que você planeje multiplicar por 100 antes de carregar qualquer valor em outros idiomas e depois fazer contas com números inteiros - você terá resultados incorretos. Armazene as coisas em centavos (a menor unidade monetária com a qual você está lidando) e economize um pouco de trabalho. Resposta muito ruim em muitos casos.
Avamander 02/06/19
111

Sua fonte não é oficial. Data de 2011 e eu nem reconheço os autores. Se o tipo de dinheiro fosse oficialmente "desencorajado", o PostgreSQL diria isso no manual - o que não acontece .

Para uma fonte mais oficial , leia este tópico no pgsql-general (a partir desta semana!) , Com declarações de desenvolvedores principais, incluindo D'Arcy JM Cain (autor original do tipo financeiro) e Tom Lane:

Resposta relacionada (e comentários!) Sobre melhorias em versões recentes:

Basicamente, moneytem seus usos (muito limitados). O Wiki do Postgres sugere evitá-lo em grande parte, exceto nos casos definidos de maneira restrita. O mais vantagem numericé o desempenho .

decimalé apenas um apelido para numericno Postgres e amplamente usado para dados monetários, sendo um tipo de "precisão arbitrária". O manual :

O tipo numericpode armazenar números com um número muito grande de dígitos. É especialmente recomendado para armazenar valores monetários e outras quantidades onde a exatidão é necessária.

Pessoalmente, eu gosto de guardar a moeda como integerrepresentando centavos se centavos fracionários nunca ocorrerem (basicamente onde o dinheiro faz sentido). Isso é mais eficiente do que qualquer outra opção mencionada.

Erwin Brandstetter
fonte
4
Existem várias discussões nas listas de discussão que dão a impressão de que o tipo de dinheiro é pelo menos não recomendado, por exemplo: aqui: postgresql.nabble.com/Money-type-todos-td1964190.html#a1964192 mais para ser justo: o manual para a versão 8.2 foi chamá-lo obsoleto: postgresql.org/docs/8.2/static/datatype-money.html
a_horse_with_no_name
11
@a_horse_with_no_name: seu link é para um thread de 2007, que também é quando 8.2 era a versão atual e o moneytipo era, de fato, obsoleto. Os problemas foram corrigidos e o tipo foi adicionado novamente em versões posteriores. Pessoalmente, gosto de armazenar moeda como integerrepresentando centavos.
Erwin Brandstetter
1
Erwin, você pode estar pensando corretamente apenas da perspectiva do banco de dados. No entanto, se você combinar Postgresql + Java, isso NÃO é de todo bom (pela minha experiência). Lendo seu comentário, usei MONEY para a maioria dos meus campos de moeda e agora recebo a exceção Java: " Ocorreu SQLException: org.postgresql.util.PSQLException: valor inválido para o tipo double: 2.500,00 ". Eu pesquisei no Google e não encontrei uma boa solução, então estou na tarefa chata de mudar todos eles para NUMÉRICO ou DECIMAL agora !!!
MD
1
@ MD: Desculpe ouvir isso, mas obviamente eu não falava em Java (o que não posso). A mensagem de erro é estranha. "Duplo"? E o separador de milhares também pode ser um problema. Você pode querer começar uma nova pergunta sobre isso.
Erwin Brandstetter
2
@PirateApp: Sim, meu favorito pessoal. Você pode ter perdido a última frase da minha resposta, dizendo exatamente isso.
Erwin Brandstetter 25/03
68

Suas escolhas são:

  1. bigint: armazene o valor em centavos. É isso que as transações EFTPOS usam.
  2. decimal(12,2): armazene o valor com exatamente duas casas decimais. É o que o software de contabilidade mais geral usa.
  3. float: idéia terrível - precisão inadequada. É isso que desenvolvedores ingênuos usam.

A opção 2 é a mais comum e mais fácil de trabalhar. Faça com que a precisão (12 no meu exemplo, que significa 12 dígitos ao todo) seja grande ou pequena, conforme for melhor para você.

Observe que, se você estiver agregando várias transações que resultaram de um cálculo (por exemplo, envolvendo uma taxa de câmbio) em um único valor com significado comercial, a precisão deve ser maior para fornecer um valor macro preciso; considere usar algo assim decimal(18, 8)para que a soma seja precisa e os valores individuais possam ser arredondados para centavos de precisão para exibição.

Boêmio
fonte
10
Se você estiver trabalhando com qualquer tipo de cálculo de imposto reverso ou câmbio, precisará de pelo menos 4 casas decimais ou perderá dados. Então, numeric(15,4)ou numeric(15,6)é uma boa ideia.
Petrus Theron
3
Existe uma quarta opção - que é usar uma String e usar um tipo decimal sem perdas equivalente no idioma do host.
amigos estão dizendo sobre ioquatix
2
que tal um número inteiro escalonado, que certamente armazenar 10000.045 não faria mal se fosse armazenado como 10000045 com um fator de escala 1000x?
PirateApp 18/03/19
26

Eu mantenho todos os meus campos monetários como:

numeric(15,6)

Parece excessivo ter tantas casas decimais, mas se houver a menor chance de você lidar com várias moedas, precisará de tanta precisão para a conversão. Não importa o que eu estou apresentando para um usuário, eu sempre guardo em dólares americanos. Dessa forma, eu posso facilmente converter para qualquer outra moeda, dada a taxa de conversão do dia envolvido.

Se você nunca fizer nada além de uma moeda, a pior coisa aqui é que você perdeu um pouco de espaço para armazenar alguns zeros.

Michael Collette
fonte
6
Isso corre o risco de resultados errados devido à falta de truncamento. Se valores diferentes de zero vazarem acidentalmente nas casas decimais restantes, por exemplo, um campo de preço contendo 0,333333 dólares, você poderá ter uma situação em que o sistema mostre o resultado de alguém comprar 3 itens a US $ 0,33 cada, totalizando US $ 1,00 em vez de US $ 0,99.
Peteris
1
Perteris, então o que você sugere? Não importa quanta precisão você jogue nesse arredondamento pode ser um problema. Simplesmente não encontrei um caminho melhor, mesmo que este não seja o ideal.
Michael Collette
3
Ponto fixo e truncar sempre que apropriado. Assim que você atingir um valor monetário "armazenável", por exemplo, um preço oferecido a um cliente, ele deverá estar em métricas apropriadas, que na maioria dos casos estariam em centavos inteiros no ambiente de varejo padrão. Se você tiver necessidades comerciais diferentes (por exemplo, preço de produtos de alto volume por unidade), pode haver uma configuração diferente de precisão, mas você deve tratar a apresentação juntamente com o armazenamento - se exibir o número da moeda com x decimais (ou vice-versa, por exemplo, em milhares inteiros), você também deve armazená-lo com essa precisão, nem menos, mas também não mais.
Peteris
Para muitos sites relacionados ao varejo que podem funcionar. O projeto principal com o qual trabalho pode ter uma parte que precisa ver o mesmo custo em uma moeda, com um cliente em outra moeda, para um fornecedor ainda na 3ª.
Michael Collette
19

Use um número inteiro de 64 bits armazenado como bigint

Eu recomendo o uso de microdólares (ou moeda principal semelhante). Micro significa milionésimo, então 1 microdólar = US $ 0,000001.

  • Simples de usar e compatível com todos os idiomas.
  • Precisão suficiente para lidar com frações de um centavo.
  • Funciona a preços por unidade muito pequenos (como impressões de anúncios ou cobranças da API).
  • Tamanho de dados menor para armazenamento do que cadeias ou números.
  • Fácil de manter a precisão através de cálculos e aplicar arredondamentos na saída final.
Mani Gandham
fonte
1
Essa é a resposta mais correta e qualquer software razoável lida com a menor unidade monetária disponível. Fazer toda a matemática em números inteiros significa que você não precisa lidar com nenhum problema de flutuação específico do idioma.
Avamander 02/06/19
1
Também estará usando isso. Graças
Por que isso numeric(15,6)sugerido em outra resposta?
Juliusz Gonera
@JuliuszGonera Pelas razões listadas na resposta. Os números inteiros são menores e são suportados em todos os lugares e evitam todos os problemas de truncamento matemático. É basicamente usando numérico, mas mudando os decimais para que você tenha um número inteiro muito mais compatível.
Mani Gandham
1
Ah, certo, eu perdi a parte sobre armazenamento. Obrigado! Em relação a "Simples de usar e compatível com todos os idiomas", infelizmente, o JavaScript suporta números inteiros até 9007199254740991, que são mais de 1000x menores que o valor máximo de bigint. Existe o developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…, mas ele vem com suporte limitado (por enquanto) e advertências (por exemplo, você não pode multiplicá-lo por um flutuador facilmente ao fazer conversão de moeda) . Dado que o máximo que você pode armazenar em um número inteiro JS usando micro-dólares é de US $ 9 bilhões, o que provavelmente ainda é bom para a maioria dos casos.
Juliusz Gonera
3

Use BigIntpara armazenar a moeda como um número inteiro positivo que representa o valor monetário na menor unidade monetária (por exemplo, 100 centavos para armazenar $ 1,00 ou 100 para armazenar ¥ 100 (iene japonês, uma moeda de zero decimal). É isso que o Stripe faz - um as empresas de serviços financeiros mais importantes para o comércio eletrônico global.

Max Hodges
fonte