2015-12-07 4 views
0

Я новичок в MongoDB, так что простите меня, если я что-то пропустил в документах. У меня есть коллекция как этот

[date: "2015-12-01", status: "resolved", parentId: 1] 
[date: "2015-12-01", status: "resolved", parentId: 2] 
[date: "2015-12-01", status: "resolved", parentId: 2] 
[date: "2015-12-01", status: "waiting", parentId: 2] 
[date: "2015-12-02", status: "resolved", parentId: 1] 
[date: "2015-12-02", status: "waiting", parentId: 2] 
[date: "2015-12-02", status: "waiting", parentId: 2] 
[date: "2015-12-03", status: "resolved", parentId: 1] 

и я ожидаю, чтобы подвести вывод, сгруппированные по

дата -> ParentID -> статус

так, что бы

{ 
    "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 
     } 
    } 
} 

любые советы, как я могу это достичь? Я уже получил это, используя структуру агрегации:

{ 
    '$group': { 
     '_id': { 
      'date': '$date', 
      'status': '$status', 
      'parentId': '$parentId' 
     }, 
     'total': { 
      '$sum': 1 
     } 
    } 
} 
+0

Вы настаиваете на такой точной структуре? Потому что тогда вы не можете использовать структуру агрегации и должны будете использовать MapReduce. – Philipp

+0

Если у вас уже есть «некоторая группировка», я предполагаю, что вы используете [конвейер агрегации] (https://docs.mongodb.org/manual/core/aggregation-pipeline/). Что помешает вам добавить дополнительную '$ group'? –

+0

Спасибо за ваши комментарии. Я только что обновил сообщение. – hopsey

ответ

3

не поклонник использования «данные» в качестве «ключей» на выходе, как правило, лучше держать «данные» как «данные», и является более последовательным с объектно-ориентированными шаблонами проектирования, где ключи согласованы между объектами и не изменяются в каждом результате. В конце концов, у кого-то был здравый смысл разрабатывать исходные данные таким образом, в первую очередь.

Так все, что вам действительно нужно здесь является уровень группировки множественного что довольно просто сделать, принимая выход одной $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 МБ, что почти наверняка будет превышено в любом реальном случае при попытке сделать это.

Кроме того, «списки в виде списков» имеют смысл и пытаются искусственно представить, что использование уникальных ключей внутри одного объекта документа имеет мало смысла. Вещи обрабатываются и передаются гораздо легче, когда вы используете правильные типы структуры данных по назначению.


Так что это подходы к обработке вашего вывода. Это действительно просто базовые манипуляции с данными по агрегации, независимо от того, какой подход был принят. Но, надеюсь, вы можете видеть здравый смысл в том, чтобы держать его как можно более эффективным и простым, так как он может быть непосредственно обработан с помощью агрегации и имеет гораздо больше смысла для последующего кода, обрабатывающего полученные результаты.

+0

большое спасибо! это имеет смысл. вероятно, я пойду с обработкой данных на стороне клиента и сохраню структуру как есть. – hopsey

+0

может быть несколько слов о конкретном случае использования. Я готов показать некоторые данные о конкретной дате, элементе и статусе. Я считаю, что доступ к многомерной структуре данных ключей проще, поскольку в моем слое представления он выглядит как результат [date] [id] [status]. так что с точки зрения «плохого дизайна», как вы упомянули, это просто касается источника данных или хранения данных, подобных этому в любом месте? – hopsey

Смежные вопросы