$ pull对象来自数组,$ pull引用另一个数组

时间:2014-10-03 12:34:46

标签: javascript node.js mongodb mongodb-query

从一组客户端考虑此文档:

client: {
  services: [
    {
      _id: 111,
      someField: 'someVal'
    },
    {
      _id: 222,
      someField: 'someVal'
    }
    ... // More services
  ]

  staff: [
    {
      _id: 'aaa',
      someField: 'someVal',
      servicesProvided: [111, 222, 333, ...]
    },
    {
      _id: 'bbb',
      someField: 'someVal',
      servicesProvided: [111, 555, 666, ...]
    },
    {
      _id: 'ccc',
      someField: 'someVal',
      servicesProvided: [111, 888, 999, ...]
    }
    ... // More staff
  ]
}

客户可以有很多员工。每位员工都参考他或她提供的服务。如果删除服务,则还需要在所有员工中删除对此服务的引用。

我想从services删除(拉取)对象(服务),并在同一查询中删除所有 {{1} servicesProvided中的可能引用对象`

例如,如果我删除了staff 111的服务,我还想删除提供此服务的员工中对此服务的所有引用。

如何编写此查询。

1 个答案:

答案 0 :(得分:1)

所以这就是事情变得有点令人讨厌的地方。你究竟如何更新与单个文档中的条件匹配的“多个”数组项?

这里的一些背景来自positional $运营商documentation

  

嵌套数组   位置$运算符不能用于遍历多个数组的查询,例如遍历嵌套在其他数组中的数组的查询,因为$ placeholder的替换是单个值

这说明了故事的“部分”,但这个问题的主要观点是“更多那个”。

因此,即使“嵌套”部分由于需要做什么而未明确true,因此重要的因素是“不止一个”。为了演示,让我们考虑一下:

{
  services: [
    {
      _id: 111,
      someField: 'someVal'
    },
    {
      _id: 222,
      someField: 'someVal'
    }
  ],

  staff: [
    {
      _id: 'aaa',
      someField: 'someVal',
      servicesProvided: [111, 222, 333, ...]
    },
    {
      _id: 'bbb',
      someField: 'someVal',
      servicesProvided: [111, 555, 666, ...]
    },
    {
      _id: 'ccc',
      someField: 'someVal',
      servicesProvided: [111, 888, 999, ...]
    }
  ]
}

现在要求删除111值。这是始终您示例中提供的“第一个”值。所以,我们可以假设这样的情况,那么更新就是“看似简单:

 db.collection.update(
     { 
         "_id": ObjectId("542ea4991cf4ad425615b84f"),
     },
     { 
         "$pull": {
             "services": { "_id": 111 },
             "staff.servicesProvided": 111
         }
     }
 )

但是。这不符合您的预期,因为元素不会像您预期的那样从所有“staff”数组元素中提取。事实上,没有一个。唯一可行的是:

 db.collection.update(
     { 
         "_id": ObjectId("542ea4991cf4ad425615b84f"),
         "staff.servicesProvided": 111
     },
     { 
         "$pull": {
             "services": { "_id": 111 },
             "staff.$.servicesProvided": 111
         }
     }
 )

但猜猜是什么!实际上只更新了“第一个”数组元素。因此,当你看上面的陈述时,这基本上就是它所说的。

然而,假设我们只是在一个带有MongoDB 2.6或更高版本服务器的现代MongoDB shell中测试它。那么这就是我们得到的回应:

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

所以请等一下。我们刚刚被告知最后一个声明“修改”了多少文件。因此,即使我们一次只能更改数组中的一个元素,这里也会有一些重要的反馈。

从“批量操作API”操作获得的新“WriteResult”对象真正的好处,实际上这是在shell中进行的,实际上是你被告知是否某些内容被前一个语句“修改”或不。比“遗留”写入响应更好的方式,现在为我们提供了一个基础,可以对循环考虑做出一些重要的决定。例如“我们的上一次操作实际上'修改'了一个文件,然后我们应该继续吗?”

所以这是一个重要的“流控制”点,即使一般的MongoDB API本身不能只是一次“更新所有元素”。现在有一个可测试的案例来决定在循环中“继续”的位置。这就是我最后通过“结合”你已经学到的东西来表达的意思。所以最终我们可以来这样的列表:

var bulk = db.collection.initializeOrderedBulkOp();
var modified = 1;

async.whilst(
    function() { return modified },
    function(callback) {
        bulk.find(
            { 
                "_id": ObjectId("542ea4991cf4ad425615b84f"),
                "staff.servicesProvided": 111
            }
        ).updateOne(
            { 
                "$pull": {
                     "services": { "_id": 111 },
                     "staff.$.servicesProvided": 111
                }
            }
        );

        bulk.execute(function(err,result) {
            modified = result.nModfified();
            callback(err);
        });
    },
    function(err) {
      // did I throw something! Suppose I should so something about it!
    }
);

或者基本上像这样可爱的东西。因此,您要求从“批量操作”.execute()获取的“结果”对象告诉您是否修改了某些内容。它仍然存在,那么你在这里再次“重新迭代”循环并执行相同的更新并再次询问结果。

最终,更新操作会告诉您“没有”被修改。这是当你退出循环并继续正常操作时。

现在处理这个问题的备用方法可能是读取整个对象,然后进行所需的所有修改:

db.collection.findOne(
    { 
        "_id": ObjectId("542ea4991cf4ad425615b84f"),
        "staff.servicesProvided": 111
    },
    function(err,doc) {
        doc.services = doc.services.filter(function(item) {
            return item._id != 111;
        });

        doc.staff = doc.staff.filter(function(item) {
            item.serviceProvided = item.servicesProvided.filter(function(sub) {
                return sub != 111;
            });
            return item;
        });
       db.collection.save( doc );
    }
);

有点矫枉过正。不完全是原子的,但足够接近测量。

所以你不能在一次写操作中真正做到这一点,至少在没有处理“读取”文档然后在修改内容之后“写”整个事情。但你可以采取“迭代”的方法,并且有一些工具可以让你控制它。

另一种可能的方法是改变你这样建模的方式:

{
  "services": [
    {
      "_id": 111,
      "someField": "someVal"
    },
    {
      "_id": 222,
      "someField": "someVal"
    }
  ],

  "provided": [ 
      { "_id": "aaa", "service": 111 },
      { "_id": "aaa", "service": 222 },
      { "_id": "aaa", "service": 111 }
  ]
}

等等。那么查询就变成这样:

db.collection.update(
    {  "_id": ObjectId("542ea4991cf4ad425615b84f") },
    {
        "$pull": {
            "services": { "_id": 111 },
            "provided": { "_id": 111 }
        }
    }
);

这真的是一个单一的更新操作,可以一次性删除所有内容,因为每个元素都包含在单个数组中。

所以有办法做到这一点,但你的模型实际上取决于你的应用程序数据访问模式。选择最适合您的解决方案。这就是您首先选择MongoDB的原因。