Menu Docs

Página inicial do DocsDesenvolver aplicaçõesManual do MongoDB

Dados monetários do modelo

Nesta página

  • Visão geral
  • Modelo numérico
  • Modelo não numérico

Aplicativos que lidam com dados monetários geralmente exigem a capacidade de capturar unidades fracionárias de moeda e precisam emular arredondamentos decimais com precisão exata ao executar aritmética. A aritmética de ponto flutuante baseada em binários usada por muitos sistemas modernos (i.e., float, double) é incapaz de representar frações decimais exatas e requer algum grau de aproximação, tornando-a inadequada para a aritmética monetária. Essa restrição é uma consideração importante ao modelar dados monetários.

Existem várias abordagens para modelar dados monetários no MongoDB usando os modelos numéricos e não numéricos.

O modelo numérico pode ser apropriado se você precisar queryr o banco de dados para obter correspondências exatas e matematicamente válidas ou se precisar executar aritmética no lado do servidor, por exemplo, $inc, $mul e aritmética de pipeline de agregação.

As seguintes abordagens seguem o modelo numérico:

  • Usando o tipo Decimal BSON , que é um formato de ponto flutuante baseado em decimal capaz de fornecer precisão exata. Disponível na versão 3 do MongoDB.4 e posterior.

  • Uso de um fator de escala para converter o valor monetário em um número inteiro 64bits (tipo long BSON), multiplicando-o por um fator de escala de potência 10 .

Se não houver necessidade de executar aritmética no lado do servidor em dados monetários, ou se as aproximações no lado do servidor forem suficientes, a modelagem de dados monetários usando o modelo não numérico pode ser adequada.

A seguinte abordagem segue o modelo não numérico:

  • Utilizando dois campos para o valor monetário: um campo armazena o valor monetário exato como um string não numérico e outro campo armazena uma aproximação de ponto flutuante baseada em binário (tipo JSON double ) do valor.

Observação

A aritmética mencionada nesta página refere-se à aritmética do lado do servidor executada por mongod ou mongos, e não à aritmética do lado do cliente.

O tipo decimal128 BSON usa o formato de numeração de ponto flutuante baseado em decimal IEEE 754 decimal128. Ao contrário dos formatos de ponto flutuante baseados em binário, como o tipo double BSON, o decimal128 não aproxima os valores decimais e é capaz de fornecer a precisão exata necessária para trabalhar com dados monetários.

Em mongosh, decimal valores são atribuídos e consultados usando o construtor Decimal128() . O exemplo a seguir adiciona um documento contendo preços de gás a uma coleção gasprices :

db.gasprices.insertOne(
{
"date" : ISODate(),
"price" : Decimal128("2.099"),
"station" : "Quikstop",
"grade" : "regular"
}
)

A seguinte consulta corresponde ao documento acima:

db.gasprices.find( { price: Decimal128("2.099") } )

Para obter mais informações sobre o tipo decimal , consulte NumberDecimal.

Os valores de uma coleção podem ser transformados para o tipo decimal executando uma transformação única ou modificando a lógica do aplicativo para executar a transformação à medida que acessa os registros.

Dica

Alternativa ao procedimento descrito abaixo, a partir da versão 4.0, você pode usar o $convert e seu operador de $toDecimal auxiliar para converter valores em Decimal128().

Uma coleção pode ser transformada iterando todos os documentos na coleção, convertendo o valor monetário para o tipo decimal e gravando o documento de volta na coleção.

Observação

É altamente recomendável adicionar o valor decimal ao documento como um novo campo e remover o campo antigo posteriormente, quando os valores do novo campo tiverem sido verificados.

Aviso

Certifique-se de testar conversões decimal em um ambiente de teste isolado. Depois que os arquivos de dados forem criados ou modificados com o MongoDB versão 3.4, não serão mais compatíveis com versões anteriores e não haverá suporte para downgrade de arquivos de dados contendo decimais.

Transformação do fator de escala:

Considere a seguinte coleção que usou a abordagem Scale Factor e salvou o valor monetário como um número inteiro 64bits representando o número de centavos:

