2016-03-03 4 views
1

Я довольно новичок в агрегатах Mongo, и мой текущий Mongo-fu достиг своего предела.MongoDB - Дальнейшее преобразование объединенного списка

В качестве примера, давайте предположим, что сборник «похода» записей со следующей структурой документа:

{ 
    hiker_id: 123, 
    trail: "Dusty Peak" 
} 

Were я использовать

db.hikes.aggregate([{$group: {_id: "$hiker_id", trails: {$addToSet: "$trail"}}}]) 

Я хотел бы получить что-то вроде:

{ 
    _id: 123, 
    trails: ["Dusty Peak", "Windy Falls", "Mushroom Alley", ... 
} 

Однако, если один турист несколько раз поднял один и тот же след, мы увидим повторы в trails список, так что я бы очень хотел это:

{ 
    _id: 123, 
    trails: { "Dusty Peak": 2, 
      "Windy Falls": 1, 
      "Mushroom Alley": 4, 
      ... 
      } 
} 

Резюме, сколько раз путешественник имеет путешествовал пешком каждый след. Как мне это сделать с aggregate?

--или--

Является ли это что-то должно быть сделано вместо того, чтобы в завершить ступеньке Map-Reduce? Собственная документация Mongo говорит, что MR имеет худшую производительность, и производительность имеет решающее значение для того, над чем я работаю.

+0

Я рекомендовал бы отделяя свои проблемы здесь. Я бы сделал обновление с помощью $ addToSet, а затем выполнил конвейеры агрегации и подсчитал ссылки. – jmugz3

+0

Я полностью согласен, и это было бы легко для меня на уровне языка, но на уровне Монго я не уверен в семантике. –

+0

, если вы сначала сделаете свой $ addtoSet, тогда вы можете сделать совокупность и использовать $ sum для подсчета полей. – jmugz3

ответ

4

Оператор $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.

+0

Теперь, когда я думаю об этом, 'trails' является списком объектов, работает лучше. Спасибо! –

+0

@fosskers Спасибо за вотум доверия, но было бы также признательно, если вы [приняли ответ.] (Http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work) –

0

Вы можете сделать это с помощью агрегированных с помощью индекса соединения (фактически группировки по путешествующему и следу, а затем делают второй $ группы по hiker_id, и на этот раз толкая имя следа и сосчитать, например:.

db.hikes.aggregate([ 
    {$group:{_id:{"hiker_id":"$hiker_id", "trail":"$trail"},count:{$sum:1}}}, 
    {$group:{_id:"$_id.hiker_id", trails:{$push:{"trail":"$_id.trail","count":"$count"}}}} 
]) 

Так первая часть группы с compount _id, которая является комбинацией hiker_id и следа, то вторая часть группирует только на hiker_id и заносит имя след и считать

Так данную коллекцию, как это:.

> db.hikes.find() 
{ "_id" : ObjectId("56d8b6bb3e30c2d1435acf96"), "hiker_id" : 123, "trail" : "Dusty Peak" } 
{ "_id" : ObjectId("56d8b6d83e30c2d1435acf97"), "hiker_id" : 123, "trail" : "Foo" } 
{ "_id" : ObjectId("56d8b6da3e30c2d1435acf98"), "hiker_id" : 123, "trail" : "Dusty Peak" } 
{ "_id" : ObjectId("56d8b6de3e30c2d1435acf99"), "hiker_id" : 123, "trail" : "Foo" } 
{ "_id" : ObjectId("56d8b6e63e30c2d1435acf9a"), "hiker_id" : 123, "trail" : "Bar" } 

Вы получите результат, как этот:

{ 
    "_id" : 123, 
    "trails" : [ 
     { 
      "trail" : "Bar", 
      "count" : 1 
     }, 
     { 
      "trail" : "Foo", 
      "count" : 2 
     }, 
     { 
      "trail" : "Dusty Peak", 
      "count" : 2 
     } 
    ] 
} 
Смежные вопросы