Как мне обновить поле в MongoDB, которое суммирует значения дочернего документа? - PullRequest
1 голос
/ 08 февраля 2020

У меня есть документ, который структурирован так:

{
    'item_id': '12345'
    'total_score': 100,
    'user_scores': {
        'ABC': 40,
        'DEF': 60
    }
}

Я использую PyMon go, но документация MongoDB кажется легко переводимой между различными дистрибутивами. С PyMon go я мог бы обновлять оценки пользователей следующим образом:

collection.update_one(
    { 'item_id': '12345' },
    { '$set': { 'user_scores.GHI': 20 } },
    upsert=True
)

Что приводит к следующему:

{
    'item_id': '12345'
    'total_score': 100,
    'user_scores': {
        'ABC': 40,
        'DEF': 60,
        'GHI': 20
    }
}

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

Одним из решений может быть поиск существующего документа с использованием find_one({'item_id: '12345'}) , (создать, если он не существует), затем обновить с новыми оценками, и обновить общий счет. Проблема в том, что я хочу запустить тысячи из них одновременно, и гораздо эффективнее вызывать bulk_write для ряда запросов.

Итак, лучшим решением было бы сделать два последовательных запросы на обновление:

request1 = UpdateOne(
    { 'item_id' : '12345' },
        { '$set': { 'user_scores.GHI': 20 } },
        upsert = True
)

request2 = UpdateOne(
    { 'item_id' : '12345' },
        { '$set': { 'total_score': { '$sum': { '$values': 'user_scores' } } } },
        upsert = True
)

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

  1. Мне нужно получить значения из словаря user_scores. { '$values': 'user_scores' } - вот как я пытался это передать.
  2. Это дает мне массив значений. Я знаю, что все это числа c, поэтому мне нужно сложить их, переданные с помощью { '$sum': { '$values': 'user_scores' } }.

. Я могу последовательно запускать эти пакетные обновления, поэтому нет риска суммировать неправильные вещи. , Опасность наличия поля total_score всегда заключается в том, что оно не обновляется и, следовательно, не содержит правильного номера. Я полагаю, что это общий случай с документами на основе моделей?

1 Ответ

2 голосов
/ 08 февраля 2020

Если вы используете Mon go версии 4.2+, они представили новую функцию: конвейерные обновления , то есть теперь вы можете делать то, что хотите, в одном go:

db.collection.updateOne({ 'item_id' : '12345' },
    [
        { '$set': { 'user_scores.GHI': 20 } },
        { '$set': { 'total_score': { '$sum': [ "$user_scores.GHI", "$user_scores.ABC", "$user_scores.GHI"] } } },,
    ]);

К сожалению, это невозможно для небольших версий Mon go, поэтому, если это так, вам придется продолжать использовать свое решение, которое разделяет его на 2 действия.

EDIT: для Dynami c обновить, мы можем использовать $ map и $ objectToArray примерно так:

db.collection.updateOne(
    {'item_id': '12345'},
    [
        {'$set': {'user_scores.GHI': 20}},
        {
            '$set':
                {
                    'total_score': {
                        '$sum': {
                            '$map': {
                                'input': {'$objectToArray': '$user_scores'},
                                'as': 'score',
                                'in': '$$score.v'
                            }
                        }
                    }
                }
        }
    ]);
...