mongodb将数字字符串按照数字大小排序
一、问题概述
最近在使用mongodb时遇见了一个问题,因为精度问题,在保存价格的时候使用了字符串!这样做也一直没遇见什么问题,只是有一天,突然有个需求,在展示商品的时候需要按照价格排序,结果悲剧了,因为价格是字符串类型的,排序的时候是按照字符串的规则进行排序的,最终导致查询出来的结果杂乱无章!
二、问题模拟
下面,我们就模拟一下自己遇见的问题。
我们先向数据库中插入如下数据:
> db.product.insertMany([
... {
... "name":"手机",
... "price":"3000"
... },
...
... {
... "name":"充电器",
... "price":"50"
... },
...
... {
... "name":"耳机",
... "price":"900"
... }
... ]);
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("6219c823ca5ebc1273fd6421"),
ObjectId("6219c823ca5ebc1273fd6422"),
ObjectId("6219c823ca5ebc1273fd6423")
]
}
在这里,我们的插入的数据价格都是字符串类型的,我们来验证一下:
> db.product.find({
... price:{$type:'string'}
... }).pretty(); # 查询价格类型为string的记录
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手机",
"price" : "3000"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充电器",
"price" : "50"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳机",
"price" : "900"
}
下面,我们来按照价格进行正序排序:
> db.product.find().sort({"price":1}).pretty(); # 按照价格进行正序排序
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手机",
"price" : "3000"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充电器",
"price" : "50"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳机",
"price" : "900"
}
从上面运行的结果中,我们看出直接对价格进行排序是按照字符串的排序规则进行排序的(即从左到右,依次按照每位进行排序)。可是我们如果想要按照数字字符串表示的数字大小进行排序,该怎么办呢?下面,我将介绍两种解决方案!
三、解决方案
方案一:使用mongodb的collation功能
collation是mongodb3.4的新特性。这就意味着如果你想要使用collation,mongodb的版本必须要大于等于3.4。
> db.product.find().collation({"locale":"zh", "numericOrdering":true}).sort({"price":1}).pretty();
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充电器",
"price" : "50"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳机",
"price" : "900"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手机",
"price" : "3000"
}
在使用了collation之后,我们发现现在的输出结果是按照价格大小进行排序了
说明:
"locale":"zh"表示汉字按照拼音进行排序"numericOrdering":true表示将字符串类型数字按照数字大小进行排序,设置该属性时,必须设置locale属性,否则会报错。
方案二: 使用聚合查询
该方案的原理是将查询出来的结果先转换为数字,再按照转换之后的结果进行排序!
> db.product.aggregate([
... {
... $addFields: {
... priceNum: {
... $toDecimal: "$price"
... }
... }
... },
... { $sort: { priceNum: 1 } }
... ]).pretty();
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充电器",
"price" : "50",
"priceNum" : NumberDecimal("50")
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳机",
"price" : "900",
"priceNum" : NumberDecimal("900")
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手机",
"price" : "3000",
"priceNum" : NumberDecimal("3000")
}
我们发现,使用该种方法也能实现数字字符串的排序!
说明:
$addFields: {priceNum: {$toDecimal: "$price"}}表示将查询出来的价格转换为Decimal,并将这个转换之后的字段命名为priceNum- sort排序一定要按照转换之后的字段排序
三、小结
目前我只知道这两种方式,如果还有其他方法,欢迎大家积极留言!