【问题标题】:How do I unset all fields except a known set of fields?如何取消设置除已知字段集之外的所有字段?
【发布时间】:2023-03-11 00:16:02
【问题描述】:

假设我的 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);
});

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

【问题讨论】:

    标签: mongodb mongodb-query


    【解决方案1】:

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

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

    【讨论】:

      【解决方案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);
      

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

      替代方案是实际上 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,因此不会丢失任何更新。而且你不需要另一个集合来做你想做的事。

      【讨论】:

      • 虽然这对单个文档有效并且很好,但如果您需要更新多个文档,则效率很低。
      【解决方案3】:

      实际上最好的方法是遍历光标并使用$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 开始,您可以使用 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(); 
      }
      

      【讨论】:

        【解决方案4】:

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

        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”字段之外的所有内容

        【讨论】:

        • 这很糟糕。你应该这样做。
        • 这是什么?为什么不好?
        • 抱歉,我应该解释一下原因。正如您从我的回答中看到的那样,还有更多更好的方法。首先,如果您的集合中有n 文档,您的解决方案意味着您将访问数据库n 次,这对性能不利。话虽如此,我也没有看到您的解决方案如何解决 OP 的问题。
        • 迭代不是问题的重点,实际上问题是关于一个文档,我的解决方案也是关于一个文档。除了我的解决方案完全解决了问题
        猜你喜欢
        • 2020-11-26
        • 2016-07-01
        • 1970-01-01
        • 2017-03-14
        • 1970-01-01
        • 2019-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多