【问题标题】:sort by embedded object value in Mongodb按 MongoDB 中的嵌入对象值排序
【发布时间】:2015-11-08 08:47:38
【问题描述】:

我有这样的架构:

{
   tags:[
      {
         id:"t1",
         score:70
      },
      {
         id:"t1",
         score:60
      }
   ]
 }

我想对 tag.id 搜索上的查询进行排序,以按相应的分数排序。因此,如果我搜索 db.collection.find({tags.id:"t1"}).sort({tags.score:-1}) ,它会按“t1”对象的分数而不是其他标签进行排序。

有什么建议吗?

【问题讨论】:

  • 如果您的数组样本包含除“t1”之外的其他值,并且可能包含多个文档和预期结果,则可能会提出更清晰的问题。但我想我明白你在说什么。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

如果您需要在运行时计算出类似这样的内容,并使用数组中的“过滤”内容确定排序顺序,那么您最好使用.aggregate() 来重塑并确定这样的排序值:

db.collection.aggregate([
    // Pre-filter the array elements
    { "$project": {
        "tags": 1,
        "score": {
            "$setDifference": [
                { "$map": {
                    "input": "$tags",
                    "as": "tag",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.id", "t1" ] },
                            "$$el.score",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }},
    // Unwind to denormalize
    { "$unwind": "$score" },
    // Group back the "max" score
    { "$group": {
        "_id": "$_id",
        "tags": { "$first": "$tags" },
        "score": { "$max": "$score" }
    }},
    // Sort descending by score
    { "$sort": { "score": -1 } }
])

管道的第一部分用于将数组内容(以及保留原始字段)“预过滤”为 id 等于“t1”的“score”值。这是通过处理$map 来完成的,它通过$cond 将条件应用于每个元素,以确定是返回该元素的“分数”还是返回false

$setDifference 操作与单个元素数组[false] 进行比较,这有效地删除了从$map 返回的任何false 值。作为一个“集合”,这也删除了重复的条目,但出于排序目的,这是一件好事。

随着数组减少并重新调整为您处理的值$unwind 准备好在下一阶段将值作为单个元素处理。 $group 阶段实质上将$max 应用于“分数”以返回过滤结果中包含的最高值。

然后只需在确定的值上应用$sort 即可对文档进行排序。当然,如果您想反过来,请使用 $min 并按升序排序。

当然,如果您真正想要的是在标签中实际包含 id 的“t1”值的文档,则在开头添加一个 $match 阶段。但这部分与您想要实现的过滤结果的排序无关。

计算的替代方法是在将条目写入文档中的数组时完成所有操作。有点乱,但它是这样的:

db.collection.update(
    { "_id": docId },
    {
        "$push": { "tags": { "id": "t1", "score": 60 } },
        "$max": { "maxt1score": 60 },
        "$min": { "mint1score": 60 }
    }
)

这里$max 更新运算符仅在新值大于现有值或不存在属性时为指定字段设置值。 $min 的情况正好相反,只有小于它才会被新值替换。

这当然会产生为文档添加各种附加属性的效果,但最终的结果是大大简化了排序:

db.collection.find().sort({ "maxt1score": -1 })

而且它的运行速度比使用聚合管道计算要快得多。

因此请考虑设计原则。数组中的结构化数据需要过滤和配对结果进行排序,这意味着在运行时计算以确定要排序的值。向.update() 上的文档添加其他属性意味着您可以简单地引用这些属性以便直接对结果进行排序。

【讨论】:

  • 感谢您的解释。第二种方法不适合我的情况,因为我可以有 50-100 个标签变体。虽然它真的很有效。聚合函数给了我一个错误,即 el 未定义,当将其转换为 $el 时,它没有显示任何结果,我在这里遗漏了什么吗?
  • 按预期工作,在您的答案中进行了小修改。
  • 我怎样才能将整个文档恢复到组中?我应该在投影中一一添加它们吗?
  • 这通常是$project 的要求,即您指定每个字段。未更改的数组作为示例包含在答案中。您始终可以使用$$ROOT 作为快捷方式,但这会从根本上改变结构。
【解决方案2】:

根据上面的描述查询

db.collection.find({tags.id:"t1"}).sort({tags.score:-1}) 

会过滤掉tag id为1的文档,返回按score降序排列的结果。

还请提供详细说明以及示例文档和预期输出结果

【讨论】:

  • 相当确定 OP 的问题明确指出“排序”仅适用于匹配“t1”的数组中的值。这不会这样做,并且只匹配包含“t1”的文档,并且不会限制排序。至少如果“点表示法”语法在这里是正确的并且不是非法的,它会。但这仍然不是所提问题的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-13
  • 2019-12-03
  • 2019-11-14
  • 1970-01-01
  • 2016-07-09
  • 1970-01-01
相关资源
最近更新 更多