skip to Main Content

I am trying to update documents values, if they already exist in a collection, if they have the same value for a specific object key.

For example, I have the following documents –

{ eventType: "A", browsers: [ { name: "Chrome", count: 10 } ]}
{ eventType: "B", browsers: [ { name: "Chrome", count: 2 } ]}
{ eventType: "A", browsers: [ { name: "Chrome", count: 5 }, { name: "Safari", count: 8 } ]}

In the end, I want the collection to merge documents by their event type, and merge browsers aggregations by the browser names value (in the example below, the "Chrome" count is combined to 15); if value does not exist, add the new object to the browsers array (In the example below, adding the "Safari" object to the existing browsers aggregation):

{ eventType: "A", browsers: [ { name: "Chrome", count: 15 }, { name: "Safari", count: 8 } ]}
{ eventType: "B", browsers: [ { name: "Chrome", count: 2 } ]}

I want to use the following query for updating and inserting a new document if needed, but I am not sure how the browser aggregation can be made.

const doc = { eventType: "A", browsers: [ { name: "Chrome", count: 5 }, { name: "Safari", count: 8 } ]}

collection.findOneAndUpdate({
    eventType: doc.eventType
  }, {

    // What to do in here?

  }, {
    upsert: true,
    new: true
  })

Is there any option to use a custom function, such as the $accumulator in the collection.aggregate use case?

P.S – I am using mongoose for this code

2

Answers


  1. Chosen as BEST ANSWER

    Apparently, it is not possible to do such thing in a single atomic query. I 'solved' it by changing the schema, so the keys will be dynamic, e.g.

    {
      browsers: {
        Chrome: {
          count: 15
        }
      }
    }
    

    With this implementation, I build the update section of the findOneAndUpdate dynamically, according to the new document that has to be inserted.


  2. It is not possible to do this in one statement, since your goal is to collect the documents into a new set of documents by eventType, but you can get much of the work done with an aggregation with a merge:

    [{$unwind: {
     path: '$browsers',
     includeArrayIndex: 'b',
     preserveNullAndEmptyArrays: true
    }}, {$group: {
     _id: '$eventType',
     eventType: {
      $first: '$eventType'
     },
     browsers: {
      $addToSet: '$browsers'
     }
    }}, {$addFields: {
     grouped: 1
    }}, {$merge: {
     into: 'so_example',
     on: '_id',
     whenMatched: 'replace',
     whenNotMatched: 'discard'
    }}]
    

    This would require a second process to a) remove documents that weren’t merged and something to reduce the browsers array so that each browser had the sum of the counts.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search