【问题标题】:Update all EmbeddedDocuments in EmbeddedDocumentListField if it matches criteria如果符合条件,则更新 EmbeddedDocumentListField 中的所有 EmbeddedDocument
【发布时间】:2019-01-14 23:03:09
【问题描述】:

我将mongoengine 与具有EmbeddedDocumentListField 属性的Document 一起使用。

class Child(mongoengine.EmbeddedDocument):
    value = mongoengine.IntField(required=True)
    child_type = mongoengine.StringField(required=True, choices=["type1", "type2", "type3"], unique_with=["version"])
    version = mongoengine.StringField(required=True, choices=["old", "current", "new"])


class Parent(mongoengine.Document):
    children = mongoengine.EmbeddedDocumentListField(Child)

我正在以这种方式填充我的数据库:

def populate():
    # for each child_type
    for child_type in ["type1", "type2", "type3"]:
        for parent_id, value in compute_stuff(child_type):
            # create a new Child embedded document with version "new" and append it to the corresponding Parent                
            parent = Parent.get(parent_id)
            child = Child(value=value, child_type=child_type, version="new")
            parent.children.append(child)
            parent.save()

        update_versions(child_type)

现在,我正在努力解决的是我的 update_versions 函数。基本上,我想用当前的child_type 和“当前”版本更新每个Child 文档,并将其更改为“旧”版本。之后,通过将版本为“new”的Child 更改为版本“current”来执行相同操作。

这是我迄今为止尝试过的:

def update_versions(child_type):
    # update "current" to "old"        
    Parent.objects(
        children__version="current",
        children__child_type=child_type
    ).update(set__children__S__version="old")
    # update "new" to "current"
    Parent.objects(
        children__version="new",
        children__child_type=child_type
    ).update(set__children__S__version="current")

不幸的是,更新没有正确完成,因为我正在尝试制作的child_type 上的过滤器似乎没有完成。这是我在数据库中得到的结果:

> // 1. before first populating -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [ ]
}
> // 2. after first populating of type1 -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "new"
        }
    ]
}
> // 3. after updating versions -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "current"  // <- this is OK
        }
    ]
}
> // 4. after first populating of type2 -> OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "current"  // <- this is OK
        },
        {
            "value" : 17,
            "child_type": "type2",
            "version": "new"  // <- this is OK
        }
    ]
}
> // 5. after updating versions (only "current" to "old") -> NOT OK
> db.parent.find({"_id": 1}).pretty()
{
    "_id" : 1,
    "children" : [
        {
            "value" : 1,
            "child_type": "type1",
            "version": "old"  // <- this is NOT OK, expecting to stay "current"
        },
        {
            "value" : 17,
            "child_type": "type2",
            "version": "new"  // <- this is OK
        }
    ]
}

我错过了什么?

编辑:这个查询似乎做我想要的,但这是一个原始的 Mongo 查询,我想“翻译它”以将它与 mongoengine 一起使用:

db.parent.updateMany(
    {"children.child_type": "type1", "children.version": "current"},
    {"$set": {"children.$[element].version": "old"}},
    {arrayFilters: [{"element.child_type": "type1", "element.version": "current"}]}
)

注意:我不认为这是重复的,因为我发现的大多数问题都是关于更新特定的 EmbeddedDocument,给定它的 id。在这里,我想更新每个 EmbeddedDocument,而不对父级进行过滤。

【问题讨论】:

    标签: python mongodb mongoengine


    【解决方案1】:

    比我之前建议的更有效的解决方案是通过获取Parent 的链接collection 对象来运行原始查询:

    def update_versions(child_type):
    
        def _update_from_to(current_version, new_version):
            Parent._get_collection().update_many(
                filter={
                    "children.child_type": child_type,
                    "children.version": current_version
                },
                update={
                    "$set": {"children.$[element].version": new_version}
                },
                array_filters=[{
                    "element.child_type": child_type,
                    "element.version": current_version
                }],
                upsert=False
            )
    
        _update_from_to("current", "old")
        _update_from_to("new", "current")
    

    这比一个一个地更新每个Child 实例要快得多! 对应的是我正在使用未记录的_get_collection 方法,尽管它有一天might be made public

    【讨论】:

      【解决方案2】:

      没有找到一种方法让它与单个查询一起工作,所以我通过一个一个地更新每个 Child 实例来做到这一点:

      def update_versions(child_type):
      
          def _update_from_to(current_version, new_version):
              # find all the Parents with a matching Child
              parents_to_update = Parent.objects(
                  children__version=current_version,
                  children__child_type=child_type
              )
              for parent in parents_to_update:
                  # find the matching Child in the children list
                  for child in parent.children:
                      if (child.version == current_version and
                              child.child_type == child_type):
                          # and update it
                          child.version = new_version
                          break
                  # each parent is updated one by one, this is not efficient...
                  parent.save()
      
          _update_from_to("current", "old")
          _update_from_to("new", "current")
      

      编辑:查看我的其他答案以获得更有效(但有点老套)的解决方案

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-17
        相关资源
        最近更新 更多