14

假设我的 mongo 集合中有一个文档,如下所示:

{
    "_id": 123,
    "field_to_prune": 
    {
        "keep_field_1": "some value",
        "random_field_1": "some value",
        "keep_field_2": "some value",
        "random_field_2": "some value",
        "random_field_3": "some value"
    }
}

我想将该文档修剪为如下所示:

{
    "_id": 123,
    "field_to_prune": 
    {
        "keep_field_1": "some value",
        "keep_field_2": "some value"
    }
}

但是,我的问题是我不知道“随机”字段名称是什么。在 mongo 中,我如何 $unset 除了几个已知字段之外的所有字段?

我可以想到几种方法,但我不知道语法.. 我可以选择所有字段名称,然后为每个未设置字段的字段选择。有点像这样:

[Some query to find all field names under "field_to_prune" for id 123].forEach(function(i) { 
    var key = "field_to_prune." + i;
    print("removing field: " + key);
    var mod = {"$unset": {}};
    mod["$unset"][key] = "";

    db.myCollection.update({ _id: "123" }, mod);
});

我正在考虑的另一种方法是取消设置字段名称不在我定义的字符串数组中的位置。也不知道该怎么做。有任何想法吗?

4

5 回答 5

2

如果你不关心原子性,那么你可以这样做save

doc = db.myCollection.findOne({"_id": 123});
for (k in doc.field_to_prune) {
  if (k === 'keep_field_1') continue;
  if (k === 'keep_field_2') continue;
  delete doc.field_to_prune[k];
}
db.myCollection.save(doc);

这个解决方案的主要问题是它不是原子的。因此,任何在和doc之间的更新都将丢失。findOnesave

替代方法是实际上unset所有不需要的字段,而不是保存doc

doc = db.myCollection.findOne({"_id": 123});
unset = {};
for (k in doc.field_to_prune) {
  if (k === 'keep_field_1') continue;
  if (k === 'keep_field_2') continue;
  unset['field_to_prune.'+k] = 1;
}
db.myCollection.update({_id: doc._id}, {$unset: unset});

这个解决方案要好得多,因为 mongo 以update原子方式运行,因此不会丢失任何更新。你不需要另一个集合来做你想做的事。

于 2013-10-18T22:16:30.643 回答
2

实际上,最好的方法是遍历游标并使用$unset更新操作来删除子文档中的那些字段,但要保留的已知字段除外。您还需要使用“批量”操作以获得最大效率。


MongoDB 3.2 弃用Bulk()及其相关方法。所以如果你应该使用.bulkWrite()

var count = 0;
var wantedField = ["keep_field_1", "keep_field_2"]; 


var requests = [];
var count = 0;
db.myCollection.find().forEach(function(document) { 
    var fieldToPrune = document.field_to_prune; 
    var unsetOp = {};
    for (var key in fieldToPrune) {     
        if ((wantedFields.indexOf(key) === -1) && Object.prototype.hasOwnProperty.call(fieldToPrune, key ) ) {
            unsetOp["field_to_prune."+key] = " ";        
        }
    }
    requests.push({ 
        "updateOne": { 
            "filter": { "_id": document._id }, 
            "update": { "$unset": unsetOp } 
         }
    });         
    count++;    
    if (count % 1000 === 0) {   
        // Execute per 1000 operations and re-init  
        db.myCollection.bulkWrite(requests); 
        requests = []; 
    } 
})

// Clean up queues
db.myCollection.bulkWrite(requests)

从 MongoDB 2.6 开始,您可以使用BulkAPI。

var bulk =  db.myCollection.initializeUnorderedBulkOp();
var count = 0;


db.myCollection.find().forEach(function(document) { 
    fieldToPrune = document.field_to_prune; 
    var unsetOp = {}; 
    for (var key in fieldToPrune) {     
        if ((wantedFields.indexOf(key) === -1) && Object.prototype.hasOwnProperty.call(fieldToPrune, key ) ) {  
            unsetOp["field_to_prune."+key] = " ";             
        } 
    } 
    bulk.find({ "_id": document._id }).updateOne( { "$unset": unsetOp } );         
    count++; 
    if (count % 1000 === 0) {
        // Execute per 1000 operations and re-init     
        bulk.execute();     
        bulk =  db.myCollection.initializeUnorderedBulkOp(); 
    } 
})

// Clean up queues
if (count > 0) { 
    bulk.execute(); 
}
于 2016-03-15T18:12:14.210 回答
0

