Для MongoDB 3.6 и новее используйте оператор $objectToArray
в конвейере агрегации для преобразования документа в массив. Возвращаемый массив содержит элемент для каждой пары field/value в исходном документе. Каждый элемент в возвращаемом массиве представляет собой документ, который содержит два поля: k
и v
.
Получив массив, вы можете использовать использование $addFields
ступени конвейера, чтобы создать поле, которое удерживает отсчеты и фактический отсчет происходят с использованием оператора в $size
.
Все это можно сделать в одном трубопроводе путем вложения выражения следующим образом:
db.collection.aggregate([
{
"$addFields": {
"answers_count": {
"$size": {
"$objectToArray": "$answer_records"
}
}
}
}
])
Пример вывода
{
"_id" : ObjectId("57eb386e37b4842ff5f386c9"),
"lesson_id" : ObjectId("57e27cd190e6993e393f5c74"),
"student_id" : ObjectId("57d3c3f590e6995fe8de7932"),
"answer_records" : {
"1" : {
"answer" : [
"A"
]
},
"3" : {
"answer" : [
"C"
]
}
},
"answers_count": 2
}
Для версий сервера MongoDB, которые не поддерживают выше операторов, вам нужно будет изменить дизайн схемы, чтобы выполнять эффективные запросы с помощью структуры агрегации. Поскольку в настоящее время вам понадобится для предварительной обработки документов на клиенте или сервере с помощью JavaScript, вы не сможете полностью использовать лучшую инфраструктуру MongoDB, созданную для более быстрого запроса.
Идеальная конструкция следующим образом:
{
"_id" : ObjectId("57eb386e37b4842ff5f386c9"),
"lesson_id" : ObjectId("57e27cd190e6993e393f5c74"),
"student_id" : ObjectId("57d3c3f590e6995fe8de7932"),
"answer_records" : [
{ "id": "1", "answer": "A" }
{ "id": "3", "answer": "C" }
]
}
, который вы можете просто применить $project
трубопровода агрегации, которая использует $size
оператора вернуть длину массива answer_records в документ:
db.collection.aggregate([
{
"$project": {
"lesson_id": 1,
"student_id": 1,
"count": { "$size": "$answer_records" }
}
}
])
Если вы хотите получить общее количество записей ответов для всей коллекции, добавьте еще $group
трубопровода, чтобы получить накопленную сумму для всех документов с использованием _id нуль:
db.collection.aggregate([
{
"$project": {
"count": { "$size": "$answer_records" }
}
},
{
"$group": {
"_id": null,
"total_answers": { "$sum": "$count" }
}
}
])
В противном случае с текущим дизайном единственным вариантом является MapReduce, который намного медленнее:
db.collection.mapReduce(
function() {
emit(this._id, Object.keys(this.answer_records).length);
},
function() { },
{ "out": { "inline": 1 } }
)
Выход для образца:
{
"results" : [
{
"_id" : ObjectId("57eb386e37b4842ff5f386c9"),
"value" : 2
}
],
....
}
Чтобы получить общее для всех документов в коллекции, то запустить эту операцию MapReduce:
db.collection.mapReduce(
function() {
emit(null, Object.keys(this.answer_records).length);
},
function(key, values) {
return Array.sum(values);
},
{ "out": { "inline": 1 } }
)