Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Docs Menu
Docs Home
/ /

Datos Monetarios del Modelo

Las aplicaciones que gestionan datos monetarios suelen requerir la capacidad de capturar unidades fraccionarias de moneda y necesitan emular el redondeo decimal con precisión exacta al realizar cálculos aritméticos. La aritmética en coma flotante basada en binario utilizada por muchos sistemas modernos (es decir, float, double) no puede representar fracciones decimales exactas y requiere cierto grado de aproximación, lo que la hace inadecuada para aritmética monetaria. Esta restricción es una consideración importante al modelar datos monetarios.

Existen varios métodos para modelar datos monetarios en MongoDB empleando modelos numéricos y no numéricos.

El modelo numérico puede ser apropiado si necesitas realizar una query en la base de datos para obtener coincidencias exactas matemáticamente válidas o necesitas realizar cálculos aritméticos del lado del servidor, por ejemplo, $inc, $mul y aritmética de la pipeline de agregación.

Los siguientes enfoques siguen el modelo numérico:

  • Usar Decimal BSON Type, que es un formato de coma flotante decimal que puede proporcionar una precisión exacta.

  • Uso de un factor de escala para convertir el valor monetario a un 64-bit integer (tipo BSON long) mediante la multiplicación por una potencia del factor de escala 10.

Si no es necesario realizar cálculos aritméticos del lado del servidor con datos monetarios o si las aproximaciones del lado del servidor son suficientes, modelar los datos monetarios utilizando el modelo no numérico puede ser adecuado.

El siguiente enfoque sigue el modelo no numérico:

  • Uso de dos campos para el valor monetario: Un campo almacena el valor monetario exacto como un string no numérico y otro campo almacena una aproximación en coma flotante basada en binarios (double tipo BSON) del valor.

Nota

La aritmética mencionada en esta página se refiere a la aritmética del lado del servidor realizada por mongod o mongos, y no a la aritmética del lado del cliente.

El decimal128 tipo de BSON utiliza el formato de numeración de coma flotante basado en decimales IEEE 754 decimal128. A diferencia de los formatos de coma flotante basados en binario, como el tipo BSON double, decimal128 no aproxima valores decimales y puede proporcionar la precisión exacta necesaria para trabajar con datos financieros.

En mongosh, los valores de decimal se asignan y consultan usando el constructor Decimal128(). El siguiente ejemplo agrega un documento con precios del gas a una colección gasprices:

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

La siguiente query coincide con el documento anterior:

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

Para obtener más información sobre el tipo decimal, consulta Decimal128.

Los valores de una colección pueden transformarse al tipo decimal mediante una transformación puntual o modificando la lógica de la aplicación para realizar la transformación al acceder a los registros.

Tip

Como alternativa al procedimiento que se describe a continuación, a partir de la versión 4.0, puede usar el $convert y su operador asistente $toDecimal para convertir valores a Decimal128().

Se puede transformar una colección iterando sobre todos los documentos de la colección, convirtiendo el valor monetario al tipo decimal y escribiendo el documento de vuelta en la colección.

Nota

Se recomienda encarecidamente agregar el valor decimal al documento como un nuevo campo y remover el campo antiguo más tarde, una vez que se hayan verificado los valores del nuevo campo.

Advertencia

Asegúrese de probar decimal conversiones en un entorno de prueba aislado. Una vez que los archivos de datos se crean o se modifican, ya no serán compatibles con versiones anteriores y no se admite la degradación de archivos de datos que contengan decimales.

Transformación por factor de escalar:

Considera la siguiente colección que utilizó el enfoque Factor de escala y guardó el valor monetario como un entero de 64bits que representa el número de centavos:

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

El valor long se puede convertir en un valor decimal con el formato apropiado multiplicando price y Decimal128("0.01") usando el operador $multiply . El siguiente pipeline de agregación asigna el valor convertido al nuevo campo priceDec en la etapa $addFields:

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

Los resultados del pipeline de agregación se pueden verificar utilizando la query db.clothes.find():

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

Si no deseas agregar un nuevo campo con el valor decimal, el campo original puede sobrescribirse. El siguiente método updateMany() primero verifica que price exista y que sea un long, luego transforma el valor long a decimal y lo almacena en el campo price:

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

Los resultados pueden verificarse utilizando la query db.clothes.find():

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

Non-Numeric Transformation:

Considera la siguiente colección que utilizó el modelo no numérico y guardó el valor monetario como string con la representación exacta del 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" }

La siguiente función primero verifica que price existe y que es un string, luego transforma el valor string a un valor decimal y lo almacena en el campo priceDec:

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

La función no devuelve nada en la línea de comandos. Los resultados se pueden verificar usando la consulta db.clothes.find():

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

Es posible realizar la transformación al tipo decimal desde dentro de la lógica de la aplicación. En este escenario, la aplicación se modifica para realizar la transformación a medida que accede a los registros.

La lógica típica de la aplicación es la siguiente:

  • Comprueba que exista el nuevo campo y que sea de tipo decimal

  • Si el nuevo campo decimal no existe:

    • Créalo convirtiendo correctamente los valores de los campos antiguos

    • Remover el campo anterior

    • Guardar el registro transformado

Nota

La utilización del tipo decimal para modelar datos monetarios es preferible al método Scale Factor.

Para modelar datos monetarios utilizando el enfoque de factor de escala:

  1. Determina la máxima precisión necesaria para el valor monetario. Por ejemplo, tu aplicación puede requerir una precisión de hasta una décima de centavo para valores monetarios en la divisa USD.

  2. Convierte el valor monetario en un entero multiplicando el valor por una potencia de 10 que garantice que la precisión máxima necesaria se convierta en el dígito menos significativo del entero. Por ejemplo, si la precisión máxima requerida es la décima de un centavo, multiplica el valor monetario por 1000.

  3. Almacene el valor monetario convertido.

Por ejemplo, la siguiente escala 9.99 USD por 1000 para preservar la precisión hasta una décima de centavo.

{ price: 9990, currency: "USD" }

El modelo asume que para un valor dado de moneda:

  • El factor de escalado es coherente para una divisa; es decir, el mismo factor de escalado para una determinada divisa.

  • El factor de escala es una propiedad constante y conocida de la moneda; es decir, las aplicaciones pueden determinar el factor de escala a partir de la moneda.

Al utilizar este modelo, las aplicaciones deben ser coherentes al realizar el escalado adecuado de los valores.

Para los casos de uso de este modelo, ve Modelo Numérico.

Para modelar datos monetarios utilizando el modelo no numérico, almacene el valor en dos campos:

  1. En un campo, codifica el valor monetario exacto como un tipo de dato no numérico; por ejemplo, BinData o un string.

  2. En el segundo campo, almacene una aproximación en coma flotante de doble precisión del valor exacto.

El siguiente ejemplo utiliza el modelo no numérico para almacenar 9.99 USD para el precio y 0.25 USD para la cuota:

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

Con un poco de cuidado, las aplicaciones pueden realizar consultas de rango y de orden sobre el campo con la aproximación numérica. Sin embargo, el uso del campo de aproximación para las operaciones de query y ordenación requiere que las aplicaciones realicen un post-procesamiento en el cliente para descodificar la representación no numérica del valor exacto y luego filtren los documentos devueltos en función del valor monetario exacto.

Para casos de uso de este modelo, consulta Modelo no numérico.

Volver

Búsqueda por palabra clave

En esta página