How do I unset all fields except a known set of fields?

时光总嘲笑我的痴心妄想 提交于 2019-12-10 02:12:03

问题


Suppose I have a single document in my mongo collection that looks like this:

{
    "_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"
    }
}

I want to prune that document to look like this:

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

However, my issue is that I don't know what the "random" field names are. In mongo, how would i $unset all fields except a couple of known fields?

I can think of a couple of ways, but i don't know the syntax.. i could select all field NAMES and then for each one of those unset the field. kind of like this:

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

Another way I was thinking of doing it was to unset where the field name is not in an array of strings that i defined. not sure how to do that either. Any ideas?


回答1:


If you don't care about atomicity then you may do it with 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);

The main problem of this solution is that it's not atomic. So, any update to doc between findOne and save will be lost.

Alternative is to actually unset all unwanted fields instead of saving the 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});

This solution is much better because mongo runs update atomically, so no update will be lost. And you don't need another collection to do what you want.




回答2:


Actually the best way to do this is to iterate over the cursor an use the $unset update operate to remove those fields in subdocuments except the known fields you want to keep. Also you need to use "bulk" operations for maximum efficiency.


MongoDB 3.2 deprecates Bulk() and its associated methods. So if you should use the .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)

From MongoDB 2.6 you can use the Bulk API.

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



回答3:


I solved this with a temporary collection. i did the following:

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();



回答4:


here is my solution, I think easier than the others I read:

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

I'm deleting everything but the fields 'keep_field_1' and 'keep_field_2'



来源:https://stackoverflow.com/questions/19459086/how-do-i-unset-all-fields-except-a-known-set-of-fields

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!