【问题标题】:Upsert Array Elements matching criteria in a MongoDB document?在 MongoDB 文档中插入匹配条件的数组元素?
【发布时间】:2012-01-15 16:40:26
【问题描述】:

根据How do I update Array Elements matching criteria in a MongoDB document?

我想更新数组元素,所以如果一个不匹配就插入它,否则更新它。

我尝试了该问题的答案,如果数组元素已经存在,它可以正常工作。如果元素不存在,则在数组字段下创建一个“$”的子元素。

我的Mongo结构如下:

Widget (collection)
--Name
--Properties (array)
  --Name
  --Value

我的应用程序从 WebService 调用中获取一个小部件名称和一个属性列表。如果名称已经存在,我希望迭代提供的属性并更新 MongoDB 中的值,或者如果不存在,则将新属性插入到 Properties 数组中。

【问题讨论】:

    标签: arrays mongodb upsert


    【解决方案1】:

    如果没有一些应用端逻辑,使用单个更新是不可能实现您所需的。请注意,作为一项功能的 upsert 与此特定问题无关,除非您希望自动创建新的 Widget 文档(如果没有提供的名称)。

    您遇到的问题是没有允许您根据数组元素的存在进行两次不同更新的功能。您仅有的两个选择是:

    1. 找到该项目,确定相关属性的存在,使用您的新属性或更改属性编译适当的更新并执行它。这带来了一个重要的缺点,即这不是一种并发安全的方法。换句话说,如果两个 Web 服务同时尝试这样做,则可能会覆盖彼此的更改。
    2. 使小部件属性成为顶级文档,而不是嵌入的。允许你使用 upserts 做你想做的事。明显的缺点是,就模式设计而言,这不是一个很好的选择。例如,如果您获取一个小部件,您将不会自动获取所有属性。

    【讨论】:

    • 我也得出了这个结论。每个小部件我有大约 3500 个属性,并且可能超过 100,000 个小部件。将这些属性放在自己的集合中是一个明智的设计吗?
    • “Upserts”是可能的,如果你可以重组你的文档。看我的回答。
    【解决方案2】:

    您不能原子地插入数组元素。但是,如果您可以重组文档以使用对象而不是数组,那么这在原子上是可能的。使用您的符号,结构将是

    Widget (collection)
      --Name
      --Properties
        --Property1
        --Property2
            .
            :
    

    一个示例文档是

    {
        name: 'widget-1',
        properties: {
            'property-1': 100,
            'property-2': 200
        }
    }
    

    要插入一个名为“property-3”且值为 300 的项目,您可以这样做

    db.widgets.update(
        { name: 'widget-1' }, 
        { $set: { 'properties.property-3': 300 } }
    )
    

    一个缺点是查询字段名称(使用$exists 查询运算符)需要扫描。您可以通过添加一个存储属性名称的额外数组字段来解决此问题。该字段可以正常被索引和查询。 Upserts变成了

    db.widgets.update(
        { name: 'widget-1' }, 
        { 
            $set: { 'properties.property-3': 300 },
            $addToSet: { propertyNames: 'property-3' }
        }
    )
    

    (请记住 $addToSet 是 O(n)。)删除属性时,您还必须将其从数组中拉出。

    db.widgets.update(
        { name: 'widget-1' }, 
        { 
            $unset: { 'properties.property-3': true },
            $pull: { propertyNames: 'property-3' }
        }
    )
    

    【讨论】:

    • 不过,这并非没有缺点。例如,您不能索引元素中的字段。此外,还有各种工具和类型系统不能很好地支持这个概念。例如,如果你想将此类型映射到 GraphQL,你会很不走运,它不支持对象中的动态键。
    • 是的。如果您绝对需要原子 upserts 并使用 GraphQL 公开集合,则需要将对象结构转换为数组(例如,通过使用自定义解析器使用 Apollo)。
    • 据我了解,从 mongodb 4.2 开始,您还可以使用管道进行原子更新以将其插入到数组中。
    【解决方案3】:

    至少在 php 中它适用于我,尝试采用您的语言。 你需要你的收藏看起来像:

            {Name: x, Properties: {name1:value1, name2:value2, name3:value3}}
    
            foreach ($Properties as $name => $value)
            {
                $propertiesArray["Properties.$name"] = $value;                
            }   
    
            $criteria  = array('Name' => $name);
            $operation = array('$set'   => $propertiesArray);
            $options   = array('upsert' => true);
    
            $collection = $this->_dbh->selectCollection('Widget');
            $collection->update($criteria, $operation, $options);
    

    如果不存在则插入新名称并更新已经存在的名称

    【讨论】:

    • 这是正确的,但您并没有真正解释为什么会这样。提问者需要重组他们的文档以使用对象而不是数组。有关更多详细信息,请参阅我的答案。 (此外,您使用 upsert 标志将 upsert 整个文档,但问题是专门关于数组元素的。)
    猜你喜欢
    • 2011-12-19
    • 2015-11-04
    • 1970-01-01
    • 2017-05-18
    • 2017-04-23
    • 2018-10-23
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多