Как суммировать все поля в поддокументе MongoDB?

У меня возникла проблема, когда я использую db.collection.aggregate в MongoDB.

У меня есть структура данных, например:

_id:...

Segment:{
  "S1":1,
  "S2":5,
  ...
  "Sn":10
}

Это означает следующее в Segment: У меня может быть несколько податрибутов с числовыми значениями. Я хотел бы суммировать их как 1 + 5 + .. + 10

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

Я пробовал такие запросы:

db.collection.aggregate([

  {$group:{
    _id:"$Account",

    total:{$sum:"$Segment.$"}
])

но это не работает.


person aaintw    schedule 15.07.2013    source источник
comment
Вам нужно будет изменить свою схему на что-то вроде segment: 5, value: 400 и поместить их в массив или использовать MapReduce. Вы не можете сделать сумму на произвольных полях.   -  person WiredPrairie    schedule 16.07.2013


Ответы (3)


Вы сделали классическую ошибку, имея произвольные имена полей. MongoDB «свободна от схемы», но это не значит, что вам не нужно думать о своей схеме. Имена ключей должны быть описательными, и в вашем случае, например, "S2" на самом деле ничего не значит. Чтобы выполнять большинство видов запросов и операций, вам нужно будет изменить схему для хранения ваших данных следующим образом:

_id:...
Segment:[
    { field: "S1", value: 1 },
    { field: "S2", value: 5 },
    { field: "Sn", value: 10 },
]

Затем вы можете запустить свой запрос, например:

db.collection.aggregate( [
    { $unwind: "$Segment" },
    { $group: {
        _id: '$_id', 
        sum: { $sum: '$Segment.value' } 
    } } 
] );

Что затем приводит к чему-то вроде этого (с единственным документом из вашего вопроса):

{
    "result" : [
        {
            "_id" : ObjectId("51e4772e13573be11ac2ca6f"),
            "sum" : 16
        }
    ],
    "ok" : 1
}
person Derick    schedule 15.07.2013
comment
Спасибо. Я думаю, вы правы. Но проблема в том, что я загрузил весь набор данных, который довольно велик. Есть ли способ преобразовать на месте? - person aaintw; 17.07.2013
comment
Я так не думаю. Вам нужно будет написать сценарий, чтобы сделать это один за другим. Убедитесь, что вы ограничиваете свой сценарий, если он уже запущен в производстве. - person Derick; 17.07.2013
comment
Что ж, ответ был действительно полезным, но $unwind может привести к серьезным проблемам с производительностью при работе с большим набором данных. Поэтому сначала я попытаюсь придумать лучшее решение. - person Himanshu Chandra; 22.12.2017
comment
Я думаю, что этот вопрос похож на этот stackoverflow.com/questions/56640054/ - person eNddy; 18.06.2019

Начиная с Mongo 3.4, этого можно достичь, применяя встроенные операции и, таким образом, избегая дорогостоящих операций, таких как $group:

// { _id: "xx", segments: { s1: 1, s2: 3, s3: 18, s4: 20 } }
db.collection.aggregate([
  { $addFields: {
      total: { $sum: {
        $map: { input: { $objectToArray: "$segments" }, as: "kv", in: "$$kv.v" }
      }}
  }}
])
// { _id: "xx", total: 42, segments: { s1: 1, s2: 3, s3: 18, s4: 20 } }

Идея состоит в том, чтобы преобразовать объект (содержащий числа для суммирования) в массив. Это роль $objectToArray, которая, начиная с Mongo 3.4.4, преобразует { s1: 1, s2: 3, ... } в [ { k: "s1", v: 1 }, { k: "s2", v: 3 }, ... ]. Таким образом, нам не нужно заботиться об именах полей, поскольку мы можем получить доступ к значениям через их "v" поля.

Наличие массива, а не объекта — это первый шаг к возможности суммировать его элементы. Но элементы, полученные с помощью $objectToArray, являются объектами, а не простыми целыми числами. Мы можем передать это путем сопоставления (операция $map) этого массива элементы для извлечения значения их поля "v". Что в нашем случае приводит к созданию такого массива: [1, 3, 18, 42].

Наконец, достаточно просто суммировать элементы в этом массиве, используя $sum операция.

person Xavier Guihot    schedule 25.06.2019
comment
Хорошая идея. Это помогло мне. - person igorkf; 08.05.2020

Сегмент: {s1: 10, s2: 4, s3: 12}

{$set: {"new_array":{$objectToArray: "$Segment"}}}, //makes field names all "k" or "v"
{$project: {_id:0, total:{$sum: "$new_array.v"}}} 

всего будет 26.

$set заменяет $addFields в новых версиях mongo. (Я использую 4.2.)

"new_array": [
                {
                    "k": "s1",
                    "v": 10
                },
                {
                    "k": "s2",
                    "v": 4
                },
                {
                    "k": "s3",
                    "v": 12
                }
            ]

Вы также можете использовать регулярные выражения. Например. /^s/i для слов, начинающихся с с.

person Honeybear65    schedule 16.02.2021