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排序一定要按照转换之后的字段排序

三、小结

目前我只知道这两种方式,如果还有其他方法,欢迎大家积极留言!