не поклонник использования «данные» в качестве «ключей» на выходе, как правило, лучше держать «данные» как «данные», и является более последовательным с объектно-ориентированными шаблонами проектирования, где ключи согласованы между объектами и не изменяются в каждом результате. В конце концов, у кого-то был здравый смысл разрабатывать исходные данные таким образом, в первую очередь.
Так все, что вам действительно нужно здесь является уровень группировки множественного что довольно просто сделать, принимая выход одной $group
стадии и подачи его к другому:
db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$date",
"parentId": "$parentId",
"status": "$status"
},
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": {
"date": "$_id.date",
"parentId": "$_id.parentId"
},
"data": { "$push": {
"status": "$_id.status",
"total": "$total"
}}
}},
{ "$group": {
"_id": "$_id.date",
"parents": { "$push": {
"parentId": "$_id.parentId",
"data": "$data"
}}
}}
])
Это будет прогрессивно гнездо данных в массивы под каждым ключом «дата» после того, как после первоначальной группы накапливается на самом высоком уровне детализации. В результате, в основном, «сворачивание» структуры в единый документ на ключ, уплотняя в массивы с помощью $push
:
[
{
"_id": "2015-12-01",
"parents": [
{
"parentId": 1,
"data": [
{ "status": "resolved", "total": 1 }
]
},
{
"parentId": 2,
"data": [
{ "status": "resolved", "total": 2 },
{ "status": "waiting", "total": 1 }
]
}
]
},
{
"_id": "2015-12-02",
"parents": [
{
"parentId": 1,
"data": [
{ "status": "resolved", "total": 1 }
]
},
{
"parentId": 2,
"data": [
{ "status": "waiting", "total": 2 }
]
}
]
},
{
"_id": "2015-12-03",
"parents": [
{
"parentId": 1,
"data": [
{ "status": "resolved", "total": 1 }
]
}
]
}
]
Или, если вы можете жить с этим, то вы можете пойти даже льстить со всеми соответствующими суб данных в одном массиве, а не вложенной один:
db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$date",
"parentId": "$parentId",
"status": "$status"
},
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.date",
"data": { "$push": {
"parentId": "$_id.parentId",
"status": "$_id.status",
"total": "$total"
}}
}}
])
Который имеет одного ребенка массив, просто сохраняя все данные введенные:
[
{
"_id": "2015-12-01",
"data": [
{
"parentId": 1,
"status": "resolved",
"total": 1
},
{
"parentId": 2,
"status": "resolved",
"total": 2
},
{
"parentId": 2,
"status": "waiting",
"total": 1
}
]
},
{
"_id": "2015-12-02",
"data": [
{
"parentId": 1,
"status": "resolved",
"total": 1
},
{
"parentId": 2,
"status": "waiting",
"total": 2
}
]
},
{
"_id": "2015-12-03",
"data": [
{
"parentId": 1,
"status": "resolved",
"total": 1
}
]
}
]
Основная суть здесь что «списки вещей» хранятся в виде массивов как ребенок того, с чем они связаны в любой форме, только с разной степенью. Это, пожалуй, легче обрабатывать и логичнее, чем определять «ключи» от объекта и итерации их, когда вы можете в принципе перебрать естественный список в любом случае.
Структура агрегации не поддерживает (совершенно сознательно) попытку массирования ключей от данных каким-либо образом, и большинство операций с запросами MongoDB также согласуются с этой философией, поскольку она имеет большой смысл для того, что по существу является «базой данных», ,
Если вы действительно должны массировать как ключи, было бы желательно сделать это на стороне клиента после получения агрегированного результата.Вы даже можете сделать это в обработке потока при прохождении через к удаленному клиенту, но в качестве основного примера трансформации:
var out = db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$date",
"parentId": "$parentId",
"status": "$status"
},
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": {
"date": "$_id.date",
"parentId": "$_id.parentId"
},
"data": { "$push": {
"status": "$_id.status",
"total": "$total"
}}
}},
{ "$group": {
"_id": "$_id.date",
"parents": { "$push": {
"parentId": "$_id.parentId",
"data": "$data"
}}
}}
]).toArray();
out.forEach(function(doc) {
var obj = {};
obj[doc._id] = {};
doc.parents.forEach(function(parent) {
obj[doc._id][parent.parentId] = {};
parent.data.forEach(function(data) {
obj[doc._id][parent.parentId][data.status] = data.total;
});
});
printjson(obj);
});
Который в основном производит вывод, как вы его структурирован, но, конечно, в виде отдельных документов, как объяснено позже :
{
"2015-12-01": {
"1": {
"resolved": 1
},
"2": {
"resolved": 2,
"waiting": 1
}
}
},
{
"2015-12-02": {
"1": {
"resolved": 1
},
"2": {
"waiting": 2
},
}
},
{
"2015-12-03": {
"1": {
"resolved": 1
}
}
}
Или вы можете заставить это на сервере с помощью обработки MapReduce и JavaScript на основе, но опять-таки опрометчивы из-за общую эффективность не столь же эффективной, как обработка агрегации:
db.collection.mapReduce(
function() {
var obj = {};
obj[this.parentId] = {};
obj[this.parentId][this.status] = 1;
emit(this.date,obj);
},
function(key,values) {
var result = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(parent) {
if (!result.hasOwnProperty(parent))
result[parent] = {};
Object.keys(parent).forEach(function(status) {
if (!result[parent].hasOwnProperty(status))
result[parent][status] = 0;
result[parent][status] += value[parent][status];
});
});
});
return result;
},
{ "out": { "inline": 1 } }
);
Почти такой же результат, но с определенным форматом вывода MapReduce всегда производит:
{
"_id": "2015-12-01",
"value": {
"1": {
"resolved": 1
},
"2": {
"resolved": 2,
"waiting": 1
}
}
},
{
"_id": "2015-12-02",
"value": {
"1": {
"resolved": 1
},
"2": {
"waiting": 2
},
}
},
{
"_id": "2015-12-03",
"value": {
"1": {
"resolved": 1
}
}
}
Заботясь там, особенно если вы не знакомы с тем, как работает MapReduce, есть очень важная причина, почему структуры испускаемые и перемещаемые последовательно между преобразователем и редуктором, а также суммирование испускаемых значений для статуса, а не просто увеличение. Это свойство mapReduce, в котором выход редуктора может снова вернуться через редуктор, пока не будет достигнут один результат.
Кроме того, как упоминалось ранее, и большущий предостережение «нового к этому», как вы утверждаете себя, что вы действительно никогда хотят компактных результатов в единый объект для ответа, как показано на ваш вопрос.
Не только это другое свойство плохого дизайна (покрытое ранее), но и существуют реалистичные «жесткие ограничения» на размер вывода MongoDB и многих разумных систем. Отдельные документы имеют ограничение по размеру BSON 16 МБ, что почти наверняка будет превышено в любом реальном случае при попытке сделать это.
Кроме того, «списки в виде списков» имеют смысл и пытаются искусственно представить, что использование уникальных ключей внутри одного объекта документа имеет мало смысла. Вещи обрабатываются и передаются гораздо легче, когда вы используете правильные типы структуры данных по назначению.
Так что это подходы к обработке вашего вывода. Это действительно просто базовые манипуляции с данными по агрегации, независимо от того, какой подход был принят. Но, надеюсь, вы можете видеть здравый смысл в том, чтобы держать его как можно более эффективным и простым, так как он может быть непосредственно обработан с помощью агрегации и имеет гораздо больше смысла для последующего кода, обрабатывающего полученные результаты.
Вы настаиваете на такой точной структуре? Потому что тогда вы не можете использовать структуру агрегации и должны будете использовать MapReduce. – Philipp
Если у вас уже есть «некоторая группировка», я предполагаю, что вы используете [конвейер агрегации] (https://docs.mongodb.org/manual/core/aggregation-pipeline/). Что помешает вам добавить дополнительную '$ group'? –
Спасибо за ваши комментарии. Я только что обновил сообщение. – hopsey