【问题标题】:Database sort on numeric values that are actually strings数据库对实际上是字符串的数值进行排序
【发布时间】:2017-10-27 08:10:13
【问题描述】:

我有以下查询。其中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" }}}
])

【问题讨论】:

  • 谁告诉你$natural 会在这里帮助你?它的用法与您所说的无关。您正在谈论将像 [ "5", "50", "6", 60" ] 这样的字符串排序为数字顺序,对吗?你不能这样做,除非我们花哨并“零填充”这些值。
  • @NeilLunn 没有人告诉我,但我认为它可以工作。 “除非我们花哨并“零填充”值”是什么意思?
  • 您的field_a 包含字符串形式的数字数据,就像我上面列出的那样,问题是如图所示"50" 认为小于"6" 是吗? (意味着我实际上需要你确认)。我的意思是,根据您使用的 MongoDB 服务器版本,您也许可以对此做一些事情来启用服务器端排序。
  • 是的,如果我的结果数据是[ "5", "50", "6", "60" ],我需要它们为[ "5", "6", "50", "60" ]
  • 好的。附加问题。上述聚合应该达到什么目的?这一切只是为了获得两个字段的独特组合并按排序顺序进行的练习吗?只是试图理解意图,因为采取了许多不必要的步骤。能否请您确认一下您的 mongodb 服务器版本。

标签: mongodb mongoose mongodb-query aggregation-framework


【解决方案1】:

简而言之,这就是你想要在这里做的事情:

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 排序会考虑这一点,并且主要按第一个键中的值排序,然后是第二个键。

【讨论】:

  • 感谢伙伴,您的解决方案很棒,但是有没有办法删除那些前导零?我找到了仅使用 forEach 循环的解决方案。有什么方法可以使用查询吗?
  • @Xyroid 你能删除服务器上的零吗?不,你不能,因为基本上那将是“转换为数字”,如果我们能做到这一点,那么我们一开始就不会这样做。在客户端,那么当然可以。那么基本上就是如果在客户端代码中可以进行操作,那么就在那里进行操作。你真的应该只在重要的服务器上做事。比如在服务器上做“排序”,这样你就可以“限制”响应返回。
  • @Xyroid 提供的答案中是否有您认为无法解决您的问题的内容?如果是这样,请对答案发表评论,以澄清究竟需要解决哪些尚未解决的问题。如果它确实回答了您提出的问题,请注意Accept your Answers您提出的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-09
  • 1970-01-01
  • 2011-02-20
  • 2011-10-07
  • 2012-11-14
相关资源
最近更新 更多