Upsert Элементы массива, соответствующие критериям в документе MongoDB?

В соответствии с Как обновить элементы массива соответствие критериям в документе MongoDB?

Я хочу обновить элементы массива, поэтому, если они не совпадают, вставьте их, иначе обновите.

Я попробовал ответить на этот вопрос, и он отлично работает, если элемент массива уже существует. Если элемент не существует, он создает дочерний элемент «$» в поле массива.

Моя структура Mongo выглядит следующим образом:

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

Мое приложение получает имя виджета и список свойств из вызова WebService. Я хочу повторить предоставленные свойства и обновить значение в MongoDB, если имя уже существует, ИЛИ вставить новое свойство в массив свойств, если его нет.


person justacodemonkey    schedule 15.01.2012    source источник


Ответы (3)


То, что вам нужно, невозможно с помощью одного обновления без некоторой логики на стороне приложения. Обратите внимание, что upsert как функция не имеет отношения к этой конкретной проблеме, если только вы не хотите автоматически создавать новые документы Widget, если их не существует с указанным именем.

Проблема, с которой вы сталкиваетесь, заключается в том, что нет функций, позволяющих выполнять два разных обновления в зависимости от существования элемента массива. Ваши единственные два варианта:

  1. Найдите элемент, определите наличие соответствующих свойств, скомпилируйте соответствующее обновление с вашими новыми или измененными свойствами и выполните его. Это связано с важным недостатком, заключающимся в том, что это не безопасный параллелизм. Другими словами, если две веб-службы попытаются сделать это одновременно, одна из них может перезаписать изменения друг друга.
  2. Сделайте свойства виджета документами верхнего уровня, а не встроенными. Позволяет вам использовать upserts, чтобы делать то, что вы хотите. Очевидным недостатком является то, что это не очень хороший вариант с точки зрения дизайна схемы. Вы не получите автоматически все свойства, если, например, выберете виджет.
person Remon van Vliet    schedule 16.01.2012
comment
Я тоже пришел к такому выводу. У меня есть около 3500 свойств на виджет и, вероятно, более 100 000 виджетов. Разумно ли иметь недвижимость в собственной коллекции? - person justacodemonkey; 16.01.2012
comment
Upserts возможны, если вы можете изменить структуру вашего документа. Смотрите мой ответ. - person G-Wiz; 25.01.2014

Вы не можете атомарно переставлять элементы массива. Но если вы можете реструктурировать свой документ, чтобы использовать объект вместо массива, то это возможно атомарно. Используя ваши обозначения, структура будет

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

Примерный документ будет

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

Чтобы обновить элемент с именем «свойство-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' }
    }
)
person G-Wiz    schedule 25.01.2014
comment
Однако это не лишено недостатков. Например, вы не можете индексировать поля в элементах. Кроме того, существуют различные инструменты и системы типов, которые не очень хорошо поддерживают эту концепцию. Например, если вы хотите сопоставить этот тип с GraphQL, вам не повезло, он не поддерживает динамические ключи в объектах. - person Dobes Vandermeer; 15.10.2019
comment
Истинный. Если вам абсолютно необходимы атомарные upserts и вы предоставляете коллекцию с помощью GraphQL, вам нужно будет преобразовать структуру объекта в массив (например, с помощью Apollo с помощью пользовательского преобразователя). - person G-Wiz; 15.10.2019
comment
Насколько я понимаю, начиная с mongodb 4.2 вы также можете выполнять атомарное обновление, используя конвейер для добавления в массив. - person Dobes Vandermeer; 16.10.2019

По крайней мере на 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);

Он вставляет новые имена, если они не существуют, и обновляет уже существующие.

person Slava Elantsev    schedule 19.02.2013
comment
Это правильно, но вы не объяснили, почему это работает. Спрашивающему необходимо реструктурировать свой документ, чтобы использовать объект вместо массива. Смотрите мой ответ для более подробной информации. (Кроме того, ваше использование флага upsert приведет к обновлению всего документа, но вопрос был конкретно об элементах массива.) - person G-Wiz; 25.01.2014