Home > Software design >  Update collection to change the rank
Update collection to change the rank

Time:05-26

i have a mongodb collection that I sort by the amount of points each item has, and it shows a rank according to it's place in the collection :

db.collection('websites').find({}).sort({  "points": -1 }).forEach(doc => {
    
  rank  ;
  doc.rank = rank;
  delete doc._id;
  console.log(doc)

enter image description here

Si I thought to myself : Ok, I'm gonna update the rank in the collection, so I added this :

 db.collection('websites').updateMany({},
    { $set: { rank: doc.rank } }
)

But I was too good to be true, and it updates every single item with the same rank, which changes at each refresh, what exactly is going on, here ?

enter image description here enter image description here

EDIT : I managed to do it by doing this :

rank = 0;

db.collection('websites').find({}).sort({  "points": -1 }).forEach(doc => {

  rank  ;
  doc.rank = rank;
  //delete doc._id;
  console.log(doc._id);

  db.collection('websites').updateMany({_id : doc._id},
    { $set: { rank: doc.rank } },
    { upsert: true }
)

})

CodePudding user response:

Try this:

db.collection('websites')
    .updateOne( //update only one
    {rank: doc.rank}, //update the one where rank is the sent in parameter doc.rank
    { $set: { rank: doc.rank } } // if multiple docs have the same rank you should send in more parameters
)

CodePudding user response:

db.collection('websites').updateMany({/*All docs match*/},
    { $set: { rank: doc.rank } }
)

Reason it updates same rank because you have no filter which means it matches all docs in the collection and you have updateMany

You need to set a filter to restrict docs to be updated.

db.collection('websites').updateMany({id: "someID"},
        { $set: { rank: doc.rank } }
    )

CodePudding user response:

The problem here is that mongo is using the same doc.rank value to update all the records that match the filter criteria (all records in your case). Now you have two options to resolve the issue -

  1. Works but is less efficient) - Idea here is that you need to calculate the rank for each website that you want to update. loop throuh all the document and run below query which will update every document with it's calculated rank. You could probably think that this is inefficient and you would be right. We are making large number of network calls to update the records. Worse part is that the slowness is unbounded and will get slower as number of records increases.
 db.collection('websites')
   .updateOne( 
      { id: 'docIdThatNeedsToBeUpdated'}, 
      { $set: { rank: 'calculatedRankOfTheWebsite' } } 
  )
  1. Efficient option - Use the same technique to calculate the rank for each website and loop through it to generate the update statement as above. But this time you would not make the update calls separately for all the websites. Rather you would use Bulk update technique. You add all your update statement to a batch and execute them all at one go.
 //loop and use below two line to add the statements to a batch.
 var bulk = db.websites.initializeUnorderedBulkOp();
 bulk.find( { id: 'docIdThatNeedsToBeUpdated'} )
     .updateOne( { $set: { rank: 'calculatedRankOfTheWebsite' }  } );

 //execute all of the statement at one go outside of the loop
 bulk.execute();

CodePudding user response:

Here is an example of where "aggregate is the new update" thanks to the power of $merge onto the same collection as the input:

db.foo.aggregate([
    // Get everything in order...                                                     
    {$sort: {'points':1}}

    // ... and turn it into a big array:                                              
    ,{$group: {_id:null, X:{$push: '$$ROOT'}}}

    // Walk the array and incrementally set rank.  The input arg
    // is $X and we set $X so we are overwriting the old X:                                    
    ,{$addFields: {X: {$function: {
        body: function(items) {
            for(var i = 0; i < items.length; i  ) {
                items[i]['rank'] = (i 1);
            }
            return items;
        },
        args: [ '$X' ],
        lang: "js"
        }}
    }}

    // Get us back to regular docs, not an array:                                     
    ,{$unwind: '$X'}
    ,{$replaceRoot: {newRoot: '$X'}}

    // ... and update everything:                                                     
    ,{$merge: {
        into: "foo",
        on: [ "_id" ],
        whenMatched: "merge",
        whenNotMatched: "fail"
    }}
]);

CodePudding user response:

I managed to do it by doing :

rank = 0;

db.collection('websites').find({}).sort({  "points": -1 }).forEach(doc => {

  rank  ;
  doc.rank = rank;
  //delete doc._id;
  console.log(doc._id);

  db.collection('websites').updateMany({_id : doc._id},
    { $set: { rank: doc.rank } },
    { upsert: true }
)

})

Thank you everyone !

  • Related