Может ли фреймворк агрегации MongoDB $ group возвращать массив значений?

Насколько гибкой является агрегатная функция для форматирования вывода в MongoDB?

Формат данных:

{
        "_id" : ObjectId("506ddd1900a47d802702a904"),
        "port_name" : "CL1-A",
        "metric" : "772.0",
        "port_number" : "0",
        "datetime" : ISODate("2012-10-03T14:03:00Z"),
        "array_serial" : "12345"
}

Прямо сейчас я использую эту агрегатную функцию для возврата массива DateTime, массива показателей и счетчика:

{$match : { 'array_serial' : array, 
                            'port_name' : { $in : ports},
                            'datetime' : { $gte : from, $lte : to}
                        }
                },
               {$project : { port_name : 1, metric : 1, datetime: 1}},
               {$group : { _id : "$port_name", 
                            datetime : { $push : "$datetime"},
                            metric : { $push : "$metric"},
                            count : { $sum : 1}}}

Это хорошо и очень быстро, но есть ли способ отформатировать вывод так, чтобы на каждую дату и время / метрику приходился один массив? Нравится:

[
    {
      "_id" : "portname",
      "data" : [
                ["2012-10-01T00:00:00.000Z", 1421.01],
                ["2012-10-01T00:01:00.000Z", 1361.01],
                ["2012-10-01T00:02:00.000Z", 1221.01]
               ]
    }
]

Это значительно упростило бы интерфейс, поскольку это формат, которого ожидает код диаграммы.


person Chris Matta    schedule 08.10.2012    source источник
comment
В то же время я получаю результат и перебираю объекты в цикле и использую функцию подчеркивания zip для их объединения, это, похоже, не добавляет много накладных расходов.   -  person Chris Matta    schedule 08.10.2012


Ответы (3)


Объединение двух полей в массив значений с помощью Aggregation Framework возможно, но определенно не так просто, как могло бы быть (по крайней мере, как в MongoDB 2.2.0).

Вот пример:

db.metrics.aggregate(

    // Find matching documents first (can take advantage of index)
    { $match : {
        'array_serial' : array, 
        'port_name' : { $in : ports},
        'datetime' : { $gte : from, $lte : to}
    }},

    // Project desired fields and add an extra $index for # of array elements
    { $project: {
        port_name: 1,
        datetime: 1,
        metric: 1,
        index: { $const:[0,1] }
    }},

    // Split into document stream based on $index
    { $unwind: '$index' },

    // Re-group data using conditional to create array [$datetime, $metric]
    { $group: {
        _id: { id: '$_id', port_name: '$port_name' },
        data: {
            $push: { $cond:[ {$eq:['$index', 0]}, '$datetime', '$metric'] }
        },
    }},

    // Sort results
    { $sort: { _id:1 } },

    // Final group by port_name with data array and count
    { $group: {
        _id: '$_id.port_name',
        data: { $push: '$data' },
        count: { $sum: 1 }
    }}
)
person Stennie    schedule 09.10.2012
comment
Ах! Я не знал, что $ group можно вызвать более одного раза. Я попробую, спасибо! - person Chris Matta; 09.10.2012
comment
Что именно делает '$ const'? Кажется, это не задокументировано. - person maxdec; 04.02.2013
comment
Оказывается, $const является внутренней деталью реализации для сериализации между mongos и mongod и не предназначен для (ab) использования в запросах конечных пользователей (см. jira.mongodb.org/browse/SERVER-6769). В частности, это может не работать должным образом через mongos. В то время я не понимал, что это не задокументированное выражение, и видел, как оно (ab) используется для добавления констант в документы, как это было сделано здесь. Я попытаюсь вернуться к этому ответу после выпуска MongoDB 2.4.0, поскольку может быть альтернативный (и задокументированный) подход. - person Stennie; 01.03.2013
comment
Проголосуйте и просмотрите запрос функции MongoDB SERVER-8141, в котором предлагается добавить агрегирование $array выражение :). - person Stennie; 10.04.2013

MongoDB 2.6 значительно упростил эту задачу, представив $map, который позволяет более простая форма транспонирования массива:

db.metrics.aggregate([
   { "$match": {
       "array_serial": array, 
       "port_name": { "$in": ports},
       "datetime": { "$gte": from, "$lte": to }
    }},
    { "$group": {
        "_id": "$port_name",
        "data": {
            "$push": {
                "$map": {
                    "input": [0,1],
                    "as": "index",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$index", 0 ] },
                            "$datetime",
                            "$metric"
                        ]
                    }
                }
            }
        },
        "count": { "$sum": 1 }
    }}
])

Как и в случае с $unwind, вы предоставляете массив в качестве «ввода» для операции сопоставления, состоящий из двух значений, а затем по существу заменяете эти значения значениями полей, которые вы хотите использовать с помощью операции $cond.

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

person Blakes Seven    schedule 01.11.2015

Построение массивов в структуре агрегации без $ push и $ addToSet, похоже, отсутствует. Я пытался заставить это работать раньше, но потерпел неудачу. Было бы здорово, если бы вы могли просто сделать:

data : {$push: [$datetime, $metric]}

в $group, но это не работает.

Кроме того, создание таких «буквальных» объектов не работает:

data : {$push: {literal:[$datetime, $metric]}}
or even data : {$push: {literal:$datetime}}

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

person Eve Freeman    schedule 08.10.2012
comment
Это точные методы, которые я пробовал, я просто предполагал, что это сработает. Думаю, нет :( - person Chris Matta; 09.10.2012