6

I am trying to group a set of documents after an $unwind operation. My documents look like this:

{ 
    "_id" : ObjectId("5cdb5b5acadf5100019da2f4"), 
    "allowedLocations" : [
        {
            "type" : "country", 
            "value" : "world", 
            "label" : "World"
        }
    ], 
    "disallowedLocations" : [
        {
            "type" : "country", 
            "value" : "CF", 
            "label" : "Central African Republic"
        }, 
        {
            "type" : "country", 
            "value" : "CN", 
            "label" : "China"
        }
    ], 
}

{ 
    "_id" : ObjectId("5cdb5b5acadf5100019da2f4"), 
    "allowedLocations" : [
        {
            "type" : "country", 
            "value" : "US", 
            "label" : "United States of America"
        }
    ], 
    "disallowedLocations" : [
        {
            "type" : "country", 
            "value" : "CA", 
            "label" : "Canada"
        }, 
        {
            "type" : "country", 
            "value" : "MX", 
            "label" : "Mexico"
        }
    ], 
}

I want to group them by _id and then concatenate the allowedLocations and disallowedLocations arrays into one. The group stage in my pipeline looks like this:

{ 
    "$group" : {
        "_id" : "$_id", 
        "allowedLocations" : {
            "$push" : "$allowedLocations"
        }, 
        "disallowedLocations" : {
            "$push" : "disallowedLocations"
        }
    }
}

The problem is, the result I get is not a document with both arrays concatenated, but an array of arrays, each element of the array being the array of each document:

{ 
    "_id" : ObjectId("5cdb5b5acadf5100019da2f4"), 
    "allowedLocations" : [
        [
            {
                "type" : "country", 
                "value" : "US", 
                "label" : "United States of America"
            }
        ],
        [
            {
                "type" : "country", 
                "value" : "world", 
                "label" : "World"
            }
        ],
    ], 
    "disallowedLocations" : [
        [
            {
                "type" : "country", 
                "value" : "CF", 
                "label" : "Central African Republic"
            }, 
            {
                "type" : "country", 
                "value" : "CN", 
                "label" : "China"
            }
        ],
        [
            {
                "type" : "country", 
                "value" : "CA", 
                "label" : "Canada"
            }, 
            {
                "type" : "country", 
                "value" : "MX", 
                "label" : "Mexico"
            }
        ]
    } 
}

Is there a way to produce a flat array with only objects as elements? I also tried with $concatArrays before the push but that creates more arrays inside the arrays.

1
  • 1
    Why don't you just $unwind the disallowedLocations and allowedLocations before executing the $group? Commented Jun 3, 2019 at 22:17

1 Answer 1

14

Two solutions here. You can either run $unwind on both arrays to get single allowed and disallowed location per document and then run your $group stage:

db.col.aggregate([
    {
        $unwind: "$allowedLocations"
    },
    {
        $unwind: "$disallowedLocations"
    },
    { 
        "$group" : {
            "_id" : "$_id", 
            "allowedLocations" : {
                "$addToSet" : "$allowedLocations"
            }, 
            "disallowedLocations" : {
                "$addToSet" : "$disallowedLocations"
            }
        }
    }
])

or you can run your $group first and then use $reduce to flatten allowedLocations and disallowedLocations:

db.col.aggregate([
    { 
        "$group" : {
            "_id" : "$_id", 
            "allowedLocations" : {
                "$push" : "$allowedLocations"
            }, 
            "disallowedLocations" : {
                "$push" : "$disallowedLocations"
            }
        }
    },
    {
        $project: {
            _id: 1,
            allowedLocations: {
                $reduce: {
                    input: "$allowedLocations",
                    initialValue: [],
                    in: { $concatArrays: [ "$$value", "$$this" ] }
                }
            },
            disallowedLocations: {
                $reduce: {
                    input: "$disallowedLocations",
                    initialValue: [],
                    in: { $concatArrays: [ "$$value", "$$this" ] }
                }
            }
        }
    }
])
Sign up to request clarification or add additional context in comments.

2 Comments

the first solution should have $addToSet instead of $push to avoid duplicate entries in array.
@VikashSingh that's true, thanks. Nonetheless I would prefer second one ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.