2014-08-27 1 views
0

У меня возникла ситуация, когда мне нужно выполнить группу по операции на основе значения массива, которое суммирует вхождения значения поля. Затем отсчет фильтруется, и результаты готовятся так, чтобы они отображались в соответствии с условием. По сути, документы преобразуются обратно к тому, как они будут представлены, если вы просто использовали функцию поиска. Я столкнулся с проблемой слишком больших временных документов из-за количества элементов, собранных в массиве matchedDocuments. Любые предложения о том, как улучшить это, будут полезны.Комплекс MongoDB Aggregation

db.collection1.aggregate([ 
{ 
    '$unwind': '$arrayOfValues' 
}, { 
    '$group': { 
     '_id': '$arrayOfValues', 
     'x_count': { 
      $sum: { 
       $cond: [{ 
         $eq: ['$field.value', 'x'] 
        }, 
        1, 0 
       ] 
      } 
     }, 
     'y_count': { 
      $sum: { 
       $cond: [{ 
         $eq: ['$field.value', 'y'] 
        }, 
        1, 0 
       ] 
      } 
     }, 
     'matchedDocuments': { 
      '$push': '$$CURRENT' 
     } 
    } 
}, 
{'$match': {'$or': [{'x_count': {'$gte': 2}}, {'y_count': { '$gte': 1}}]}}, 
{'$unwind': '$matchedDocuments'}, 
{ 
    '$group': { 
     '_id': '$matchedDocuments.key', 
     'document': { 
      '$last': '$$CURRENT.matchedDocuments' 
     } 
    } 
} 
], { 
    allowDiskUse: true 
}) 

Ниже приведены некоторые примеры документов и ожидаемый результат на основе вышеуказанных критериев:

// Sample documents 

{ "_id" : ObjectId("5407c76b7b1c276c74f90524"), "field" : "x", "arrayOfValues" : [ "a", "b", "c" ] } 
{ "_id" : ObjectId("5407c76b7b1c276c74f90525"), "field" : "x", "arrayOfValues" : [ "b", "c" ] } 
{ "_id" : ObjectId("5407c76b7b1c276c74f90526"), "field" : "z", "arrayOfValues" : [ "a" ] } 
{ "_id" : ObjectId("5407c76b7b1c276c74f90527"), "field" : "x", "arrayOfValues" : [ "a", "c" ] } 
{ "_id" : ObjectId("5407c76b7b1c276c74f90528"), "field" : "z", "arrayOfValues" : [ "b" ] } 
{ "_id" : ObjectId("5407c76b7b1c276c74f90529"), "field" : "y", "arrayOfValues" : [ "k" ] } 


// Expected Result 

[ 
    { "_id" : ObjectId("5407c76b7b1c276c74f90524"), "field" : "x", "arrayOfValues" : [ "a", "b", "c" ] } 
    { "_id" : ObjectId("5407c76b7b1c276c74f90525"), "field" : "x", "arrayOfValues" : [ "b", "c" ] } 
    { "_id" : ObjectId("5407c76b7b1c276c74f90527"), "field" : "x", "arrayOfValues" : [ "a", "c" ] } 
    { "_id" : ObjectId("5407c76b7b1c276c74f90529"), "field" : "y", "arrayOfValues" : [ "k" ] } 
] 
+1

Проблема, несомненно, связана с первым ключом группировки. Но поскольку вы группируете значения из массива, который вы только что размотали, трудно понять, что вы на самом деле пытаетесь сделать здесь. Образец документа и ожидаемые результаты обычно лучше всего объясняют ваши намерения. –

+0

Я добавил примеры документов и ожидаемых результатов, чтобы помочь визуализировать проблему. – user1595702

+0

Почему бы просто не посчитать количество документов в массиве 'arrayOfValues'? Это сделало бы агрегацию простой находкой для каждого значения 'field'. – wdberkeley

ответ

2

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

Для меня я просто попытаюсь определить, какие условия в документе приведут к совпадению, а затем выдаст отдельный запрос, чтобы вернуть фактические документы. Вы можете адаптировать агрегацию ниже, чтобы попытаться вернуть документ, но я думаю, что это очень вероятно, если это произойдет, поскольку это будет обратное тому, для чего вы должны использовать массивы.

Процесс также, как правило, намного более эффективен в том, как происходит согласование, в котором вы, во-первых, «выбираете элементы, которые вас интересуют, с условием соответствия», а во-вторых, «используйте условия естественной группировки, а не полагайтесь на условные суммы ".

var cursor = db.collection.aggregate([ 
    { "$match": { "field": { "$in": ["x", "y"] } } }, 
    { "$unwind": "$arrayOfValues" }, 
    { "$group": { 
     "_id": { 
      "elem": "$arrayOfValues", 
      "field": "$field" 
     }, 
     "count": { "$sum": 1 } 
    }}, 
    { "$match": { 
     "$or": [ 
      { "_id.field": "x", "count": { "$gte": 2 } }, 
      { "_id.field": "y", "count": { "$gte": 1 } } 
     ] 
    }}, 
    { "$group": { 
     "_id": "$_id.field", 
     "values": { "$push": "$_id.elem" } 
    }} 
]) 

var query = { "$or": [] }; 

cursor.forEach(function(doc) { 
    query["$or"].push({ 
     "field": doc._id, 
     "arrayOfValues": { "$in": doc.values } 
    }); 
}); 

db.collection.find(query) 

Для записи запрос должен выйти как это, учитывая Предоставленные данные:

{ 
    "$or" : [ 
     { 
      "field" : "x", 
      "arrayOfValues" : { 
       "$in" : [ 
        "c", 
        "b", 
        "a" 
       ] 
      } 
     }, 
     { 
      "field" : "y", 
      "arrayOfValues" : { 
       "$in" : [ 
        "k" 
       ] 
      } 
     } 
    ] 
} 

Основная логика удовлетворяется просто ищем значения «поля», которые вы заинтересованы в , таким образом, по крайней мере, устраняя все остальные из возможных результатов. Затем вы в основном хотите подсчитать подсчеты для каждого элемента массива под каждым из этих значений поля и проверить, где были выполнены необходимые вхождения.

Это может быть или не работать лучше всего наоборот, но здесь образец показывает наибольшую вариацию «arrayOfValues», поэтому это имеет смысл как второй уровень группировки.

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

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

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

+0

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

+0

@ user1595702, если вы говорите о выпуске отдельных запросов, тогда нет лучшего подхода, и я думаю, что объяснил это. Рамка агрегации является мощной, но остерегайтесь «хвоста, виляющего собаку». Делайте вещи разумно. –