Menu Docs
Página inicial do Docs
/
Manual do MongoDB
/ / /

Dados monetários do modelo

Nesta página

  • Visão geral
  • Modelo numérico
  • Non-Numeric Model

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., flutuante, 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 consultar o banco de dados para correspondências exatas e matematicamente válidas ou se precisar executar aritmética no lado do servidor, por exemplo, $inc, $mul e aritmética do 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.

  • Usando um fator de escala para converter o valor monetário em um inteiro de 64bits (long tipo BSON) multiplicando por uma potência de 10 fator de escala.

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:

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.

No mongosh, os valores decimal são atribuídos e querydos usando o construtor Decimal128(). O exemplo a seguir adiciona um documento que contém 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 Decimal128.

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, eles 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 fator de escala e salvou o valor monetário como um número inteiro de 64 bits 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.replaceOne( 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") }

Non-Numeric Transformation:

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.replaceOne( 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

O uso do 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.

Voltar

pesquisa por palavra-chave