Страницы

Поиск по вопросам

Показаны сообщения с ярлыком aggregation-framework. Показать все сообщения
Показаны сообщения с ярлыком aggregation-framework. Показать все сообщения

среда, 18 декабря 2019 г.

Поиск документов, значение ключа у которых не выходит за рамки полученного массива

#mongodb #aggregation_framework #mongodb_query


Мне нужно получить массив от пользователя и сделать выборку из базы по следующему
принципу:
если полученный массив такой

animals = ['cat', 'dog', 'penguin']


то мне нужны документы где

animals = ['cat', 'dog', 'penguin']
animals = ['cat', 'dog']
animals = ['cat' 'penguin']
и т.д.


Подскажите, пожалуйста, как вообще называются такие выборки и как их правильно делать
в mongodb.
    


Ответы

Ответ 1



Если запрос достаточно сложный, и базовых средств либо знаний не хватает, то всегда можно воспользоваться механизмом $where. То есть можно написать сложную логику получения данных, включая if-ы, и вернуть результат, чего в обычных базах данных добиться очень и очень сложно (имею в виду условия и циклы внутри SQL-запросов). Синтаксис прост — в качестве параметра передаётся функция: db.foo.find({$where : функция}) При этом используется объект (переменная) this, по сути — курсор, у которой можно получать свойства. Функция должна вернуть true или false, что означает, попадёт строка в выборку или нет. Вот пример из википедии: db.foo.find({$where : function() { return this.x == this.y; }}) При этом также можно использовать вложенные OR или AND, как в классических SQL-запросах: db.foo.find({$where : function() { if((this.cost>5 && this.cost<10 )|| (this.is_hidden != 1) || (this.link == this.url)){ return true; } return false; }}) Таким образом, курсор пробегает по базе, и выполняет проверки. разумеется, всё это можно ограничивать лимитами на количество строк, $where — всего лишь ключ в массиве параметров запроса. Необходимо понимать, что это всё-таки медленне, чем обычные запросы, но всё равно достаточно быстро. скопировано отсюда по наводке из комментария автора вопроса.

Ответ 2



Я полагаю, у вас есть следующие документы в вашей коллекции. { "_id" : ObjectId("56ac7281ebc094e3677b809f"), "animals" : [ "rat", "cat" ] }, { "_id" : ObjectId("56ac7281ebc094e3677b80a0"), "animals" : [ "dog", "cat", "penguin" ] }, { "_id" : ObjectId("56ac7281ebc094e3677b80a1"), "animals" : [ "dog" ] }, { "_id" : ObjectId("56ac7281ebc094e3677b80a2"), "animals" : [ "cat", "dog" ] }, { "_id" : ObjectId("56ac7281ebc094e3677b80a3"), "animals" : [ "dog", "penguin" ] } и массив "animals": var animals = [ "cat", "dog", "penguin" ]; Лучший способ это делать это используя .aggregate() метод который обеспечивает доступ к агрегации цепочке. Первый этап в цепочке это $match где вы отфильтроваете документы, которые не имеют какой-либо элемента в массиве "animals". Это также уменьшает количество документов, которые будут обрабатываться в следующей стадии. Следующий и последний этап — это $redact. Здесь нужно использовать оператор $setIsSubset точно делает что мы хотим; он возврашает true если все элементы первого множество появляются во втором множество, в том числе, когда первый множеств равен второй; и также потому MongoDB не позволяет использовать $in в $cond. Конечно $$KEEP и $$PRUNE являются системные переменные. db.collection.aggregate([ { "$match": { "animals": { "$in": animals } } }, { "$redact": { "$cond": [ { "$setIsSubset": [ "$animals", animals ] }, "$$KEEP", "$$PRUNE" ] }} ]) Это запрос возврашает следующий результат: { "_id" : ObjectId("56ac7281ebc094e3677b80a0"), "animals" : [ "dog", "cat", "penguin" ] } { "_id" : ObjectId("56ac7281ebc094e3677b80a1"), "animals" : [ "dog" ] } { "_id" : ObjectId("56ac7281ebc094e3677b80a2"), "animals" : [ "cat", "dog" ] } { "_id" : ObjectId("56ac7281ebc094e3677b80a3"), "animals" : [ "dog", "penguin" ] } Здесь вам не нужно использовать оператор $where поскольку он вызовет падение производительности. $where evaluates JavaScript and cannot take advantage of indexes. Therefore, query performance improves when you express your query using the standard MongoDB operators (e.g., $gt, $in). In general, you should use $where only when you can’t express your query using another operator. If you must use $where, try to include at least one other standard query operator to filter the result set. Using $where alone requires a table scan. Конечно $redact не использует индексы. Он даже делает сканирования все документы в коллекции но более лучше.

