mongodb aggregation, is it possible to add entries to an array during $project
Asked Answered
S

2

7

I have documents of the form:

{
    _id : ObjectId(.....),
    prop1 : "foo",
    links : [ 1, 2, 3, 4 ]
}

{
    _id : ObjectId(.....),
    prop1 : "bar",
    links : [ 5, 6, 7, 8 ]
}

I am using the aggregation framework to process these documents, I use $unwind to generate a document for each value in the links array.

But I have three cases where I need to update the documents before calling $unwind, I have been looking at the $project operation, but I can find no information about how to create or update arrays for the following cases.

1) The links property is missing

{
    _id : ObjectId(.....),
    prop1 : "far"
}

I need to insert the links array

2) The links array property is an empty array

{
    _id : ObjectId(.....),
    prop1 : "far",
    links : []
}

I need to insert a value into the array

3) The links array has too few values

{
    _id : ObjectId(.....),
    prop1 : "far",
    links : [ 9, 10 ]
}

I need to insert additional values into the array

Scrim answered 11/12, 2013 at 11:19 Comment(4)
I'd look at $cond to conditionally add the values: docs.mongodb.org/manual/reference/operator/aggregation/condKassiekassity
I am familiar with $cond, The problem I have is that I can't figure out how to actually create arrays or update array values. I just updated the question to better reflect this.Scrim
You can't actually modify the documents permanently. It would only be during the pipeline that you could modify a field's value.Kassiekassity
Yes, that's exactly what I want, I want to modify them for the scope of the aggregation pipeline only. The values are query dependent so can't be persisted to the documents in the DB.Scrim
K
2

You should be able to use $ifNull (reference):

db.test.aggregate({ $project : { the_links: { $ifNull : ["$links" , [5,6]]}} })

It's simple logic that if the referenced field ($links) is null, the replacement value (in this case [5, 6]) is used. I renamed the field to the_links in the example.

Assuming the links field is null (and not an empty array). Given data like:

{ "_id" : ObjectId(...), "prop1" : "foo", "links" : [  1,  2,  3,  4 ] }
{ "_id" : ObjectId(...), "prop1" : "bar" }

The aggregation above produces:

{
    "result" : [
            {
                    "_id" : ObjectId("52a869d51d02442354276cff"),
                    "the_links" : [
                            1,
                            2,
                            3,
                            4
                    ]
            },
            {
                    "_id" : ObjectId("52a869e31d02442354276d00"),
                    "the_links" : [
                            5,
                            6
                    ]
            }
    ],
    "ok" : 1
}

If links were an empty array [] rather than null, you could do something like:

db.test.aggregate({ $project : 
     { the_links: { $cond : [ { $eq : ["$links", []]}, '$links', [5,6]]}} })

But, if it's either null or [], then you'd need to add an additional check for that condition as an $or within the $cond operator.

If the list has values, and you want to add more values conditionally, the current (2.4.x) production build of MongoDB does not have an effective solution. The development branch has an operator called $size which will return the length of an array (jira). You could then conditionally add them using yet another development feature called $setUnion:

$setUnion Takes any number of arrays and returns an array that containing the elements that appear in any input array.

Kassiekassity answered 11/12, 2013 at 13:42 Comment(6)
That looks like it will work for cases 1 and 2, do have any thoughts on case 3, where I need to add values to an existing array. I have been looking at the changes for mongo 2.6 and there is a $map function that can be applied to each entry in an array but it does not look like it will let me insert values into the array.Scrim
actually just seen the new set operators in 2.6, $setUnion will take any number of arrays and generate the union of them. I might be able to use $project to add the new values into a new array property and then $setUnion to merge them, or maybe even use a literal array as one of the values. I'll have to download 2.6 and give it a try.Scrim
Could you explain what you are trying to do? Why are you trying to add values to the array? I added a few details about future version support.Kassiekassity
I think maybe you have the parameters of the $cond operator in the wrong order. From documentation: { $cond: [ <boolean-expression>, <true-case>, <false-case> ] } So in your example, it will keep the empty array, or replace an non-empty array with the hard coded one...Gaylordgaylussac
@Gaylordgaylussac - It's a two year old question, and from the looks of it, I would have said that I actually used the MongoDB console to test and produce the output you see in the answer.Kassiekassity
Yes, i did notice the age. Anyway, i had to flip the cases around before it worked today - but then it worked, so either they changed the order in the last two years or something else is going on....Gaylordgaylussac
W
0

You can use combination of $ifNull and $concatArrays as following:

{$project: {links: {
    $ifNull : [
          {$concatArrays: ['$links', [5, 6]]},    // if valued
          [1,2]                                   // if missing or null
    ]}
}}

$ifNull determines whether links is missing or not.
By using $concatArrays you can add array of new values to existing links field.

Wanderjahr answered 31/7, 2024 at 15:55 Comment(2)
How is this different from the accepted answer? The usage of $ifNull is already mentioned.Neoclassic
The accepted answer resolved the problem with 2 separated parts while my answer has compacted everything to a single solution. Both solutions are correct but I think my solution is more simple.Wanderjahr

© 2022 - 2025 — McMap. All rights reserved.