Агрегат mongodb с группой по вложенному полю и подсчетом по другому полю

У меня три разных документа:

{
  "category" : "aaaaa",
  "summary" : {
    "details" : {
      "city" : "abc"
      "year_of_reg" : "2012",
      "dept" : "dev"
    }
  }
}

{
  "category" : "bbbb",
  "summary" : {
    "details" : {
      "city" : "abc",
      "year_of_reg" : "2016",
      "dept" : "dev"
    }
  }
}

{
  "category" : "aaaaa",
  "summary" : {
    "details" : {
      "dept" : "ui",
      "year_of_reg" : "2018"
    }
  }
}

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

{
  "dep_dev":[
      {
         "category":"aaaaa",
         "count":1.0
      },
      {
         "category":"bbbb",
         "count":1.0
      }
   ],
   "dep_ui":[
      {
         "category":"aaaaa",
         "count":1.0
      }
   ],
   "year_of_reg_2012":[
      {
         "category":"aaaaa",
         "count":1.0
      }
   ],
   "year_of_reg_2016":[
      {
         "category":"bbbb",
         "count":1.0
      }
   ],
   "year_of_reg_2018":[
      {
         "category":"aaaaa",
         "count":1.0
      }
   ],      
   "city_abc":[
      {
         "category":"aaaaa",
         "count":1.0
      },
      {
         "category":"bbbb",
         "count":1.0
      }
   ]
}

Как этого можно достичь в агрегате mongo? Можно ли это сделать с помощью фасета? Как выходные ключи могут генерироваться динамически в совокупности? Есть ли способ получить все ключи, доступные в деталях, с помощью одного запроса mongo?

2
0
2 316
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Выполните следующие шаги: Преобразуйте объект «деталь» в массив с помощью $ objectToArray. Теперь поле деталей выглядит как "details: [{k: "city", v: "abc"}, {v: "dep", k: "dev"}].

$ развернуть поле массива "детали".

$ project новый лейбл "label: $details.k_$details.v".

$ group как по новому ярлыку, так и по категории.

$ группировать по категориям.

db.collections.aggregate([
{
    $project: {
        "category": 1,
        "details": { $objectToArray: "details"}
    }
},
{
    $unwind: "details"
},
{
    $project: {
        "label": { $concat ["$details.k", '_', "$details.v"]}
    }
},
{
    $group: {
        "_id": {
            "label": "label",
            "category": "category"
        },
        "count": {$sum: 1}
    }
},
{
    $group: {
        "_id": "_id.label",
        "summary": {
            $push: {
                "category": "$_id.category",
                "count": "$count"
            }
        }
    }
}
])
Ответ принят как подходящий

Для получения желаемых результатов вам потребуется запустить следующий агрегатный конвейер:

db.getCollection('test').aggregate([
    /*
    1. Create a field with an array of the summary details key concatenated with their 
       corresponding values. 
    */
    { "$addFields": { 
        "summary": {
            "$map": {
                "input": { "$objectToArray": "$summary.details" },
                "as": "el",
                "in": {
                    "$concat": ["$$el.k", "_", "$$el.v"]
                }
            }
        }
    } },

    /*
    2. Flatten the new array to produce a copy of each document per array entry. 
    */
    { "$unwind": "$summary" },

    /*
    3. Group the documents initially by the key and category. 
    */
    { "$group": {
        "_id": { 
            "key": "$summary",
            "category": "$category"
        },
        "count": { "$sum": 1 }
    } },

    /*
    4. Group the input documents from the previous pipeline by the key and aggregate the 
       category and corresponding counts  
    */
    {  "$group": {
        "_id": "$_id.key",
        "counts": {
            "$push": {
                "category": "$_id.category",
                "count": "$count"
            }
        }
    } },

    /*
    4. Calculate accumulated values for all the input documents as a whole.
    */
    {  "$group": {
        "_id": null,
        "counts": {
            "$push": {
                "k": "$_id",
                "v": "$counts"
            }
        }
    } },
    { "$replaceRoot": {
        "newRoot": { "$arrayToObject": "$counts" }
    } } 
])

Это почти то же самое, что и мой ответ ниже.

user123 10.08.2018 17:42

@ngocchien Я согласен, есть некоторые сходства с вашим и моим ответом, например. использование $objectToArray, имя ключа summary и $concat. Однако есть заметные отличия от трубопроводов; Я использую единый конвейер $map для создания массива новых ключей, и, боюсь, я могу указать на несколько недостатков в вашем ответе. Начнем с того, что ваш первый конвейер $project выдаст ошибку на "details": { $objectToArray: "details"}, потому что он не распознает "details" как поле, отсутствует ключевое слово $, а также это не встроенный документ.

chridam 10.08.2018 18:01

@ngocchien Также, даже если весь агрегатный конвейер исправлен, он не даст желаемых результатов, которые ищет OP, вам все равно понадобятся дополнительные конвейеры, такие как перегруппировка счетчиков для расчета накопленных значений для всех входных документов в целом и передачи их в финальный конвейер "$replaceRoot".

chridam 10.08.2018 18:05

Да, я согласен, что в моем ответе есть ошибки. Я просто подал идею. Я даже еще не тестировал результат. В любом случае, я думаю, что наша идея верна, и это самое главное, и спрашивающий будет знать, как исправить ее потребности.

user123 11.08.2018 07:28

Спасибо @chridam. Это сработало отлично. Здесь мне нужно учесть две вещи. 1) Результаты перемешиваются. Мне нужно, чтобы он был отсортирован по общему количеству на основе ключей под подробностями в сводке (отдел, город, год_из_рег), и если два ключа имеют одинаковое количество подсчетов, его следует отсортировать в алфавитном порядке (отредактировал ожидаемый ответ в вопросе) 2) $ addToFields появился в Mongo 3.4. Как можно изменить приведенный выше запрос для Mongo 2.4

NagaLakshmi 11.08.2018 08:38

@NagaLakshmi К сожалению, в структуре агрегации в 2.4 отсутствуют операторы, необходимые для получения ключей в документе, а также операторы, необходимые для замены корневого каталога, в противном случае вам, возможно, придется использовать для этого MapReduce. Что касается сортировки результатов, поместите конвейер $sort сразу после $unwind, чтобы направить их в следующий конвейер по порядку.

chridam 11.08.2018 08:55

Сортировка @chridam после раскрутки не работает должным образом, так как мне нужна сортировка первого уровня по общему количеству ключей. Например: Все документы имеют ключ города. Это должно быть первым в результате. Сортировка второго уровня является алфавитной, если несколько ключей имеют одинаковое общее количество.

NagaLakshmi 11.08.2018 11:07

@chridam кажется, что документы сначала нужно отсортировать по количеству ключей, а затем обработать для получения окончательного результата. Но я не могу этого добиться. Не могли бы вы предоставить мне обновленный запрос

NagaLakshmi 11.08.2018 12:27

Другие вопросы по теме