суббота, 14 декабря 2019 г.

Как обновить несколько элементов в массиве?

#mongodb #aggregation_framework #mongodb_query


Необходимо обновить одним запросом несколько данных. Вот пример запроса:

db.coll.update(
  { article: 100500 },
  {
    $set: { a: 555 },
    $pull: { arr: { t: { $lt: 20 } } }
  }
);


А вот пример самого документа:

{
  article: 100500,
  a: 400,
  arr: [
    { t: 30, b: 12, n: 90 },
    { t: 10, b: 16, n: 60 }
  ]
}


Мой запрос:


Обновляет значение a на 555.
Удаляет все элементы массива arr, где t < 20.


Суть вопроса:
Надо обновить значения b в маccве arr. То есть, везде где n == 90, значение b надо
поменять на 777. Как можно дополнить этот запрос, чтобы "убить сразу 3-х зайцев"? 

В итоге должен получиться такой документ:

{
  article: 100500,
  a: 555, /* Тут было: 400 */
  arr: [
    { t: 30, b: 777, n: 90 },
    /* Тут был элемент массива */
  ]
}

    


Ответы

Ответ 1



Я полагаю, у вас есть следующие документы в вашей коллекции. { "_id" : ObjectId("56b71025973d202a52a5e650"), "article" : 100500, "a" : 400, "arr" : [ { "t" : 30, "b" : 12, "n" : 90 }, { "t" : 20, "b" : 16, "n" : 60 } ] }, { "_id" : ObjectId("56b7102b973d202a52a5e651"), "article" : 100500, "a" : 400, "arr" : [ { "t" : 0, "b" : 12, "n" : 90 }, { "t" : 10, "b" : 16, "n" : 60 } ] }, { "_id" : ObjectId("56b710d4973d202a52a5e652"), "article" : 100500, "a" : 400, "arr" : [ { "t" : 30, "b" : 12, "n" : 90 }, { "t" : 27, "b" : 16, "n" : 32 } ] } Прежде всего вы должны знать, что это не возможно, чтобы обновить более одного элемента в массиве используя метод update() даже с опцей multi: true или используя метод updateMany(); и все логики позади, что вы пытаетесь сделать; делать вещи более сложнее. Лучший способ это делать это используя Bulk Операции. Решение для MongoDB версия 3.2 или новее: MongoDB 3.2 не рекомендуется Bulk() и связанные с ним методы. Нужно использовать метод .bulkWrite() Здесь у нас есть два варианта: Нужно использовать агрегация чтотбы уменшать количество докуменьов которые нужно обновить. здесь в цепочке нужно только один этап: $project где мы используем оператор $filter. let requests = []; db.collection.aggregate([ { "$project": { "deleteElements": { "$filter": { "input": "$arr", "as": "del", "cond": { "$lt": [ "$$del.t", 12 ] } } }, "updateElements": { "$filter": { "input": "$arr", "as": "upd", "cond": { "$and": [ { "$gte": [ "$$upd.t", 12 ] }, { "$eq": [ "$$upd.n", 90 ] } ] } } } }} ]).forEach(function(document) { document.deleteElements.forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": element.t }, "update": { "$pull": { "arr": element } } } } ); }); document.updateElements.forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": element.n }, "update": { "$set": { "arr.$.b": 777 } } } } ); }); requests.push( { "updateOne": { "filter": { "_id": document._id }, "update": { "$set": { "a": 555 } } } } ); }) db.collection.bulkWrite(requests) Используя метод .find() db.collection.find({"article": 100500}).snapshot().forEach(function(document) { document.arr.filter(function(arr) { return arr.t < 12; }).forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": { "$lt": 12 }}, "update": { "$pull": { "arr": element } } } } ); }); document.arr.filter(function(arr) { return arr.n === 90; }).forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.n": 90 }, "update": { "$set": { "arr.$.b": 777 } } } } ); }); requests.push( { "updateOne": { "filter": { "_id": document._id }, "update": { "$set": { "a": 555 } } } } ); }) db.collection.bulkWrite(requests); Решение для MongoDB версии 2.6 или новее: var bulk = db.collection.initializeOrderedBulkOp(); var count = 0; db.collection.find({"article": 100500}).snapshot().forEach(function(document) { document.arr.filter(function(arr) { return arr.t < 12; }).forEach(function(element) { bulk.find({ "_id": document._id, "arr.t": { "$lt": 12 } } ).updateOne({ "$pull": { "arr": element } }); count++; }); document.arr.filter(function(arr) { return arr.n === 90; }).forEach(function(element) { bulk.find({ "_id": document._id, "arr.n": 90 }).updateOne({ "$set": { "arr.$.b": 777 } }); count++; }); bulk.find( { "_id": document._id } ).updateOne( { "$set": { "a": 555 } } ); count++; if (count % 1000 === 0) { // Выпольнить после 1000 операции bulk.execute(); bulk = db.collection.initializeOrderedBulkOp(); } }) // Очистить очереди if (count > 0) { bulk.execute(); } Результать { "_id" : ObjectId("56b71025973d202a52a5e650"), "article" : 100500, "a" : 555, "arr" : [ { "t" : 30, "b" : 777, "n" : 90 }, { "t" : 20, "b" : 16, "n" : 60 } ] } { "_id" : ObjectId("56b7102b973d202a52a5e651"), "article" : 100500, "a" : 555, "arr" : [ ] } { "_id" : ObjectId("56b710d4973d202a52a5e652"), "article" : 100500, "a" : 555, "arr" : [ { "t" : 30, "b" : 777, "n" : 90 }, { "t" : 27, "b" : 16, "n" : 32 } ] }

вторник, 10 декабря 2019 г.

Как оставить в массиве элемент с максимальным значением

#mongodb #aggregation_framework #mongodb_query


У меня следующие документы в коллекции

{
   "_id": 1,
   "values":[
      {
         "value1":10,
         "value2":12,
         "value3":30
      },
      {
         "value1":20,
         "value2":12,
         "value3":100
      },
      {
         "value1":30,
         "value2":14,
         "value3":50
      }
   ]
},
{
   "_id": 2,
   "values":[
      {
         "value1":10,
         "value2":12,
         "value3":60
      },
      {
         "value1":20,
         "value2":12,
         "value3":80
      },
      {
         "value1":30,
         "value2":14,
         "value3":70
      }
   ]
}


Мне нужно оставить один документ с одним элементом в массиве по следующим критериям:

values.value1 = 20
values.value2 = 12
values.value3  // максимальное значение из всех значений values.value3 во всех документах


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

{
   "_id": 1,
   "values":[
      {
         "value1":20,
         "value2":12,
         "value3":100
      }
   ]
}


Также, подойдет решение, просто сортирующие документы и элементы по указанным критериям.
Чтобы первым документом был тот, в котором значение values.value3 максимально из возможных
и первым элементом в массиве был объект с максимальным значением. Например:

{
   "_id": 1,
   "values":[
     {
         "value1":20,
         "value2":12,
         "value3":100
      },
      {
         elements...
      }
   ]
},
{
   "_id": 2,
   "values":[
      {
         elements...
      }
   ]
}


Сделать это у меня получается, но я использую $match, $unwind, $group, $redact, $sort
по несколько раз. Может быть кто-то предложит более правильную реализацию.
    


Ответы

Ответ 1



Лучший способ это запустить два запроса. Первый, чтобы нашел максимальное значение и второй, чтобы остался только документ, где value3 равно этому значению. Решение для MongoDB версия 3.2 или новее: Начиная с версии 3.2 можно использовать оператор $max, который возвращает максимальное значение для каждого документа в $project этапе. Оператор $map здесь возвращает массив value3. Следующий этап в цепочке — это $group, где нужно группировать документы и возвращать максимальное значение. var result = db.collection.aggregate([ { "$project": { "maxVal": { "$max": { "$map": { "input": "$values", "as": "value", "in": "$$value.value3" } } } } }, { "$group": { "_id": null, "mval": { "$max": "$maxVal" } } } ]); Поскольку метод .aggregate() возвращает курсор, нужно использовать метод toArray() и оператор [] для получения значения. var maximumValue = result.toArray()[0]['mval']; Теперь можно использовать это значение, чтобы оставить в массиве только элемент с максимальным значением. Для этого нужно использовать оператор $filter db.collection.aggregate( [ { "$match": { "values.value3": maximumValue } }, { "$project": { "values": { "$filter": { "input": "$values", "as": "value", "cond": { "$eq": [ "$$value.value3", maximumValue ] } } } } } ]) Решение для MongoDB версии 2.6 или новее: Решение для версий с 2.6 по 3.2 менее эффективно. Сначала в первом запросе нужно денормализовать массив value3 сразу после $project, используя оператор $unwind. Последний этап в цепочке это $group, где группируем документы и возвращаем максимальное значение value3 var result = db.collection.aggregate([ { "$project": { "value3": { "$map": { "input": "$values", "as": "value", "in": "$$value.value3" } } } }, { "$unwind": "$value3" }, { "$group": { "_id": null, "mval": { "$max": "$value3" } } } ]); var maximumValue = result.toArray()[0]['mval']; Чтобы оставить в массиве только элемент с максимальным значением, у нас есть два варианта: Используя оператор $redact db.collection.aggregate( [ { "$match": { "values.value3": maximumValue } }, { "$redact": { "$cond": [ { "$or": [ { "$eq": [ "$value3", maximumValue ] }, { "$not": "$value3" } ]}, "$$DESCEND", "$$PRUNE" ] }} ]) Используя оператор $project и $setDifference db.collection.aggregate( [ { "$match": { "values.value3": maximumValue } }, { "$project": { "values": { "$setDifference": [ { "$map": { "input": "$values", "as": "value", "in": { "$cond": [ { "$eq": [ "$$value.value3", maximumValue ] }, "$$value", false ] } }}, [false] ] } }} ])

среда, 10 июля 2019 г.

Mongodb aggregate (агрегация/группировка товаров)

Решил поупражняться с парсерами и работой с mongodb, но столкнулся со следующей проблемой.
Имеется коллекция с товарами. Для примера возьмем следующие документы из нее:
{title: 'acer aspire 6420', source: 'amazon', price: 300} {title: 'acer aspire 6420', source: 'ebay', price: 320}
Из них необходимо получить следующий документ:
{title: 'acer aspire 6420', amazon: 300, ebay: 320}
то есть сгруппировать по названию и вывести цену в каждом из магазинов.
В ходе разбирательства написал следующий код на питоне:
from bson.code import Code
reducer = Code(""" function(origin, res){ res[origin.source] = origin.price } """)
db.products.group(key={"source":1, 'title': 1, 'price': 1}, condition={'title': 'acer aspire 6420'}, initial={}, reduce=reducer)
но по итогу получаю вот такой результат:
{title: 'acer aspire 6420', source: 'amazon', price: 300, amazon: 300, ebay: 320} {title: 'acer aspire 6420', source: 'ebay', price: 320, amazon: 300, ebay: 320}
Подскажите, пожалуйста, в какую сторону дальше копать или как это реализовать, чтобы результат получился таким:
{title: 'acer aspire 6420', amazon: 300, ebay: 320}
Я гуглил и читал документацию, правда. Просто не очень понимаю как это все правильно связать вместе.


Ответ

Вариант с aggregation framework (для mongoshell):
db.products.aggregate([ {$match: {title: "acer aspire 6420"}}, {$project: {_id: 0, title: 1, price: {source: "$source", value: "$price"} }}, {$group: {_id: "$title", prices: {$push: "$price"}}} ])
Что дает на выходе:
{ "_id" : "acer aspire 6420", "prices" : [ { "source" : "amazon", "value" : 300 }, { "source" : "ebay", "value" : 320 } ] }
Первая операция фильтрует по названию (title) товара, следующая формирует поле price в виде объекта с двумя полями - source и value, и последняя операция группирует по title, добавляя все варианты цен в создаваемое поле-массив prices
Т.к. у вас по условию задачи source должен быть именем поля в результате, а пока в mongodb aggregation framework поддержки динамических имен для создаваемых полей нет (https://jira.mongodb.org/browse/SERVER-5947), то можно пройтись по результату с помощью map
db.products.aggregate([ {$match: {title: "acer aspire 6420"}}, {$project: {_id: 0, title: 1, price: {source: "$source", value: "$price"} }}, {$group: {_id: "$title", prices: {$push: "$price"}}} ]).map(function(e) { var r = {} r.title = e._id; e.prices.forEach(function(i){r[i.source] = i.value}); return r; })
Что даст на выходе:
[ { "title" : "acer aspire 6420", "amazon" : 300, "ebay" : 320 } ]
В этом случае есть вероятность (в отличии от первого варианта) потерять информацию, если у вас, например, для amazon есть две разных цены.
Для питона, думаю, сможете сделать по аналогии.

вторник, 23 октября 2018 г.

Как обновить несколько элементов в массиве?

Необходимо обновить одним запросом несколько данных. Вот пример запроса:
db.coll.update( { article: 100500 }, { $set: { a: 555 }, $pull: { arr: { t: { $lt: 20 } } } } );
А вот пример самого документа:
{ article: 100500, a: 400, arr: [ { t: 30, b: 12, n: 90 }, { t: 10, b: 16, n: 60 } ] }
Мой запрос:
Обновляет значение a на 555 Удаляет все элементы массива arr, где t < 20
Суть вопроса: Надо обновить значения b в маccве arr. То есть, везде где n == 90, значение b надо поменять на 777. Как можно дополнить этот запрос, чтобы "убить сразу 3-х зайцев"?
В итоге должен получиться такой документ:
{ article: 100500, a: 555, /* Тут было: 400 */ arr: [ { t: 30, b: 777, n: 90 }, /* Тут был элемент массива */ ] }


Ответ

Я полагаю, у вас есть следующие документы в вашей коллекции.
{ "_id" : ObjectId("56b71025973d202a52a5e650"), "article" : 100500, "a" : 400, "arr" : [ { "t" : 30, "b" : 12, "n" : 90 }, { "t" : 20, "b" : 16, "n" : 60 } ] }, { "_id" : ObjectId("56b7102b973d202a52a5e651"), "article" : 100500, "a" : 400, "arr" : [ { "t" : 0, "b" : 12, "n" : 90 }, { "t" : 10, "b" : 16, "n" : 60 } ] }, { "_id" : ObjectId("56b710d4973d202a52a5e652"), "article" : 100500, "a" : 400, "arr" : [ { "t" : 30, "b" : 12, "n" : 90 }, { "t" : 27, "b" : 16, "n" : 32 } ] }

Прежде всего вы должны знать, что это не возможно, чтобы обновить более одного элемента в массиве используя метод update() даже с опцей multi: true или используя метод updateMany(); и все логики позади, что вы пытаетесь сделать; делать вещи более сложнее.
Лучший способ это делать это используя Bulk Операции
Решение для MongoDB версия 3.2 или новее:
MongoDB 3.2 не рекомендуется Bulk() и связанные с ним методы. Нужно использовать метод .bulkWrite()
Здесь у нас есть два варианта:
Нужно использовать агрегация чтотбы уменшать количество докуменьов которые нужно обновить. здесь в цепочке нужно только один этап: $project где мы используем оператор $filter
let requests = [];
db.collection.aggregate([ { "$project": { "deleteElements": { "$filter": { "input": "$arr", "as": "del", "cond": { "$lt": [ "$$del.t", 12 ] } } }, "updateElements": { "$filter": { "input": "$arr", "as": "upd", "cond": { "$and": [ { "$gte": [ "$$upd.t", 12 ] }, { "$eq": [ "$$upd.n", 90 ] } ] } } } }} ]).forEach(function(document) { document.deleteElements.forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": element.t }, "update": { "$pull": { "arr": element } } } } ); }); document.updateElements.forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": element.n }, "update": { "$set": { "arr.$.b": 777 } } } } ); }); requests.push( { "updateOne": { "filter": { "_id": document._id }, "update": { "$set": { "a": 555 } } } } ); })
db.collection.bulkWrite(requests) Используя метод .find()
db.collection.find({"article": 100500}).snapshot().forEach(function(document) { document.arr.filter(function(arr) { return arr.t < 12; }).forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": { "$lt": 12 }}, "update": { "$pull": { "arr": element } } } } ); }); document.arr.filter(function(arr) { return arr.n === 90; }).forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.n": 90 }, "update": { "$set": { "arr.$.b": 777 } } } } ); }); requests.push( { "updateOne": { "filter": { "_id": document._id }, "update": { "$set": { "a": 555 } } } } ); })
db.collection.bulkWrite(requests);

