更新 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 值来更新所有符合筛选条件的记录(您的所有记录案件)。现在您有两种选择来解决问题 -
- 有效但效率较低) - 这里的想法是您需要计算要更新的每个网站的排名。遍历所有文档和下面的 运行 查询,这将使用计算出的排名更新每个文档。您可能会认为这是低效的,您是对的。我们正在进行大量网络调用以更新记录。更糟糕的是,缓慢是无限的,并且随着记录数量的增加会变得更慢。
db.collection('websites')
.updateOne(
{ id: 'docIdThatNeedsToBeUpdated'},
{ $set: { rank: 'calculatedRankOfTheWebsite' } }
)
- 高效选项 - 使用相同的技术计算每个网站的排名并循环生成上面的更新语句。但是这次您不会为所有网站单独进行更新调用。相反,您会使用 批量更新 技术。您将所有更新语句添加到一个批处理中并一次执行它们。
//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 对它们进行“重新排序”并更新数据库。这是一个示例,其中“聚合是新更新”,这要归功于 $merge
到 same 集合作为输入的强大功能:
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 }
)
})
谢谢大家!
我有一个 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 值来更新所有符合筛选条件的记录(您的所有记录案件)。现在您有两种选择来解决问题 -
- 有效但效率较低) - 这里的想法是您需要计算要更新的每个网站的排名。遍历所有文档和下面的 运行 查询,这将使用计算出的排名更新每个文档。您可能会认为这是低效的,您是对的。我们正在进行大量网络调用以更新记录。更糟糕的是,缓慢是无限的,并且随着记录数量的增加会变得更慢。
db.collection('websites')
.updateOne(
{ id: 'docIdThatNeedsToBeUpdated'},
{ $set: { rank: 'calculatedRankOfTheWebsite' } }
)
- 高效选项 - 使用相同的技术计算每个网站的排名并循环生成上面的更新语句。但是这次您不会为所有网站单独进行更新调用。相反,您会使用 批量更新 技术。您将所有更新语句添加到一个批处理中并一次执行它们。
//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 对它们进行“重新排序”并更新数据库。这是一个示例,其中“聚合是新更新”,这要归功于 $merge
到 same 集合作为输入的强大功能:
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 }
)
})
谢谢大家!