Upsert field with aggregation

This is a continuation of this thread but it got locked.

I marked the solution that works with seasons arrays. However, I also got a solution from @Prasad_Saya that works with seasons objects (which is the desired solution)

My collection has objects that are like this:

{
    totalWins: 12,
    seasons: {
        1: {
            wins: 10
        },
        2: {
            wins: 2
        }
    }
}

I want to sum the wins from the seasons objects and put that sum into the field totalWins . However, it is possible that the seasons field might not exist, or it has no objects in itself, in which case totalWins should be 0.

The aggregation I got is:

db.test.aggregate([
  { 
      $project: { 
          seasons: { "$objectToArray": "$seasons" } 
      } 
  },
  { 
      $project: { 
          totalWins: { 
              $reduce: { 
                  input: { $ifNull: [ "$seasons", [] ] }, 
                  initialValue: 0, 
                  in: { $add: [ "$$value", "$$this.v.wins" ] }
              }
          }
      }
  }
])

This returns a collection with totalWins in the objects. However, I want the totalWins to be updated/inserted to the original collection so I did updateMany along with the aggregation. The problem is that doing so causes the original documents to be replaced by the documents returned by the aggregation. As a result, I lose all of the other fields that were in the original documents before, and now my documents only have _id and totalWins. So my question is how do I run the aggregation, take the field totalWins and append it to the existing documents without overwriting them?

You don’t need to $project anything.

Just use a $set: { totalWins: {expression}} and your aggregated document will emerge from the $set stage with an extra, added field.

1 Like

As @Jack_Woehr had mentioned, for the update do use $set. For the query
use $addFields instead of $project in the aggregation - this will ensure that the existing fields will remain in addition to the updated/created fields.

1 Like
db.test.updateMany({}, [
  { 
      $project: { 
          seasons: { "$objectToArray": "$seasons" } 
      } 
  },
  { 
      $set: { 
          totalWins: { 
              $reduce: { 
                  input: { $ifNull: [ "$seasons", [] ] }, 
                  initialValue: 0, 
                  in: { $add: [ "$$value", "$$this.v.wins" ] }
              }
          }
      }
  }
])

Do you mean like this? If so this didn’t work. Fields in the original documents were gone, now the documents only have _id, seasons as an array instead of an object, and totalWins. Am I misunderstanding something here?

Just replace the $project with $addFields in the first stage of the aggregation.

Doing so mutates the seasons field, but the other fields are fine. Instead of ‘seasons’ originally being an object, now it’s an array. I want to preserve to original ‘seasons’ object as well.

This will preserve the seasons:

seasons_array: { "$objectToArray": "$seasons" }

Then, use that in the following reduce operation:

input: { $ifNull: [ "$seasons_array", [] ] }

Doing so results in both seasons and seasons_array in each document. So I ended up adding $unset: seasons_array to remove the field.

Now my question is: is this the best way to approach this? Or is there like another way to sort of declare a local variable to store the temporary array so that it doesn’t get added to the document in the end.

Yes, thats the correct way of doing it.

2 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.