Решение для MongoDB версии 2.6 или новее:
var bulk = db.collection.initializeOrderedBulkOp(); var count = 0; db.collection.find({"article": 100500}).snapshot().forEach(function(document) { document.arr.filter(function(arr) { return arr.t < 12; }).forEach(function(element) { bulk.find({ "_id": document._id, "arr.t": { "$lt": 12 } } ).updateOne({ "$pull": { "arr": element } }); count++; }); document.arr.filter(function(arr) { return arr.n === 90; }).forEach(function(element) { bulk.find({ "_id": document._id, "arr.n": 90 }).updateOne({ "$set": { "arr.$.b": 777 } }); count++; }); bulk.find( { "_id": document._id } ).updateOne( { "$set": { "a": 555 } } ); count++; if (count % 1000 === 0) { // Выпольнить после 1000 операции bulk.execute(); bulk = db.collection.initializeOrderedBulkOp(); } })
// Очистить очереди if (count > 0) { bulk.execute(); }

Результать
{ "_id" : ObjectId("56b71025973d202a52a5e650"), "article" : 100500, "a" : 555, "arr" : [ { "t" : 30, "b" : 777, "n" : 90 }, { "t" : 20, "b" : 16, "n" : 60 } ] } { "_id" : ObjectId("56b7102b973d202a52a5e651"), "article" : 100500, "a" : 555, "arr" : [ ] } { "_id" : ObjectId("56b710d4973d202a52a5e652"), "article" : 100500, "a" : 555, "arr" : [ { "t" : 30, "b" : 777, "n" : 90 }, { "t" : 27, "b" : 16, "n" : 32 } ] }

вторник, 16 октября 2018 г.

Как оставить в массиве элемент с максимальным значением

У меня следующие документы в коллекции
{ "_id": 1, "values":[ { "value1":10, "value2":12, "value3":30 }, { "value1":20, "value2":12, "value3":100 }, { "value1":30, "value2":14, "value3":50 } ] }, { "_id": 2, "values":[ { "value1":10, "value2":12, "value3":60 }, { "value1":20, "value2":12, "value3":80 }, { "value1":30, "value2":14, "value3":70 } ] }
Мне нужно оставить один документ с одним элементом в массиве по следующим критериям:
values.value1 = 20 values.value2 = 12 values.value3 // максимальное значение из всех значений values.value3 во всех документах
Например, по вышеуказанным критериям должен получиться такой документ:
{ "_id": 1, "values":[ { "value1":20, "value2":12, "value3":100 } ] }
Также, подойдет решение, просто сортирующие документы и элементы по указанным критериям. Чтобы первым документом был тот, в котором значение values.value3 максимально из возможных и первым элементом в массиве был объект с максимальным значением. Например:
{ "_id": 1, "values":[ { "value1":20, "value2":12, "value3":100 }, { elements... } ] }, { "_id": 2, "values":[ { elements... } ] }
Сделать это у меня получается, но я использую $match, $unwind, $group, $redact, $sort по несколько раз. Может быть кто-то предложит более правильную реализацию.


Ответ

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

Решение для MongoDB версия 3.2 или новее:
Начиная с версии 3.2 можно использовать оператор $max, который возвращает максимальное значение для каждого документа в $project этапе. Оператор $map здесь возвращает массив value3.
Следующий этап в цепочке — это $group, где нужно группировать документы и возвращать максимальное значение.
var result = db.collection.aggregate([ { "$project": { "maxVal": { "$max": { "$map": { "input": "$values", "as": "value", "in": "$$value.value3" } } } } }, { "$group": { "_id": null, "mval": { "$max": "$maxVal" } } } ]);
Поскольку метод .aggregate() возвращает курсор, нужно использовать метод toArray() и оператор [] для получения значения.
var maximumValue = result.toArray()[0]['mval'];
Теперь можно использовать это значение, чтобы оставить в массиве только элемент с максимальным значением. Для этого нужно использовать оператор $filter
db.collection.aggregate( [ { "$match": { "values.value3": maximumValue } }, { "$project": { "values": { "$filter": { "input": "$values", "as": "value", "cond": { "$eq": [ "$$value.value3", maximumValue ] } } } } } ])

Решение для MongoDB версии 2.6 или новее:
Решение для версий с 2.6 по 3.2 менее эффективно. Сначала в первом запросе нужно денормализовать массив value3 сразу после $project, используя оператор $unwind. Последний этап в цепочке это $group, где группируем документы и возвращаем максимальное значение value3
var result = db.collection.aggregate([ { "$project": { "value3": { "$map": { "input": "$values", "as": "value", "in": "$$value.value3" } } } }, { "$unwind": "$value3" }, { "$group": { "_id": null, "mval": { "$max": "$value3" } } } ]);
var maximumValue = result.toArray()[0]['mval'];
Чтобы оставить в массиве только элемент с максимальным значением, у нас есть два варианта:
Используя оператор $redact
db.collection.aggregate( [ { "$match": { "values.value3": maximumValue } }, { "$redact": { "$cond": [ { "$or": [ { "$eq": [ "$value3", maximumValue ] }, { "$not": "$value3" } ]}, "$$DESCEND", "$$PRUNE" ] }} ]) Используя оператор $project и $setDifference
db.collection.aggregate( [ { "$match": { "values.value3": maximumValue } }, { "$project": { "values": { "$setDifference": [ { "$map": { "input": "$values", "as": "value", "in": { "$cond": [ { "$eq": [ "$$value.value3", maximumValue ] }, "$$value", false ] } }}, [false] ] } }} ])