Join us at MongoDB.local London on 7 May to unlock new possibilities for your data. Use WEB50 to save 50%.
Register now >
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 enfoques para modelar datos monetarios en MongoDB utilizando modelos numéricos y no numéricos.

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

Los siguientes enfoques siguen el modelo numérico:

  • Utilizando el tipo BSON decimal, que es un formato de punto flotante basado en decimal capaz de proporcionar 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 sobre datos monetarios o si las aproximaciones del lado del servidor son suficientes, puede ser adecuado modelar los datos monetarios utilizando el modelo no numérico.

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 consulta coincide con el documento anterior:

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

Para obtener más información sobre el decimal tipo, consulte Decimal.128

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

Una colección se puede transformar iterando sobre todos los documentos de la colección, convirtiendo el valor monetario al tipo decimal y escribiendo el documento nuevamente 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 las conversiones decimal en un entorno de prueba aislado. Una vez creados o modificados los archivos de datos, estos dejarán de ser compatibles con versiones anteriores y no se puede degradar la versió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:

  • Pruebe que el nuevo campo existe y que es 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 del 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. Convierta el valor monetario en un entero multiplicándolo 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 céntimo, multiplique 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 escala es consistente para una moneda; es decir, el mismo factor de escala para una moneda determinada.

  • 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 consistentes al realizar el escalamiento apropiado 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, codifique el valor monetario exacto como un tipo de datos no numérico; por ejemplo, BinData o 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