Может ли структура агрегации 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"
}
Сейчас я использую эту функцию aggregate для возврата массива 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}}}
Что хорошо, и очень быстро, но есть ли способ форматировать вывод, чтобы там был один массив в datetime/metric? Вот так:
[
{
"_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]
]
}
]
Это значительно упростит интерфейс, так как формат, который ожидает код диаграммы.
Ответы
Ответ 1
Объединение двух полей в массив значений с помощью 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 }
}}
)
Ответ 2
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
, вы предоставляете массив как "enter" операции с картой, состоящей из двух значений, и затем по существу заменяете эти значения значениями полей, которые вы хотите использовать с помощью операции $cond
.
Это фактически удаляет все манипуляции с конвейером, необходимые для преобразования документа, как это требовалось в предыдущих выпусках, и просто оставляет фактическое агрегирование в задание под рукой, которое в основном накапливается на значение "имя порта", а преобразование в массив не является больше проблемной области.
Ответ 3
Создание массивов в структуре агрегации без $push и $addToSet - это то, чего, кажется, не хватает. Я пытался заставить это работать раньше, и не смог. Было бы здорово, если бы вы могли просто сделать:
data : {$push: [$datetime, $metric]}
в $group
, но это не работает.
Кроме того, создание "литерала" таких объектов, как это, не работает:
data : {$push: {literal:[$datetime, $metric]}}
or even data : {$push: {literal:$datetime}}
Надеюсь, что в конце концов они придумают несколько лучших способов массирования таких данных.
Ответ 4
Вы можете использовать $zip для создания массива значений в 3.4
$zip
с помощью $arrayElemAt
для создания массива datetime
и metrics
.
Что-то вроде
db.collection.aggregate([ {
"$match": {
"array_serial": array,
"port_name": {
"$in": ports
},
"datetime": {
"$gte": from,
"$lte": to
}
}
},
{
"$group": {
"_id": "$port_name",
"data": {
"$push": {
"$arrayElemAt": [
{
"$zip": {
"inputs": [
[
"$datetime"
],
[
"$metric"
]
]
}
},
0
]
}
},
"count": {
"$sum": 1
}
}
}
])