数组中的$ pull对象,另一个数组中的$ pull引用 [英] $pull object from array, and $pull references in another array

查看:76
本文介绍了数组中的$ pull对象,另一个数组中的$ pull引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从众多客户中考虑此文档:

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中删除(拉出)对象(服务),并在同一查询中删除所有 staff对象中的servicesProvided中的可能引用.

例如,如果我使用_id 111删除服务,我也想删除提供此服务的工作人员中对该服务的所有引用.

如何编写此查询.

解决方案

所以这里有点令人讨厌.确实如何更新符合单个文档中条件的多个"数组项?

这里的背景来自位置$ 运算符文档:

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

这说明了故事的一部分",但是这里特定于此问题的要点是不止一个".

因此,即使由于需要执行的操作,嵌套"部分并未明确显示为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
         }
     }
 )

但是.这将无法满足您的期望,因为这些元素不会像您期望的那样从 all 个"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的原因.

Consider this document from a collection of clients:

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
  ]
}

A client can have many staff members. Each staff has a reference to the services he or she provide. If a service is deleted, reference to this service also need to be deleted in all staff.

I want to delete (pull) an object (a service) from services, and in the same query delete possible reference in the servicesProvided in all staff objects`

For example, if I delete service with _id 111, I also want to delete all references to this service in staffmembers that provide this service.

How do i write this query.

解决方案

So this is where things get a little nasty. How indeed do you update "multiple" array items that would match the conditions in a single document?

A bit of background here comes from the positional $ operator documentation:

Nested Arrays The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value

That tells "part" of the story, but the main point here that is specific to this question is "more that one".

So even though the "nested" part is not explicitly true due to what needs to be done, the important factor is "more than one". To demonstrate, lets consider this:

{
  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, ...]
    }
  ]
}

Now you ask to remove the 111 value. This is always the "first" value as provided in your example. So where we can assume this to be the case then the update is "what seems to be: simple:

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

But. That won't do what you expect as the elements will not be pulled from all "staff" array elements as you might expect. In fact, none of them. The only thing that will work is this:

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

But guess what! Only the "first" array element was actually updated. So when you look at the statement above, this is basically what it says will happen.

Then again though, suppose we were just testing this in a modern MongoDB shell with a server of MongoDB 2.6 version or greater. Then this is the response we get:

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

So hang on a moment. We were just told how many documents were "modified" by the last statement. So even though we can only change one element of the array at a time, there is some important feedback here to the condition.

The really great thing about the new "WriteResult" objects obtained from "Bulk Operations API" operations, which in fact this is doing in the shell, is that you actually get told if something was "modified" by the previous statement or not. Way better than the "legacy" write responses, this now gives us a grounding to make some important decisions on looping considerations. Such as "Did our last operation actually 'modify' a document, and then should we continue?"

So this is an important "flow control" point, even if the general MongoDB API itself cannot just "update all elements" all at once. Now there is a testable case to decide where to "continue" in a loop or not. This is what I mean finally by "combining" what you have already learned. So eventually we can come to a listing like this:

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

Or basically something cute like that. So you are asking for the "result" object obtained from the "bulk operations" .execute() to tell you if something was modified or not. Where it still was, then you are "re-iterating" the loop again here and performing the same update and asking for the result again.

Eventually, the update operation will tell you that "nothing" was modified at all. This is when you exit the loop and continue normal operations.

Now an alternate way to handle this might well be to read in the entire object and then make all the modifications that you require:

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

Bit of overkill. Not entirely atomic, but close enough for measure.

So you cannot really do this in a single write operation, at least without dealing with "reading" the document and then "writing" the whole thing back after modifying the content. But you can take and "iterative" approach, and there are the tools around to allow you to control that.

Another possible way to approach this is to change the way you model like this:

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

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

And so on. So then the query becomes something like this:

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

And that truly would be a singular update operation that removes everything in one go because each element is contained in singular arrays.

So there are ways to do it, but how you model really depends on your application data access patterns. Choose the solution that suits you best. This is why you choose MongoDB in the first place.

这篇关于数组中的$ pull对象,另一个数组中的$ pull引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