MongoDB — добавление в набор и увеличение

#mongodb

#mongodb

Вопрос:

Я пытаюсь подсчитать использование слов с помощью MongoDB. Моя коллекция в настоящее время выглядит так:

 {'_id':###, 'username':'Foo', words:[{'word':'foo', 'count':1}, {'word':'bar', 'count':1}]}
 

Когда создается новое сообщение, я извлекаю все новые слова в массив, но я пытаюсь выяснить, как выполнить upsert в массив words и увеличить количество, если слово уже существует.

Например, в приведенном выше примере, если пользователь «Foo» опубликовал «lorem ipsum foo», я бы добавил «lorem» и «ipsum» в массив слов пользователей, но увеличил количество для «foo».

Возможно ли это в одном запросе? В настоящее время я использую addToSet:

 '$addToSet':{'words':{'$each':word_array}}
 

Но, похоже, это не дает никакого способа увеличить количество слов.

Был бы очень признателен за помощь 🙂

Ответ №1:

Если вы хотите переключиться со списка на хэш (объект), вы можете сделать это атомарно.

Из документов: « $inc … увеличивает поле на числовое значение, если поле присутствует в объекте, в противном случае присваивает полю числовое значение. »

 { $inc : { field : value } }
 

Итак, если бы вы могли реорганизовать свой контейнер и объект:

 words: [
  {
    'word': 'foo',
    'count': 1
  },
  ...
]
 

Для:

 words: {
  'foo': 1,
  'other_word: 2,
  ...
}
 

вы могли бы использовать операцию update с:

 { $inc: { 'words.foo': 1 } }
 

который будет создан { 'foo': 1 } , если ‘foo’ не существует, иначе увеличит foo .

Например.:

 $ db.bar.insert({ id: 1, words: {} });
$ db.bar.find({ id: 1 })
[ 
  {   ...,   "words" : {     },   "id" : 1   }
]
$ db.bar.update({ id: 1 }, { $inc: { 'words.foo': 1 } });
$ db.bar.find({ id: 1 })
[ 
  {   ...,   "id" : 1,   "words" : {   "foo" : 1   }   }
]
$ db.bar.update({ id: 1 }, { $inc: { 'words.foo': 1 } });
$ db.bar.find({ id: 1 })
[ 
  {   ...,   "id" : 1,   "words" : {   "foo" : 2   }   }
]
 

Комментарии:

1. Это работает только для каждого слова и делает невозможным выполнение каких-либо запросов к самим словам. Я немного смущен тем, насколько это лучше, чем выделенные кортежи счетчиков для каждого слова. Это также связано с проблемами, о которых я упоминал в своем ответе.

2. Пользователь может ввести весь массив слов (измененный на word: 1 форму dict), а ключи word либо добавляются как «1», либо увеличиваются. Итак, это именно то, что было запрошено в вопросе: «Я извлекаю все новые слова в массив, но я пытаюсь выяснить, как выполнить upsert в массив words и увеличить количество, если слово уже существует». — и все это за одну атомарную операцию (update), и это несколько ближек исходной структуре вопроса (правильной или неправильной).

3. Достаточно справедливо. Я все еще думаю, что это неоптимальное решение проблемы.

Ответ №2:

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

 db.users {_id:###, username:'Foo'}
db.words.counters {_id:###, word:'Word', userId: ###, count: 1}
 

Это позволит избежать многих проблем, таких как :

  • Ограничение максимального размера документа
  • Принудительное перемещение mongo по вашим документам по мере увеличения их размера

Оба сценария требуют двух обновлений, чтобы делать то, что вы хотите, что приводит к проблемам атомарности. Обновление для каждого слова путем перебора word_array лучше и безопаснее (и возможно с обоими решениями).