{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberLong("1999") },
{ "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberLong("3999") },
{ "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberLong("2999") },
{ "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberLong("2495") },
{ "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberLong("8000") }

O valor long pode ser convertido em um valor decimal formatado adequadamente, multiplicando price e NumberDecimal("0.01") usando o operador $multiply. O seguinte pipeline de agregação atribui o valor convertido ao novo campo priceDec na etapa $addFields:

db.clothes.aggregate(
[
{ $match: { price: { $type: "long" }, priceDec: { $exists: 0 } } },
{
$addFields: {
priceDec: {
$multiply: [ "$price", NumberDecimal( "0.01" ) ]
}
}
}
]
).forEach( ( function( doc ) {
db.clothes.save( doc );
} ) )

Os resultados do pipeline de agregação podem ser verificados utilizando a consulta db.clothes.find():

{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberLong(1999), "priceDec" : NumberDecimal("19.99") }
{ "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberLong(3999), "priceDec" : NumberDecimal("39.99") }
{ "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberLong(2999), "priceDec" : NumberDecimal("29.99") }
{ "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberLong(2495), "priceDec" : NumberDecimal("24.95") }
{ "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberLong(8000), "priceDec" : NumberDecimal("80.00") }

Se você não desejar adicionar um novo campo com o valor decimal, o campo original poderá ser substituído. O método updateMany() a seguir primeiro verifica se price existe e se é um long e, em seguida, transforma o valor long em decimal e o armazena no campo price:

db.clothes.updateMany(
{ price: { $type: "long" } },
{ $mul: { price: NumberDecimal( "0.01" ) } }
)

Os resultados podem ser verificados utilizando a consulta db.clothes.find():

{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberDecimal("19.99") }
{ "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberDecimal("39.99") }
{ "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberDecimal("29.99") }
{ "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberDecimal("24.95") }
{ "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberDecimal("80.00") }

Transformação não numérica:

Considere a seguinte coleção que utilizou o modelo não numérico e salvou o valor monetário como um string com a representação exata do valor:

{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99" }
{ "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99" }
{ "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99" }
{ "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95" }
{ "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00" }

A função a seguir verifica primeiro se price existe e se é um string, depois transforma o valor string em um valor decimal e o armazena no campo priceDec:

db.clothes.find( { $and : [ { price: { $exists: true } }, { price: { $type: "string" } } ] } ).forEach( function( doc ) {
doc.priceDec = NumberDecimal( doc.price );
db.clothes.save( doc );
} );

A função não envia nada para a linha de comando. Os resultados podem ser verificados usando a consulta db.clothes.find():

{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99", "priceDec" : NumberDecimal("19.99") }
{ "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99", "priceDec" : NumberDecimal("39.99") }
{ "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99", "priceDec" : NumberDecimal("29.99") }
{ "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95", "priceDec" : NumberDecimal("24.95") }
{ "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00", "priceDec" : NumberDecimal("80.00") }

É possível executar a transformação para o tipo decimal a partir da lógica de aplicativos. Neste cenário, o aplicativo foi modificado para realizar a transformação à medida que acessa os registros.

A lógica típica do aplicativo é a seguinte:

  • Teste se o novo campo existe e se é do tipo decimal

  • Se o novo campo decimal não existir:

    • Crie-o convertendo corretamente os valores dos campos antigos

    • Remover o campo antigo

    • Persistir o registro transformado

Observação

Se você estiver usando o MongoDB versão 3.4 ou superior, usar o tipo decimal para modelar dados monetários é preferível ao método Scale Factor .

Para modelar dados monetários usando a abordagem do fator de escala:

  1. Determine a precisão máxima necessária para o valor monetário. Por exemplo, sua aplicação pode exigir precisão até o décimo de um centavo para valores monetários na moeda USD.

  2. Converta o valor monetário em um número inteiro multiplicando o valor por uma potência de 10 que garante que a precisão máxima necessária se torne o dígito menos significativo do número inteiro. Por exemplo, se a precisão máxima exigida for o décimo de um centavo, multiplique o valor monetário por 1000.

  3. Armazene o valor monetário convertido.

Por exemplo, as seguintes escalas 9.99 USD por 1000 para preservar a precisão até um décimo de um centavo.

{ price: 9990, currency: "USD" }

O modelo pressupõe que para um determinado valor monetário:

  • O fator de escala é consistente para uma moeda; ou seja, o mesmo fator de escala para uma determinada moeda.

  • O fator de escala é uma propriedade constante e conhecida da moeda; ou seja, os aplicativos podem determinar o fator de escala a partir da moeda.

Ao usar esse modelo, os aplicativos devem ser consistentes na execução da escala apropriada dos valores.

Para casos de uso deste modelo, consulte Modelo numérico.

Para modelar dados monetários usando o modelo não numérico, armazene o valor em dois campos:

  1. Em um campo, codifique o valor monetário exato como um tipo de dados não numéricos; por exemplo, BinData ou string.

  2. No segundo campo, armazene uma aproximação de ponto flutuante de precisão dupla do valor exato.

O exemplo a seguir usa o modelo não numérico para armazenar 9.99 USD pelo preço e 0.25 USD pela taxa:

{
price: { display: "9.99", approx: 9.9900000000000002, currency: "USD" },
fee: { display: "0.25", approx: 0.2499999999999999, currency: "USD" }
}

Com alguns cuidados, os aplicativos podem realizar consultas de intervalo e classificação no campo com a aproximação numérica. No entanto, o uso do campo de aproximação para as operações de consulta e classificação requer que os aplicativos executem pós-processamento do lado do cliente para decodificar a representação não numérica do valor exato e, em seguida, filtrar os documentos retornados com base no valor monetário exato.

Para casos de uso deste modelo, consulte Modelo não numérico.

← Dados de modelo para controle de versão de esquema