Mongodb:在嵌入文档中插入其他字段

时间:2015-10-08 10:21:47

标签: mongodb mongodb-query

注意:不是常见的upsert案例。

Present Document example:
{
   _id  : 123
   array:[
              {
                   id   : a
                   col1 : val1
                   col2 : val2  
              },
              {
                   id   : b
                   col1 : val1
                   col2 : val2  
              }
         ]
}


Update Document Sample:
{
     id   : a
     col1 : val1
     col2 : val2
     col3 : val3
}
  1. 如果文档不存在,请创建新文档
  2. 如果数组不存在,请创建数组并添加文档
  3. 如果存在数组但没有给定id的元素,则将文档添加为数组元素
  4. 如果给定id的元素出现在数组中,而新文档有一个额外字段,则添加该字段
  5. 如果给定id的元素存在于数组中,但新文档中没有其他字段(旧文档可能有比新文档多的字段),则不执行任何操作。
  6. 我无法弄清楚如何做到这一点。 我使用更新查询:

    {
         _id      : 123
         array.id : a
    }
    

    这应该照顾我认为的前3个点(更新选项有upsert:true)

    如何确保仅附加其他元素。由于文档不完全相同,Upsert会复制。由于我想索引这些id,因此操作会因重复而失败。

1 个答案:

答案 0 :(得分:1)

这里的基本情况是“upserts”和“添加到数组”在组合中作为单个语句不能很好地混合。您可以使用$addToSet进行非常“基本”的处理,但这通常不适合这种情况,特别是当您想要“更新”相关数组的内容时。

所以这总是分解为“多个”更新语句,以便获得最终所需的数据状态。 “多个”更新的最佳案例处理是使用Bulk Operations API,因为所有操作都可以在“单个”请求中以“单个”响应发送。

这使得处理方面的效率更高,因为在尝试下一个操作之前,您不需要等待来自服务器的每个操作的响应。它只需要仔细制作操作,以便在添加项目时它们之间不会发生冲突。

假设您“实际上”收到了这样的更新数据包:

{
     "_id"  : 123,
     "id"   : "a",
     "col1" : "val1",
     "col2" : "val2",
     "col3" : "val3"
}

然后你的操作编码如下:

var bulk = db.collection.initializeOrderBulkOp();

// Try to set fields in matched array where found
bulk.find({ "_id": 123, "array.id": "a" }).updateOne({
    "$set": { 
        "array.$.col1": "val1",
        "array.$.col2": "val2",
        "array.$.col3": "val3"
    }
});

// Try to "push" fields to new array where array not found ( no upsert )
bulk.find({ "_id": 123, "array.id": { "$ne": "a" } }).updateOne({
    "$push": {
        "array": {
            "id": "a",
            "col1": "val1",
            "col2": "val2",
            "col3": "val3"
        }
    }
});

// Try to "upsert" where the basic document is not found.
// Only modify on "insert" via $setOnInsert
bulk.find({ "_id": 123 }).upsert().updateOne({
    "$setOnInsert": {
        "array": [
            {
                "id": "a",
                "col1": "val1",
                "col2": "val2",
                "col3": "val3"
            }
        ]
    }
});

// Actually send and execute the operations
bulk.execute();

这通过实现以下逻辑来解决问题:

  1. 查找当前数组并相应地更新。这些是"positional"更新地址第3点和第5点。

  2. 如果匹配的文档中不存在该数组,则“推送”新的数组条目。所以$ne意味着数组不存在。请注意,这是 upsert尝试,故意这样做。地址分别为第2点和第4点。

  3. 如果文档根本不存在,则添加带有全新数组的新文档。地址1,实际上也是2。

  4. 这是多次更新,但它仍然只是“一个”请求和对数据库服务器的响应,所以这是一件好事。 MongoDB甚至不会尝试“替换”已经存在的具有相同值的值,因此不必担心这一点,而不是真的很重要。

    我还建议您重新构建输入数据包,以更好地反映您想要做的事情:

    {
         "_id"  : 123,
         "array": [{
             "id"   : "a",
             "col1" : "val1",
             "col2" : "val2",
             "col3" : "val3"
         }]
    }
    

    因为以编程方式使用它来构造执行更新所需的语句要容易得多,因为结构已经存在以供引用。

    如果您尝试将“upsert”与这些操作“混合”,那么所需的测试不可避免地会导致创建新文档,而您并不打算这样做。因此,您需要在要执行的更新类型中进行多项操作。

    正是出于这个原因,为什么“upsert”执行“last”,并始终使用$setOnInsert修饰符,以便任何“匹配”文档实际上都不会被修改,除非结果实际上是“upsert” “,创建一个全新的文档。同样,在单个语句中将其他更新修饰符与此操作混合是不明智的。

    这是“Bulk API”存在的一个很好的理由,因此像这样的“链式”逻辑可以放入单个服务器请求中,而不是多个请求。