3

I'm trying to write an aggregate query using $unwind no matter the element is an array or not. I know $unwind does not work on no array elements, but I wonder if there is a way to make it work, like converting this element into an array.

I have a collection like this:

{

    {"x" : 1, "y" : {"c" : 2, "i" : 3}},
    {"x" : 1, "y" : [{"c" : 4, "i" : 5}, {"c" : 6, "i" : 7}]}

}

Before I $unwind I think I need something like this:

{

  {"x" : 1, "y" : [{"c" : 2, "i" : 3}]},
  {"x" : 1, "y" : [{"c" : 4, "i" : 5}, {"c" : 6, "i" : 7}]}
}

So far in $project stage I can check if element is array or not, but I don't know how to use this information to make an array or not. I know I can use $push to make an array, but how to leave untouched arrays elements and just $push no array elements?

I tried this:

{$group : {"_id" : "$x", "myArray" : {$push : {$cond : {if : "$isArray", then : "$y", else : ["$y"]}}}}}

With the code above I tried to have all elements in the same level but did not work since ["$y"] returns exactly that (["$y"]), no evaluation, just an array with a string in it.

I don't want to $unwind an empty array, I want to convert a non array element into an array element so I can $unwind.

any help will be appreciated.

2
  • I don't understand your comment about changing the sample into the same thing. The first sample contains array and non array elements for "y", the second one is what I nedd, with all "y" elements as arrays. About the possible duplicate, I think this is not my case, since I don´t have and empty array. I have a non array element that I want to turn into and array so I can $unwind. Commented Jun 16, 2015 at 4:21
  • I agree this is not a good idea, but I didn't write that collection, and change it is not an option now. Commented Jun 16, 2015 at 4:37

1 Answer 1

5

This basically does what you want with some help from $cond and $ifNull:

db.collection.aggregate([
    { "$project": { 
        "x": 1,
        "y": { 
            "$cond": [
                { "$ifNull": [ "$y.0", null] },
                "$y",
                { "$map": {
                    "input": ["A"],
                    "as": "el",
                    "in": "$y"
                }}
            ]
        }
    }}
])

So those first conditions work out "is the element an array" by basically testing for the presence of the "first index" of an array element. Where the condition is true then the existing element is used, i.e The array.

Where that is not true, the element is "transformed" into an array via the $map function and a single "dummy" array element.

The output is just what you want:

{
    "_id" : ObjectId("557f9d9d655c7c61fdcb7909"),
    "x" : 1,
    "y" : [
            {
                    "c" : 2,
                    "i" : 3
            }
    ]
}
{
    "_id" : ObjectId("557f9d9d655c7c61fdcb790a"),
    "x" : 1,
    "y" : [
            {
                    "c" : 4,
                    "i" : 5
            },
            {
                    "c" : 6,
                    "i" : 7
            }
    ]
}

I still would advise where possible that you alter the documents to actually contain an array element in your collection rather than working this into the pipeline. Something like this:

db.collection.find({ "y.0": { "$exists": false } }).forEach(function(doc) {
    db.collection.update(
        { "_id": doc._id },
        { "$set": { "y": [doc.y] } }
    )
})
Sign up to request clarification or add additional context in comments.

2 Comments

This works great, I still have to understand it, but it works great. Thanks a lot. I can not vote up, but I would if I could.
@RaulMartinez You might not yet have the required privilege to "upvote" an answer, but you can accept 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.