我用一个临时集合解决了这个问题。我做了以下事情:

db.myCollection.find({"_id": "123"}).forEach(function(i) {
    db.temp.insert(i);
});

db.myCollection.update(
    {_id: "123"}, 
    { $unset: { "field_to_prune": ""}}
)

db.temp.find().forEach(function(i) {
    var key1 = "field_to_prune.keep_field_1";
    var key2 = "field_to_prune.keep_field_2";
    var mod = {"$set": {}};
    mod["$set"][key1] = i.field_to_prune.keep_field_1;
    mod["$set"][key2] = i.field_to_prune.keep_field_2;

    db.myCollection.update({_id: "123"}, mod)
});

db.getCollection("temp").drop();
于 2013-10-18T21:30:04.320 回答
0

不幸的是,目前所有的解决方案都依赖于脚本执行和某种forEach调用,最终一次只能处理一个文档。如果要规范化的集合很大,这将是不切实际的并且需要太长时间。

此外,传递给的函数forEach也在客户端上执行,这意味着如果与数据库的连接丢失,操作将在进程中间中断,可能会使集合处于不一致状态。

可以通过使用像@styvane在这里提出的批量操作来缓解性能问题。这是中肯的建议。

但我们可以做得更好。更新操作支持自 MongoDB 4.2 以来的聚合管道语法,允许通过简单地创建一个仅包含所需字段的新临时对象来实现数据规范化操作,取消设置旧对象,然后将临时对象放回原位,所有这些都使用文档的当前值作为参考:

db.theCollection.updateMany(
  {field_to_prune: {$exists: true}},
  [
    {$set: {_temp: {
      keep_field_1: '$field_to_prune.keep_field_1',
      keep_field_2: '$field_to_prune.keep_field_2'
    }}},
    {$unset: 'field_to_prune'},
    {$set: {field_to_prune: '$_temp'}},
    {$unset: '_temp'}
  ]
)

例子:

> db.myColl.insertOne({
...   _id: 123,
...   field_to_prune: {
...     keep_field_1: "some value",
...     random_field_1: "some value",
...     keep_field_2: "some value",
...     random_field_2: "some value",
...     random_field_3: "some value"
...   }
... })
{ "acknowledged" : true, "insertedId" : 123 }
>
> db.myColl.insertOne({
...   _id: 234,
...   field_to_prune: {
...     // keep_field_1 is absent
...     random_field_1: "some value",
...     keep_field_2: "some value",
...     random_field_2: "some value",
...     random_field_3: "some value"
...   }
... })
{ "acknowledged" : true, "insertedId" : 234 }
>
> db.myColl.find()
{ "_id" : 123, "field_to_prune" : { "keep_field_1" : "some value", "random_field_1" : "some value", "keep_field_2" : "some value", "random_field_2" : "some value", "random_field_3" : "some value" } }
{ "_id" : 234, "field_to_prune" : { "random_field_1" : "some value", "keep_field_2" : "some value", "random_field_2" : "some value", "random_field_3" : "some value" } }
>
> db.myColl.updateMany(
...  {field_to_prune: {$exists: true}},
...  [
...    {$set: {_temp: {
...      keep_field_1: '$field_to_prune.keep_field_1',
...      keep_field_2: '$field_to_prune.keep_field_2'
...    }}},
...    {$unset: 'field_to_prune'},
...    {$set: {field_to_prune: '$_temp'}},
...    {$unset: '_temp'}
...  ]
...)
{ "acknowledged" : true, "matchedCount" : 2, "modifiedCount" : 2 }
>
> db.myColl.find()
{ "_id" : 123, "field_to_prune" : { "keep_field_1" : "some value", "keep_field_2" : "some value" } }
{ "_id" : 234, "field_to_prune" : { "keep_field_2" : "some value" } }

于 2022-03-01T11:38:21.740 回答
-1

这是我的解决方案,我认为比我阅读的其他解决方案更容易:

db.labels.find({"_id" : ObjectId("123")}).snapshot().forEach(
function (elem) {
db.labels.update({_id: elem._id},
{'field_to_prune.keep_field_1': elem.field_to_prune.keep_field_1, 
 'field_to_prune.keep_field_2': elem.field_to_prune.keep_field_2});
});

我正在删除除“keep_field_1”和“keep_field_2”字段之外的所有内容

于 2016-04-06T14:50:02.300 回答