Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB Aggregation Query- Rename Fields Returned from Within Embedded Documents

I'm currently using aggregate operator to return documents that has an array of embedded (sub) documents. I want to rename the field name for the array and also rename fields names in the embedded documents of the array.

As an example, for projection I want to rename the array from "friends" to "buddies" and I also want to rename fields in the embedded document from "name" to "nickName". Can I do this within an aggregate operation and if so how?

Here's an example of the source document:

[
    {
        _id: ObjectID,
        name: 'Matthew',
        friends: [
            {name: 'Slim', age: '32'},
            {name: 'buba', age: '36'}
        ]
    }
]

Here's what the results should look like:

[
    {
        _id: ObjectID,
        name: 'Matthew',
        buddies: [
            {nickName: 'Chris', age: '32'},
            {nickName: 'Jim', age: '36'}
        ]
    }
]

Thanks for the help in advance.

like image 581
Mike Barlow - BarDev Avatar asked Sep 10 '14 03:09

Mike Barlow - BarDev


2 Answers

There are a couple of approaches to this, but it largely depends on your MongoDB version. More recent versions from 2.6 and upwards support the $map operator which you can use in $project to do what you want:

db.friend.aggregate([
    { "$project": {
        "name": 1,
        "buddies": {
            "$map": {
                "input": "$friends",
                "as": "el",
                "in": {
                    "nickName": "$$el.name",
                    "age": "$$el.age"
                }
            }
        }
    }}
])

In prior versions you would use $unwind to work with the array elements and re-construct via $group:

db.collection.aggregate([
    { "$unwind": "$friends" },
    { "$group": {
        "_id": "$_id",
        "name": { "$first": "$name" },
        "buddies": {
            "$push": {
                "nickName": "$friends.name",
                "age": "$friends.age"
            }
        }
    }}
])

With the first form being a little more efficient as you are not de-normalizing the array content and producing more documents in the pipeline to process.

like image 147
Neil Lunn Avatar answered Sep 23 '22 03:09

Neil Lunn


I think I figured it out the following aggregate query. I would be interested in if there is a better way of doing this.

db.friends.aggregate([
    { $unwind: '$friends'},
    { $project: {
        _id: 1,
        name: 1,
        buddy: {
            nickName: '$friends.name',
            age: '$friends.age',
        }
    }},
    { $group: {        
        _id: '$_id',
        name: {'$first': '$name'},
        buddies: {"$push": "$buddy"}
    }}
]);

Below I describe the response of each operator ($unwind, $project, $group)

The $unwind would return something like this. It breaks out each Embedded Document into its own document. So where we had 1 document with 2 friends sub document, now we have two document. Both documents will have the same id and name fields but the friends sub document will be different:

{
    "_id" : ObjectId("539c80d43cb9fe99d183a5f7"),
    "name" : "Matthew",
    "friends" : {"name" : "slim", "age" : "32"}
}, {
    "_id" : : ObjectId("539c80d43cb9fe99d183a5f7"),,
    "name" : "Matthew",
    "friends" : {"name" : "buba", "age" : "36"}
}

The $project would return something like this. Here I'm creating a new Embedded Document called buddy and notice that I assigning "$friends.name" to "nickName":

{
    "_id" : ObjectId("539c80d43cb9fe99d183a5f7"),
    "name" : "Matthew",
    "buddy" : {"nickName" : "slim","age" : "32"}
}, {
    "_id" : : ObjectId("539c80d43cb9fe99d183a5f7"),,
    "name" : "Matthew",
    "friends" : {"nickName" : "buba","age" : "36"}
}

And then the $group would do something like this. Here I'm just group everything back together, but setting the array name to "buddies":

{
    "_id" : ObjectId("539c80d43cb9fe99d183a5f7"),
    "name" : "Matthew",
    "buddies" : [
        {"nickName" : "slim","age" : "32"},
        {"nickName" : "buba","age" : "36"}
    ]
}

This seems to work, but if the friends array had duplicates, this process would remove the duplicates. This can be solved if there is a unique id for each friend.

like image 42
Mike Barlow - BarDev Avatar answered Sep 20 '22 03:09

Mike Barlow - BarDev