数据库对实际为字符串的数值进行排序

时间:2017-05-26 12:19:22

标签: mongodb mongoose mongodb-query aggregation-framework

我有以下查询。其中field_aString属性,field_bNumber类型的数组。我想要一个具有唯一组合的属性field_afield_b的数组。这里field_a包含数值但是以字符串格式。所以我想在聚合管道中应用自然排序。 $natural只能用于此类查询db.collection.find().sort( { $natural: 1 } )

那么如何在MongoDB中使用自然排序或者我需要依赖JS函数或lodash / underscore.js?

db.collection.aggregate([
    {"$group": { "_id": { field_a: "$field_a", field_b: "$field_b" } } },
    { $project: { a: "$_id" } },
    {"$group": {"_id": 'a', "res": {"$addToSet": "$_id" }}},
    {"$unwind": "$res"},
    {"$sort": { "res": 1}},
    {"$group": { "_id": null, "res": {"$push": "$res" }}}
])

1 个答案:

答案 0 :(得分:0)

我说,这就是你想要做的事情:

db.collection.aggregate([
  { "$group": {
    "_id": {
      "field_a": {
        "$concat": [
          { "$substrCP": [ 
            "0000000000",
            0,
            { "$subtract": [ 10, { "$strLenCP": "$field_a" } ] }
          ]},
          "$field_a"
        ]
      },
      "field_b": "$field_b"
    }
  }},
  { "$sort": { "_id": 1 } }
])

说明

作为一个基本概念,您遇到的问题是“字符串”的排序方式不能转化为数字排序的工作方式。

作为一个简短的例子,这些文档使用字符串值:

{ "_id" : ObjectId("5928276f84c3559bc2fd458b"), "a" : "5" }
{ "_id" : ObjectId("5928277484c3559bc2fd458c"), "a" : "50" }
{ "_id" : ObjectId("5928277e84c3559bc2fd458d"), "a" : "60" }
{ "_id" : ObjectId("5928278284c3559bc2fd458e"), "a" : "6" }

如果您尝试对这些进行排序,则适用词法顺序:

> db.list.find().sort({ "a": 1 })
{ "_id" : ObjectId("5928276f84c3559bc2fd458b"), "a" : "5" }
{ "_id" : ObjectId("5928277484c3559bc2fd458c"), "a" : "50" }
{ "_id" : ObjectId("5928278284c3559bc2fd458e"), "a" : "6" }
{ "_id" : ObjectId("5928277e84c3559bc2fd458d"), "a" : "60" }

作为字符串,这是有道理的。由于"50""5"开头,因此小于"6"

由于聚合框架不能将这些“转换”为数值,因此您唯一的选择是以一种词汇方式呈现“字符串”,其方式与数值相同。

简而言之,我们将它们“零填充”它们,这实际上是使值固定长度字符串,这些字符串带有前缀或“填充”0,这使得“字符串”看起来像它们在数字上一样有序:

db.list.aggregate([
  { "$project": {
    "a": {
      "$concat": [
        { "$substrCP": [ 
          "0000000000",
          0,
          { "$subtract": [ 10, { "$strLenCP": "$a" } ] }
        ]},
        "$a"
      ]
    }
  }},
  { "$sort": { "a": 1 } }
])

这将产生一个列表,如:

{ "_id" : ObjectId("5928276f84c3559bc2fd458b"), "a" : "0000000005" }
{ "_id" : ObjectId("5928278284c3559bc2fd458e"), "a" : "0000000006" }
{ "_id" : ObjectId("5928277484c3559bc2fd458c"), "a" : "0000000050" }
{ "_id" : ObjectId("5928277e84c3559bc2fd458d"), "a" : "0000000060" }

这里的基本前提是你接受一个“模板”字符串,在这个例子中是一个0字符串,长度为10个字符。然后,我们使用$strLenCP$subtract来查看要转换的字段数据的长度,该长度来自10,这是此处使用的模板字符串的长度。

长度差异作为从模板中获取的字符数馈送到$substrCP运算符。然后将此输出提供给$concat,以便创建一个类似于模板的“字符串”长度为10个字符,但以零开头并以初始数字字符串结尾。

在您的实际使用中,这将最终作为复合键的一部分。然而,由于转换后的密钥首先在密钥顺序中,因此简单地按_id排序会考虑这一点并主要按第一个键中的值排序,然后按第二个键排序。