如果您需要在运行时计算出类似这样的内容,并使用数组中的“过滤”内容确定排序顺序,那么您最好使用.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() 上的文档添加其他属性意味着您可以简单地引用这些属性以便直接对结果进行排序。