Группировка / подсчет вложенных документов с сохранением корневых полей - PullRequest
0 голосов
/ 05 марта 2019

В mongodb после нескольких $ match и $ project я получаю следующие 2 документа.Я пытаюсь выяснить, как сгруппировать / посчитать список состояний для каждой команды в каждой группе каждого события.Короче говоря, мне нужно знать, сколько команд в каждом штате (0, 1 или 2).Я начну со следующих документов:

{ 
    "_id" : "event1", 
    "groups" : [
        {
            "_id" : "group1", 
            "wlActive" : true, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }, 
        {
            "_id" : "group2", 
            "wlActive" : false, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }
    ]
},
{ 
    "_id" : "event2", 
    "groups" : [
        {
            "_id" : "group3", 
            "wlActive" : true, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }, 
        {
            "_id" : "group4",
            "wlActive" : false, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }
    ]
}

То, чем я надеюсь закончить, будет примерно таким:

{ 
    "_id" : "event1", 
    "groups" : [
        {
            "_id" : "group1", 
            "wlActive" : true, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }, 
        {
            "_id" : "group2", 
            "wlActive" : false, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }
    ]
},
{ 
    "_id" : "event2", 
    "groups" : [
        {
            "_id" : "group3", 
            "wlActive" : true, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }, 
        {
            "_id" : "group4",
            "wlActive" : false, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }
    ]
}

Это не обязательно должно быть именно так, нодо тех пор, пока я могу получить счетчик каждого состояния команды, а также сохранить поля, такие как «wlActive» для каждой группы.Я видел подобные примеры здесь, но я просто не могу решить этот вопрос.

1 Ответ

0 голосов
/ 05 марта 2019

На самом деле вы можете просто сделать это с помощью $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)
  }))
}))
...