按Mongodb中的嵌入式对象值排序 [英] sort by embedded object value in Mongodb

查看:104
本文介绍了按Mongodb中的嵌入式对象值排序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个这样的架构:

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

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

有什么建议吗?

解决方案

如果您需要在运行时计算出类似的结果,并使用数组中的已过滤"内容确定排序顺序,那么最好使用 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 阶段实际上适用于 $sort 在确定的值上订购文件.当然,如果您想以其他方式使用它,请使用 $min 并按升序排序.

当然要在开始处添加一个 $match 阶段如果您只想要真正包含标签中id的"t1"值的文档.但这部分与您要获得的过滤结果的排序无关.

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

 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()上向文档添加其他属性意味着您可以简单地引用这些属性以直接对结果进行排序.

I have a schema like this:

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

I want to sort the query on tag.id search to get sorted by the corresponding score. So if I search db.collection.find({tags.id:"t1"}).sort({tags.score:-1}) , it sorted by score of the "t1" object not other tags.

Any suggestion?

解决方案

If you need to calculate out something like this at runtime, with "filtered" content from the array determining the sort order, then you best do something with .aggregate() to reshape and determine a sort value like this:

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 } }
])

Where the first part of the pipeline is used to "pre-filter" the array content ( as well as keeping the original field ) to just those values of "score" where the id is equal to "t1". This is done by processing $map which applies a condition to each element via $cond to determine whether to return the "score" for that element or false.

The $setDifference operation does a comparison to a single element array [false] which effectively removes any false values returned from the $map. As a "set", this also removes duplicate entries, but for the sort purpose here this is a good thing.

With the array reduced and reshaped to values you process $unwind ready for the next stage to deal with the values as individual elements. The $group stage essentially applies $max on the "score" to return the highest value contained in the filtered results.

Then it is just a matter of applying the $sort on the determined value to order the documents. Naturaly if you wanted this the other way around then use $min and sort in ascending order instead.

Of course add a $match stage to the beginning if all you really want is documents that actually contain "t1" values for id within the tags. But that part is of least relevance to the sorting on filtered results you want to achieve.

The alternate to calculating is to do it all as you write entries to the array in the documents. Kind of messy, but it goes something like this:

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

Here the $max update operator only sets the value for the specified field if the new value is greater than the existing value or otherwise no property yet exists. The reverse case is true of $min, where only if less than it will be replaced with the new value.

This would of course have the effect of adding various additional properties to the documents, but the end result is sorting is greatly simplified:

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

And it's going to run a lot faster than calculating with an aggregation pipeline.

So consider the design principles. Structured data in arrays where you want filtered and paired results for sorting means calculating at run-time to determine which value to sort on. Adding additional properties to the document on .update() means you can simply reference those properties in order to directly sort results.

这篇关于按Mongodb中的嵌入式对象值排序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