货币数据模型


进入MongoDB中文手册(4.2版本)目录

1 概述

处理货币数据的应用程序通常需要能够获取货币的小数单位,并且在执行算术运算时需要根据精度对十进制小数四舍五入。许多现代系统(例如,float,double)使用的基于二进制的浮点算法无法表示精确的十进制小数,并且需要某种程度的近似值,使其不适用于货币算法。在对货币数据进行建模时,此约束是重要的考虑因素。
在MongoDB中,有几种使用数值模型和非数值模型对货币数据进行建模的方法。

1.1 数值模型

如果你需要在数据库中准确的查询,数学上有效的匹配或需要进行服务器端的运算,例如$inc,$mul和 聚合框架运算,数值模型可能是合适的。
以下方法遵循数值模型:

  • 使用十进制BSON类型,这是一种基于十进制的浮点格式,能够提供精确的精度。在MongoDB 3.4版和更高版本中可用。
  • 使用比例因子乘以10的幂后可以将货币值转换为64位整数(long BSON类型)。

1.2 非数值模型

如果不需要对货币数据执行服务器端运算,或者如果服务器端近似值足够,则使用非数值模型对货币数据进行建模可能是合适的。
以下方法遵循非数值模型:

  • 使用两个字段来表示货币值:一个字段将确切的货币值存储为非数字值string,另一个字段存储值的基于二进制的浮点(double BSON类型)近似值。

注意
本页中提到的运算是指由mongod或mongos执行的服务器端运算,而不是客户端运算。

2 数值模型

2.1 使用十进制BSON类型

3.4版的新功能。
十进制SON类型使用基于十进制浮点数字格式的IEEE 754标准的decimal128类型。与基于二进制的浮点格式(即double BSON类型)不同,decimal128不是近似十进制值,而是能够提供处理货币数据所需的精确精度。
在mongo shell中使用构造函数NumberDecimal()分配和查询decimal值。以下示例将包含汽油价格的文档添加到gasprices 集合中:

db.gasprices.insert{ "_id" : 1, "date" : ISODate(), "price" : NumberDecimal("2.099"), "station" : "Quikstop", "grade" : "regular" }

以下查询与上面的文档匹配:

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

有关decimal类型的更多信息,请参见NumberDecimal

2.2 将值转换为十进制

可以通过执行一次转换或通过修改应用程序逻辑,并在访问记录时执行转换来将集合的值转换为decimal类型。

提示
从4.0版开始,可以使用$convert和其帮助器$toDecimal运算符将值转换为NumberDecimal(),替代下面概述的过程。

2.3 一次性集合转换

转换集合的方式,可以通过遍历集合中的所有文档,将货币值转换为decimal类型,然后将文档写回到集合中。

注意
强烈建议将decimal值作为新字段添加到文档中,并在新字段的值经过验证后再删除旧字段。

警告
确保在隔离的测试环境中测试转换decimal。使用MongoDB 3.4版创建或修改数据文件后,它们将不再与以前的版本兼容,并且不支持降级包含小数位的数据文件。

2.4 比例因子转换

考虑以下使用比例因子方法并将货币值保存为代表美分数的64位整数的集合 :

{ "_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") }

通过$multiply运算符将price和NumberDecimal(“0.01”)相乘,long值可以被转换为适当格式的decimal值。以下聚合管道在$addFields阶段中将转换后的值分配给新字段priceDec:

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

可以使用以下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") }

如果您不想使用decimal值添加新字段,则可以覆盖原始字段。以下 update()方法首先检查price是否存在并是否是long类型,然后将long值转换为decimal并将其存储在price字段中:

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

可以使用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") }

2.5 非数值转换

考虑以下使用非数值模型的集合,该集合将货币值存储为一个带有该值的精确表示形式的字符串(string):

{ "_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" }

以下函数首先检查price是否存在且是否是字符串,将字符串值转换为一个decimal值并将其存储在priceDec字段中:

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

函数不向命令行输出任何内容。可以使用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") }

2.6 应用逻辑变换

可以从应用程序逻辑中执行到decimal 类型的转换。在这种情况下,应用程序修改为在访问记录时执行转换。
典型的应用程序逻辑如下:

  • 测试新字段是否存在并且是否是decimal类型
  • 如果新decimal字段不存在:
    • 通过正确转换旧字段值来创建它
    • 删除旧字段
    • 保持转换后的记录

2.7 使用比例因子

注意
如果您使用的是MongoDB 3.4版或更高版本,则使用十进制类型建模货币数据比使用“ 比例因子”方法更好 。

使用比例因子方法对货币数据进行建模:

  • 确定货币值所需的最大精度。例如,对于USD货币的货币值,您的应用程序可能要求精度低至十分之一美分。
  • 过将货币值乘以10的幂来将货币值转换为整数,以确保所需的最大精度成为整数的最低有效位。例如,如果所需的最大精度是美分的十分之一,则将货币值乘以1000。
  • 存储转换后的货币值。

例如,以下将9.99USD放大1000倍, 从而保持高达十分之一美分的精度。

{ price: 9990, currency: "USD" }

该模型假定对于给定的货币值:

  • 比例因子对于货币是一致的;即给定货币的比例因子相同。
  • 比例因子是货币的恒定且已知的属性;即应用程序可以根据货币确定比例因子。

使用此模型时,应用程序必须在对值进行适当的缩放时保持一致。
有关此模型的用例,请参见本页的数值模型。

3 非数值模型

要使用非数值模型对货币数据进行建模,请将值存储在两个字段中:

  • 一个字段中,将确切的货币值编码为非数值数据类型;例如BinData或string。
  • 在第二个字段中,存储精确值的双精度浮点近似值。

以下示例使用非数值模型存储价格为9.99 USD和费用为0.25 USD:

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

一定要小心,应用程序可以使用数值近似值在字段上执行范围和排序查询。但是,将近似字段用于查询和排序操作要求应用程序执行客户端后处理(client-side post-processing),来解码非数值的精确的表示形式,然后基于精确货币值过滤掉返回的文档。
有关此模型的用例,请参见本页的 1.2 非数值模型。

进入MongoDB中文手册(4.2版本)目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值