我有一组我刚刚导入的数据。在每次导入时,我都会将“历史”子文档附加到历史数组中。整体结构是这样的,但有更多的领域:
{ _id: ObjectId('000000000000000001'),
history: [ {date: ISODate("2014-05-25T22:00:00Z"), value: 1},
{date: ISODate("2014-05-26T22:00:00Z"), value: 1},
{date: ISODate("2014-05-26T22:00:00Z"), value: 1}
]
}
问题是,在一些情况下,导入很糟糕,我最终在同一天重复了历史记录。我想删除所有重复项。我尝试使用$pull
更新运算符执行此操作,并将重复调用它,直到每个日期都有适当数量的历史记录条目。问题是,我有超过一百万个数据点,每个数据点都有不同数量的重复数据 - 有些数据点多达12个。有没有办法在没有使用mapReduce的情况下拉除除一个之外的所有东西?我想的是:
db.test.update({'history.date': new Date(2014,4,26)},
{
$pullAll :
{'history': {date: new Date(2014,4,27)}},
$push : {'history' : {}}
}, {multi:true})
答案 0 :(得分:2)
试试这个,效果很好:
db.collection.find().forEach(function(doc) {
db.collection.update(
{ "_id": doc._id },
{ "$set": { "history": [doc.history] } }
);
})
答案 1 :(得分:1)
你提出的建议的问题是你实际上在你的陈述中最终会有冲突的路径,因为这两个操作都在"历史"阵列。所以这些操作实际上并没有按顺序执行#34;正如您可能认为的那样,这会导致冲突,在尝试解析查询时会产生错误。
你基本上也是"擦拭"数组的内容,如果你的符号只是真正的简写而不是打算只是推动"和空对象{}
,然后根据该文档中找到的现有值,确实无法更新文档。
所以最后的方法是循环来做这件事,这真的不是那么糟糕:
db.collection.find().forEach(function(doc) {
db.collection.update(
{ "_id": doc._id },
{ "$set": { "history": [] } }
);
db.collection.update(
{ "_id": doc._id },
{ "$addToSet": { "history": { "$each": doc.history } } }
);
})
当然,如果您拥有MongoDB 2.6或更高版本,您可以在批量操作中实现这一目标,从而提高效率:
var count = 0;
var bulk = db.collection.initializeOrderedBulkOp();
db.collection.find().forEach(function(doc) {
bulk.find({ "_id": doc._id }).update({
"$set": { "history": [] }
});
bulk.find({ "_id": doc._id }).update({
"$addToSet": { "history": { "$each": doc.history } }
});
count++;
if ( count % 500 == 0 ) {
bulk.execute();
bulk = db.collection.initializeOrderedBulkOp();
count = 0;
}
});
if ( count > 0 )
bulk.execute();
因此,对操作组合并发送500或1000组操作,这些操作应安全地遵循BSON 16MB限制,当然您可以根据需要进行调整。虽然每个更新实际上是按顺序执行的,但在此示例中,每500个项目仅对服务器发送一次实际发送/响应。
您也可以考虑使用聚合方法查找包含重复项的文档,以便通过不更新不需要更新的文档来提高效率:
db.collection.aggregate([
{ "$project": {
"_id": "$$ROOT",
"history": 1
}},
{ "$unwind": "$history" },
{ "$group": {
"_id": { "date": "$history.date", "value": "$history.value" },
"orig": { "$first": "_id" }
}},
{ "$group": {
"_id": "$orig._id",
"history": { "$first": "$orig.history" }
}}
]).forEach(function(doc) {
// same as above
甚至可以将其用作跳板来删除重复内容,这样您只需要使用$set
每个循环发送一个更新,方法是删除重复项
var count = 0;
var bulk = db.collection.initializeOrderedBulkOp();
db.collection.aggregate([
{ "$unwind": "$history" },
{ "$group": {
"_id": { "date": "$history.date", "value": "$history.value" },
"orig": { "$first": "_id" }
}},
{ "$group": {
"_id": "$orig._id",
"history": { "$push": "$_id" }
}}
]).forEach(function(doc) {
bulk.find({ "_id": doc._id }).update({
"$set": { "history": doc.history }
});
count++;
if ( count % 500 == 0 ) {
bulk.execute();
bulk = db.collection.initializeOrderedBulkOp();
count = 0;
}
]);
if ( count > 0 )
bulk.execute();
因此,有一些方法可以摆脱那些您可以考虑并适应您需求的重复条目。
答案 2 :(得分:0)
当我想到我可以在mongo shell中分三步执行此操作时,我正准备实现上面提到的一个脚本:
date = new Date(2014,4,26);
temp = 'SOMESPECIALTEMPVALUE'
db.test.update({'history.date': date},
{$set: {
'history.$.date' : temp
}}, {multi:true})
db.test.update({'history.date': temp},
{$pull: {
'history.date' : temp
}}, {multi:true})
db.test.update({'history.date': temp},
{$set: {
'history.$.date' : date
}}, {multi:true})
这是有效的,因为$
仅更新第一个匹配的子文档。使用pull
我然后删除所有剩余的重复项。最后,我将剩余的临时值重置为其原始值。这对我来说效果很好,因为它是一次只有三个主观日期的操作。否则我可能会采用脚本方法。