У меня три разных документа:
{
"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?
Выполните следующие шаги:
Преобразуйте объект «деталь» в массив с помощью $ 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" }
} }
])
@ngocchien Я согласен, есть некоторые сходства с вашим и моим ответом, например. использование $objectToArray
, имя ключа summary
и $concat
. Однако есть заметные отличия от трубопроводов; Я использую единый конвейер $map
для создания массива новых ключей, и, боюсь, я могу указать на несколько недостатков в вашем ответе. Начнем с того, что ваш первый конвейер $project
выдаст ошибку на "details": { $objectToArray: "details"}
, потому что он не распознает "details"
как поле, отсутствует ключевое слово $
, а также это не встроенный документ.
@ngocchien Также, даже если весь агрегатный конвейер исправлен, он не даст желаемых результатов, которые ищет OP, вам все равно понадобятся дополнительные конвейеры, такие как перегруппировка счетчиков для расчета накопленных значений для всех входных документов в целом и передачи их в финальный конвейер "$replaceRoot"
.
Да, я согласен, что в моем ответе есть ошибки. Я просто подал идею. Я даже еще не тестировал результат. В любом случае, я думаю, что наша идея верна, и это самое главное, и спрашивающий будет знать, как исправить ее потребности.
Спасибо @chridam. Это сработало отлично. Здесь мне нужно учесть две вещи. 1) Результаты перемешиваются. Мне нужно, чтобы он был отсортирован по общему количеству на основе ключей под подробностями в сводке (отдел, город, год_из_рег), и если два ключа имеют одинаковое количество подсчетов, его следует отсортировать в алфавитном порядке (отредактировал ожидаемый ответ в вопросе) 2) $ addToFields появился в Mongo 3.4. Как можно изменить приведенный выше запрос для Mongo 2.4
@NagaLakshmi К сожалению, в структуре агрегации в 2.4 отсутствуют операторы, необходимые для получения ключей в документе, а также операторы, необходимые для замены корневого каталога, в противном случае вам, возможно, придется использовать для этого MapReduce. Что касается сортировки результатов, поместите конвейер $sort
сразу после $unwind
, чтобы направить их в следующий конвейер по порядку.
Сортировка @chridam после раскрутки не работает должным образом, так как мне нужна сортировка первого уровня по общему количеству ключей. Например: Все документы имеют ключ города. Это должно быть первым в результате. Сортировка второго уровня является алфавитной, если несколько ключей имеют одинаковое общее количество.
@chridam кажется, что документы сначала нужно отсортировать по количеству ключей, а затем обработать для получения окончательного результата. Но я не могу этого добиться. Не могли бы вы предоставить мне обновленный запрос
Это почти то же самое, что и мой ответ ниже.