Question

db.test.aggregate([
    { "$unwind": "$Data" },
    { "$sort" : { "Data.cost" : -1 } },
    { "$group":{  
        "_id":"$Data.name",
        "Data":{ "$push": "$Data" }
    }}
])

I fired above query. It is giving me result as follows:

{
    "result":[{
        "_id" : "abc"
        "Data" : [
            {
                "uid" : "1...A",
                "name" : "abc",
                "city" : "Paris",
                "description" : {
                    "things" : [
                        {
                            "fruit" : {
                                "fruit_name" : "apple",
                                "fruit_rate" : "4 USD"
                            },
                            "flower" : {
                                "flower_name" : "rose",
                                "flower_rate" : "2 USD"
                            }
                        }
                    ]
                },
                "cost" : "6 USD"
            },
            {
                "uid" : "1...B",
                "name" : "abc",
                "city" : "Paris",
                "description" : {
                    "things" : [
                        {
                            "fruit" : {
                                "fruit_name" : "cherry",
                                "fruit_rate" : "3 USD"
                            },
                            "flower" : {
                                "flower_name" : "orchid",
                                "flower_rate" : "2 USD"
                            }
                        }
                    ]
                }, 
                "cost" : "5 USD"
            }
        ]
    }]
}

But I dont want result like this. I want to merge "description" array of both data if "name" is same. like mention below:

{
    "result":[{
        "_id" : "abc"
        "Data" : [
            {
                "uid" : "1...A",
                "name" : "abc",
                "city" : "Paris",
                "description" : {
                    "things" : [
                        {
                            "fruit" : {
                                "fruit_name" : "apple",
                                "fruit_rate" : "4 USD"
                            },
                            "flower" : {
                                "flower_name" : "rose",
                                "flower_rate" : "2 USD"
                            }
                        }
                    ]
                },
                "description" : {
                    "things" : [
                        {
                            "fruit" : {
                                "fruit_name" : "cherry",
                                "fruit_rate" : "3 USD"
                            },
                            "flower" : {
                                "flower_name" : "orchid",
                                "flower_rate" : "2 USD"
                            }
                        }
                    ]
                }, 
                "cost" : "6 USD"   
            }
        ]
    }]
}

Is it possible to get result like this? what changes I have to do in my query?

Thank you.

Was it helpful?

Solution

The way you have structured your desired result is simply not possible. The reason for this is you are basically breaking the principle behind a Hash Table or dictionary/associative array ( whatever term suits you better ) in that you cannot have more than one key value with the same name.

If you want multiple keys of the same name, then those must be contained within an array, which is very much similar to the sort of structure you have and also within your result. And that result doesn't really do anything other than sort the array elements and then group them back into an array.

So giving you a bit of headroom here for that you have simply done a copy and paste to represent your desired result, and that you actually want some form of merging of the inner elements, you can always do something like this:

db.test.aggregate([
    { "$unwind": "$Data" },
    { "$unwind": "$Data.description.things" },
    { "$group": {
        "_id": "$Data.name",
        "city": { "$first": "$Data.city" },
        "things": { "$addToSet": "$Data.description.things" }
    }}
])

Which produces a result:

{
    "_id" : "abc",
    "city" : "Paris",
    "things" : [
        {
            "fruit" : {
                "fruit_name" : "cherry",
                "fruit_rate" : "3 USD"
             },
             "flower" : {
                 "flower_name" : "orchid",
                 "flower_rate" : "2 USD"
             }
        },
        {
             "fruit" : {
                 "fruit_name" : "apple",
                 "fruit_rate" : "4 USD"
             },
             "flower" : {
                 "flower_name" : "rose",
                 "flower_rate" : "2 USD"
             }
        }
    ]
}

So that has the inner "things" now "pushed" together into a singular array while grouping on a common element and adding some additional fields.

If you actually want something with even more "merging" and even possibly avoiding removal of duplicate "set" items, then you could further re-shape with a statement like this:

db.test.aggregate([
    { "$unwind": "$Data" },
    { "$unwind": "$Data.description.things" },
    { "$project": {
         "name": "$Data.name",
         "city": "$Data.city",
         "things": "$Data.description.things",
         "type": { "$literal": [ "flower", "fruit" ] }
    }},
    { "$unwind": "$type" },
    { "$group": {
        "_id": "$name",
        "city": { "$first": "$city" },
        "things": { "$push": { "$cond": [
            { "$eq": [ "$type", "flower" ] },
            { 
                "type": "$type", 
                "name": "$things.flower.flower_name", 
                "rate": "$things.flower.flower_rate"
            },
            { 
                "type": "$type", 
                "name": "$things.fruit.fruit_name", 
                "rate": "$things.fruit.fruit_rate"
            },
        ]}}
    }}
 ])

Which gives a result:

{
    "_id" : "abc",
    "city" : "Paris",
    "things" : [
        {
            "type" : "flower",
            "name" : "rose",
            "rate" : "2 USD"
        },
        {
            "type" : "fruit",
            "name" : "apple",
            "rate" : "4 USD"
        },
        {
            "type" : "flower",
            "name" : "orchid",
            "rate" : "2 USD"
        },
        {
            "type" : "fruit",
            "name" : "cherry",
            "rate" : "3 USD"
        }
    ]
}

Which would possibly even indicate how you original data would be better structured in the first place. Certainly you would need to re-shape like this if you wanted to do something like "Find the total value of 'cherries', or 'flowers' or 'fruit'" or whatever the type.

So the way you structured your result, not possible, your breaking the rules as mentioned. In the forms I have presented, well there are a few ways to do that.

P.S: I am deliberately staying away from your $sort representation as though it "sort of" worked for you in your initial example, do not expect this to work in wider examples as your value is a string and not a number. In short this means that "10 USD" is actually less than "4 USD" as that is how strings are lexically compared. i.e: 4 is greater than 1, which is the order in which the comparison is done.

So change these by splitting up your fields and using a numerical type, as in:

        {
            "type" : "fruit",
            "name" : "cherry",
            "rate" : 3,
            "currency": "USD"
        }

And you even get to filter on "currency" if that is required.

P.P.S: the $literal operator is a construct available for MongoDB 2.6 and upwards. In prior versions where that operator is not available, you can instead code that like this:

         "type": { "$cond": [ 1, [ "flower", "fruit" ], 0 ] }

Which obscurely does that same thing as the returned true value from $cond (or even the false value ) is "literally" declared, so what you put there will actually be produced. In this case, it is a way of adding an "array" to the projection, which is wanted in order to match the "types".

You might find references on the net that use $const for this purpose, but I don't particularly trust that as, while it does exist, it was not intended for this purpose and is hence not officially documented.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top