На самом деле вы можете просто сделать это с помощью $addFields
или $project
db.collection.aggregate([
{ "$addFields": {
"groups": {
"$map": {
"input": "$groups",
"in": {
"$mergeObjects": [
"$$this",
{ "teams": {
"$reduce": {
"input": "$$this.teams",
"initialValue": [ ],
"in": {
"$cond": {
"if": {
"$ne": [ { "$indexOfArray": ["$$value.state", "$$this.state"] }, -1 ]
},
"then": {
"$concatArrays": [
{ "$filter": {
"input": "$$value",
"as": "v",
"cond": { "$ne": [ "$$v.state", "$$this.state" ] }
}},
[{
"state": "$$this.state",
"count": { "$sum": [
{ "$arrayElemAt": [
"$$value.count",
{ "$indexOfArray": ["$$value.state", "$$this.state" ] }
]},
1
]}
}]
]
},
"else": {
"$concatArrays": [
"$$value",
[{ "state": "$$this.state", "count": 1 }]
]
}
}
}
}
}}
]
}
}
}
}}
])
Это довольно сложно и в основном использует $reduce
"inline" вместо оператора конвейера $group
.
$reduce
является основной частью работы, поскольку она повторяетсякаждый элемент массива "сводится" к другому массиву с "сгруппированными" суммами по ключам.Это достигается путем поиска значения state
в текущем уменьшенном результате с помощью $indexOfArray
.Когда что-то не найдено (-1
возвращено), оно добавляется к текущему результату через $concatArrays
с новыми state
и count
из 1
.Это случай else
.
Когда что-то найдено (случай then
), мы удаляем соответствующий элемент из массива результатов с помощью $filter
и объединяют новый элемент из сопоставленного индекса $indexOfArray
и извлекают значение, используя $arrayElemAt
.Это дает текущий count
соответствующего элемента, который добавляется с помощью $sum
, чтобы увеличить счет на 1
.
Конечно, вы можете традиционно сделать это с$unwind
и $group
операторы:
db.collection.aggregate([
{ "$unwind": "$groups" },
{ "$unwind": "$groups.teams" },
{ "$group": {
"_id": {
"_id": "$_id",
"gId": "$groups._id",
"wlActive": "$groups.wlActive",
"state": "$groups.teams.state"
},
"count": { "$sum": 1 }
}},
{ "$sort": { "_id": -1, "count": -1 } },
{ "$group": {
"_id": {
"_id": "$_id._id",
"gId": "$_id.gId",
"wlActive": "$_id.wlActive",
},
"teams": { "$push": { "state": "$_id.state", "count": "$count" } }
}},
{ "$group": {
"_id": "$_id._id",
"groups": {
"$push": {
"_id": "$_id.gId",
"wlActive": "$_id.wlActive",
"teams": "$teams"
}
}
}}
])
Здесь $unwind
используется для «выравнивания» массив содержимого в отдельные документы.Вы делаете это до уровня teams
и $group
на составном ключе , который идентифицирует уникальность до уровня state
.
Поскольку все детали документа являются частью начального ключа $group
, вы удаляете уровень "уникальность" , поэтому teams
становится массивом, используя $push
.Чтобы вернуться к исходной форме документа, выполняется другое $group
для исходного значения _id
для документов, а $push
восстанавливает массив groups
.
Эта форма, вероятно, "проще" для понимания, однако для ее выполнения требуется значительно больше времени и больше ресурсов.Первая форма - оптимальная , поскольку на самом деле вам не нужно $group
в существующем документе, и вам, как правило, следует избегать $unwind
, если в этом нет крайней необходимости,то есть группирование state
по всем документам необходимо, но в пределах одного документа это не так.
В любом случае, в основном, возвращается один и тот же результат:
{
"_id" : "event1",
"groups" : [
{
"_id" : "group1",
"wlActive" : true,
"teams" : [
{
"state" : 2,
"count" : 2
},
{
"state" : 1,
"count" : 3
},
{
"state" : 0,
"count" : 2
}
]
},
{
"_id" : "group2",
"wlActive" : false,
"teams" : [
{
"state" : 2,
"count" : 2
},
{
"state" : 1,
"count" : 3
},
{
"state" : 0,
"count" : 2
}
]
}
]
}
{
"_id" : "event2",
"groups" : [
{
"_id" : "group3",
"wlActive" : true,
"teams" : [
{
"state" : 2,
"count" : 2
},
{
"state" : 1,
"count" : 3
},
{
"state" : 0,
"count" : 2
}
]
},
{
"_id" : "group4",
"wlActive" : false,
"teams" : [
{
"state" : 2,
"count" : 2
},
{
"state" : 1,
"count" : 3
},
{
"state" : 0,
"count" : 2
}
]
}
]
}
Во что бы то ни стало, поскольку на самом деле это "агрегирование" чего-либо между документами не так просто, вы можете просто вернуть все данные и "агрегировать" элементы массива в коде на стороне клиента.
В качестве примера оболочки монго:
db.collection.find().map(doc => Object.assign({}, doc, {
_id: doc._id,
groups: doc.groups.map(g => Object.assign({}, g, {
_id: g._id,
wlActive: g.wlActive,
teams: ((input) => {
var obj = input.reduce((o, e) =>
(o.hasOwnProperty(e.state)) ?
Object.assign({} , o, { [e.state]: o[e.state]+1 })
: Object.assign({}, o, { [e.state]: 1 }), {});
return Object.keys(obj)
.map(k => ({ state: parseInt(k), count: obj[k] }))
.sort((a,b) => b.state - a.state);
})(g.teams)
}))
}))