Оператор $addToSet
действительно просто еще один вид $group
, но только содержащиеся в записи массива в качестве результата. Поэтому, чтобы подсчитать происхождение этих ключей, просто «сгруппируйте» их. Второй $group
может поместить их в массив:
db.hikes.aggregate([
// Group on distinct trail per hiker
{ "$group": {
"_id": {
"hiker": "$hiker_id",
"trail": "$trail"
},
"count": { "$sum": 1 }
}},
// Now roll-up per hiker and push to array
{ "$group": {
"_id": "$_id.hiker",
"trails": {
"$push": { "name": "$_id.trail", "count": "$count" }
}
}}
])
Это дает вам результат, как:
{
"_id": 123,
"trails": [
{ "name": "Dusty Peak", "count": 2 },
{ "name": "Windy Falls", "count": 1 },
{ "name": "Mushroom Alley", "count": 4 }
]
}
Если вы думаете об этом, то на самом деле все результаты, которые вы должны фактически достигнутые в первом $group
этап трубопровода, хотя и в одном документе за тропу на одного туриста. Все второе $group
делает (и на самом деле довольно быстро) просто «свертывает» результаты на одного туриста, добавляя остальную информацию в массив.
Это не то же самое, что вы предлагаете, но это то, что делает инфраструктура агрегации. Он никак не преобразует «данные» в «ключи». ИМХО, это хорошо, потому что я не думаю, что «названные ключи», которые представляют точки данных, являются хорошей идеей. Вышеприведенное чисто и легко повторяется как естественный массив. И, конечно, все необходимые данные есть.
Если вы действительно сердце набора на трансформацию модели для ключей, то выше все еще применяется, и это лучше всего делать на стороне tranformation клиента:
db.hikes.aggregate([
// Group on distinct trail per hiker
{ "$group": {
"_id": {
"hiker": "$hiker_id",
"trail": "$trail"
},
"count": { "$sum": 1 }
}},
// Now roll-up per hiker and push to array
{ "$group": {
"_id": "$_id.hiker",
"trails": {
"$push": { "name": "$_id.trail", "count": "$count" }
}
}}
]).forEach(function(doc) {
var newTrails = {};
doc.trails.forEach(function(trail) {
newTrails[trail.name] = trail.count;
});
doc.trails = newTrails;
printjson(doc);
})
или в основном, что подобная картина итератора в реализации любого языка использовать.
Для записи, MapReduce способа сделать это будет:
db.hikes.mapReduce(
function() {
var data = {};
data[this.trail] = 1;
emit(this.hiker_id,data);
},
function(key,values) {
var result = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(key) {
if (!result.hasOwnProperty(key))
result[key] = 0;
result[key] += value[key];
})
});
return result;
},
{ "out": { "inline": 1 } }
)
Что на мой взгляд, это глупо, так как дополнительные «группировка» опирается на перебор ключей объектов. Результат также имеет собственные MapReduce причуды:
{
"_id": 123,
"value": {
"Dusty Peak": 2,
"Mushroom Alley": 4,
"Windy Falls": 1
}
}
Думал, что это все сделано на сервере, это не без его затрат, а не только в interpretaion JavaScript. Процесс mapReduce работает, часто вызывая функцию reducer
несколько раз, а это означает, что выход редуктора может на самом деле заканчиваться по мере его ввода (ключевая точка дизайна). С этой точки зрения это означает, что при последовательных проходах объект результата будет «расти», а это означает больше накладных расходов при повторении и тестировании наличия ключей.
Процесс альтернативной структуры агрегации обрабатывает это гораздо более естественным образом и с эффективными алгоритмами в коллекции данных $group
.
Я рекомендовал бы отделяя свои проблемы здесь. Я бы сделал обновление с помощью $ addToSet, а затем выполнил конвейеры агрегации и подсчитал ссылки. – jmugz3
Я полностью согласен, и это было бы легко для меня на уровне языка, но на уровне Монго я не уверен в семантике. –
, если вы сначала сделаете свой $ addtoSet, тогда вы можете сделать совокупность и использовать $ sum для подсчета полей. – jmugz3