简体   繁体   中英

MongoDB average of embedded Array as extra field

My documents looks like this:

{
    "_id" : "53ce85eda2579da8b40c1f0f",
    "name" : "Autokino",
    "tags" : [
        "forMen"
    ],
    "ratings" : [
        { "rating" : 5, "uuid" : "..."},
        { "rating" : 4, "uuid" : "..."},
        { "rating" : 4, "uuid" : "..."},
        { "rating" : 1, "uuid" : "..."},
    ]
}

Now I need the average of ratings.rating (here it should be 3.5). My query looks like this:

activities.aggregate([
    { $match: { _id: ObjectID(req.params.id) } },
    { $unwind: '$ratings' },                                                                                                                                                                          
    { $group: {
        _id: '$_id',
        rating: { $avg: '$ratings.rating'},
    }},
]);

It works, but what I get is:

{
  "_id" : "53ce85eda2579da8b40c1f0f",
  "rating" : 3.5
}

and this is what I need to get:

{
    "_id" : "53ce85eda2579da8b40c1f0f",
    "name" : "Autokino",
    "tags" : [
        "forMen"
    ],
    "rating" : 3.5
}

(The original document without ratings array but with rating average)

How can I solve this problem?

Pipeline stages like $group and $project are "absolute" in that only the declared fields are emitted. You need another operator here. $first will do:

activities.aggregate([
    { "$match": { "_id": ObjectID(req.params.id) } },
    { "$unwind": "$ratings" },                                                                                                                                                                          
    { "$group": {
        "_id": "$_id",
        "name": { "$first": "$name" },
        "tags": { "$first": "$tags" },
        "rating": { "$avg": "$ratings.rating" },
    }},
]);

Since $unwind makes many documents out of the array contents de-normalized, you can use $first here to just take the "first" occurrence of the additional field that you are not otherwise aggregating.

If you are worried about lots of fields to declare this way MongoDB 2.6 does offer the $$ROOT variable. I'ts usage and output are likely not what you really want:

activities.aggregate([
    { "$match": { "_id": ObjectID(req.params.id) } },
    { "$project": {
        "_id": "$$ROOT",
        "ratings": 1
    }},
    { "$unwind": "$ratings" },                                                                                                                                                                          
    { "$group": {
        "_id": "$_id",
        "rating": { "$avg": "$ratings.rating" },
    }},
]);

This gives you something like:

{
    "_id" : {
        "_id": "53ce85eda2579da8b40c1f0f",
        "name" : "Autokino",
        "tags" : [
            "forMen"
        ],
        "ratings" : [
            { "rating" : 5, "uuid" : "..."},
            { "rating" : 4, "uuid" : "..."},
            { "rating" : 4, "uuid" : "..."},
            { "rating" : 1, "uuid" : "..."},
        ]
    },
    "rating": 3.5
}

This is okay here since grouping by _id is the same as grouping on the whole document. So you can always add a final $project to return to similar state. But there are no wildcards here.

I just went through this whole song and dance as well and ended up having to re-add all my fields back. Not ideal!

So I just found this - much easier: I have a reviews array field, which has a rating property of 1 - 5

{
      $addFields: { avg: { $avg: '$reviews.rating'}}
},

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM