更新 collection 以更改排名

Update collection to change the rank

我有一个 mongodb collection,我按每个项目的点数排序,它根据它在 collection 中的位置显示排名:

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

Si 我心想:好的,我要更新 collection 中的排名,所以我添加了这个:

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

但我说得太好了,它会更新具有相同等级的每个项目,每次刷新都会改变,这里到底发生了什么?

编辑:我通过这样做设法做到了:

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 }
)

})

试试这个:

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
)
db.collection('websites').updateMany({/*All docs match*/},
    { $set: { rank: doc.rank } }
)

它更新相同排名的原因是您没有过滤器,这意味着它匹配集合中的所有文档并且您有 updateMany

您需要设置一个过滤器来限制要更新的文档。

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

这里的问题是 mongo 使用相同的 doc.rank 值来更新所有符合筛选条件的记录(您的所有记录案件)。现在您有两种选择来解决问题 -

  1. 有效但效率较低) - 这里的想法是您需要计算要更新的每个网站的排名。遍历所有文档和下面的 运行 查询,这将使用计算出的排名更新每个文档。您可能会认为这是低效的,您是对的。我们正在进行大量网络调用以更新记录。更糟糕的是,缓慢是无限的,并且随着记录数量的增加会变得更慢。
db.collection('websites')
  .updateOne( 
    { id: 'docIdThatNeedsToBeUpdated'}, 
    { $set: { rank: 'calculatedRankOfTheWebsite' } } 
  )
  1. 高效选项 - 使用相同的技术计算每个网站的排名并循环生成上面的更新语句。但是这次您不会为所有网站单独进行更新调用。相反,您会使用 批量更新 技术。您将所有更新语句添加到一个批处理中并一次执行它们。
//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();

OP 指出我们要按点对所有文档进行排序,然后按该顺序从 1 到 n 对它们进行“重新排序”并更新数据库。这是一个示例,其中“聚合是新更新”,这要归功于 $mergesame 集合作为输入的强大功能:

db.foo.aggregate([
    // Get everything in descending 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"
    }}
]);

如果使用 $function 吓到了您,您可以使用更迟钝的方法 $reduce 作为有状态的 for 循环替代。为了更好地理解正在发生的事情,请用 /* */ 块注释 $group 下面的阶段,并逐个取消注释每个后续阶段以查看该运算符如何影响管道。

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

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

    // Use $reduce as a for loop with state.                                          
    ,{$addFields: {X: {$reduce: {
        input: '$X',

        // The value (stateful) part of the loop will contain a                       
        // counter n and the array newX which we will rebuild with                    
        // the incremental rank:                                                      
        initialValue: {
            n:0,
            newX:[]
        },
        in: {$let: {
           vars: {qq:{$add:['$$value.n',1]}}, // n = n + 1                       
           in: {
               n: '$$qq',
               newX: {$concatArrays: [
                        '$$value.newX',
                        // A little weird but this means "take the 
                        // current item in the array ($$this) and 
                        // set $$this.rank = $qq by merging it into the 
                        // item.  This results in a new object but 
                        // $concatArrays needs an array so wrap it 
                        // with [ ]":
                        [ {$mergeObjects: ['$$this',{rank:'$$qq'}]} ]
                      ]}
                    }
            }}
        }}
    }}

    ,{$unwind: '$X.newX'}
    ,{$replaceRoot: {newRoot: '$X.newX'}}

    ,{$merge: {
        into: "foo",
        on: [ "_id" ],
        whenMatched: "merge",
        whenNotMatched: "fail"
    }}
]);

我设法做到了:

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 }
  )
})

谢谢大家!