Получить только запрошенный элемент в массиве объектов в коллекции MongoDB
Предположим, у вас есть следующие документы в моей коллекции:
{
"_id":ObjectId("562e7c594c12942f08fe4192"),
"shapes":[
{
"shape":"square",
"color":"blue"
},
{
"shape":"circle",
"color":"red"
}
]
},
{
"_id":ObjectId("562e7c594c12942f08fe4193"),
"shapes":[
{
"shape":"square",
"color":"black"
},
{
"shape":"circle",
"color":"green"
}
]
}
Do query:
db.test.find({"shapes.color": "red"}, {"shapes.color": 1})
или
db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})
возвращает согласованный документ (документ 1), но всегда со всеми элементами массива в shapes
:
{ "shapes":
[
{"shape": "square", "color": "blue"},
{"shape": "circle", "color": "red"}
]
}
однако, я хотел бы получить документ (документ 1) только с массивом, который содержит color=red
:
{ "shapes":
[
{"shape": "circle", "color": "red"}
]
}
как я могу это сделать?
10 ответов:
MongoDB 2.2 новый
$elemMatch
оператор проекции предоставляет другой способ изменить возвращаемый документ, чтобы он содержал только первый соответствуютshapes
элемент:db.test.find( {"shapes.color": "red"}, {_id: 0, shapes: {$elemMatch: {color: "red"}}});
возвращает:
{"shapes" : [{"shape": "circle", "color": "red"}]}
в 2.2 вы также можете сделать это с помощью
$ projection operator
, где$
в проекции имя поля объекта представляет собой индекс первого соответствующего элемента массива поля из запроса. Этот следующие возвращает те же результаты, что и выше:db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});
Обновление MongoDB 3.2
начиная с версии 3.2, вы можете использовать новый тег
$filter
оператор агрегации для фильтрации массива во время проекции, который имеет преимущество включения все матчи, а не только первый.db.test.aggregate([ // Get just the docs that contain a shapes element where color is 'red' {$match: {'shapes.color': 'red'}}, {$project: { shapes: {$filter: { input: '$shapes', as: 'shape', cond: {$eq: ['$$shape.color', 'red']} }}, _id: 0 }} ])
результаты:
[ { "shapes" : [ { "shape" : "circle", "color" : "red" } ] } ]
новая Aggregation Framework в MongoDB 2.2 + предусмотрена альтернатива Map / Reduce. Элемент
$unwind
оператор может быть использован для разделенияshapes
массив в поток документов, которые могут быть сопоставлены:db.test.aggregate( // Start with a $match pipeline which can take advantage of an index and limit documents processed { $match : { "shapes.color": "red" }}, { $unwind : "$shapes" }, { $match : { "shapes.color": "red" }} )
результаты:
{ "result" : [ { "_id" : ObjectId("504425059b7c9fa7ec92beec"), "shapes" : { "shape" : "circle", "color" : "red" } } ], "ok" : 1 }
внимание: этот ответ дает решение, соответствующее в это время, прежде чем были введены новые функции MongoDB 2.2 и выше. См. другие ответы, если вы используете более позднюю версию MongoDB.
параметр селектора полей ограничен полными свойствами. Он не может быть использован для выбора части массива, только весь массив. Я пробовал использовать $ позиционные оператор, но это не работа.
самый простой способ-просто отфильтровать фигуры в клиенте.
Если вы действительно нужно правильный вывод непосредственно из MongoDB, вы можете использовать карту-уменьшить для фильтрации форм.
function map() { filteredShapes = []; this.shapes.forEach(function (s) { if (s.color === "red") { filteredShapes.push(s); } }); emit(this._id, { shapes: filteredShapes }); } function reduce(key, values) { return values[0]; } res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } }) db[res.result].find()
еще один интересный способ-использовать $redact, что является одной из новых функций агрегации MongoDB 2.6. Если вы используете 2.6, вам не нужен $unwind, который может вызвать проблемы с производительностью, если у вас есть большие массивы.
db.test.aggregate([ { $match: { shapes: { $elemMatch: {color: "red"} } }}, { $redact : { $cond: { if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]}, then: "$$DESCEND", else: "$$PRUNE" } }}]);
$redact
"ограничивает содержание документов на основе информации, хранящейся в самих документах". Так что он будет работать только внутри документ. Он в основном сканирует ваш документ сверху вниз и проверяет, соответствует ли он вашемуif
состояние, которое в$cond
, Если есть совпадение, он либо сохранит содержимое ($$DESCEND
) или удалить($$PRUNE
).в примере выше, первый
$match
возвращает целоеshapes
массив, и $redact удаляет его до ожидаемого результата.отметим, что
{$not:"$color"}
необходимо, потому что он будет сканировать верхний документ, а также, если$redact
не найтиcolor
поле на верхнем уровне это вернетfalse
это может лишить весь документ, который мы не хотим.
лучше вы можете запросить в соответствующем элементе массива с помощью
$slice
полезно ли возвращать значимый объект в массиве.db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})
$slice
полезно, когда вы знаете индекс элемента, но иногда хочется какой бы элемент массива не соответствовал вашим критериям. Вы можете вернуть соответствующий элемент с помощью$
оператора.
синтаксис для поиска в mongodb является
db.<collection name>.find(query, projection);
и второй запрос, который вы написали, что составляет
db.test.find( {shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color":1})
в этом вы использовали
$elemMatch
оператор в части запроса, тогда как если вы используете этот оператор в части проекции, то вы получите желаемый результат. Вы можете записать свой запрос какdb.users.find( {"shapes.color":"red"}, {_id:0, shapes: {$elemMatch : {color: "red"}}})
это даст вам желаемый результат.
db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
выходы
{ "shapes" : [ { "shape" : "circle", "color" : "red" } ] }
спасибо JohnnyHK.
здесь я просто хочу добавить более сложное использование.
// Document { "_id" : 1 "shapes" : [ {"shape" : "square", "color" : "red"}, {"shape" : "circle", "color" : "green"} ] } { "_id" : 2 "shapes" : [ {"shape" : "square", "color" : "red"}, {"shape" : "circle", "color" : "green"} ] } // The Query db.contents.find({ "_id" : ObjectId(1), "shapes.color":"red" },{ "_id": 0, "shapes" :{ "$elemMatch":{ "color" : "red" } } }) //And the Result {"shapes":[ { "shape" : "square", "color" : "red" } ]}
вам просто нужно запустить запрос
db.test.find( {"shapes.color": "red"}, {shapes: {$elemMatch: {color: "red"}}});
вывод этого запроса
{ "_id" : ObjectId("562e7c594c12942f08fe4192"), "shapes" : [ {"shape" : "circle", "color" : "red"} ] }
Как вы и ожидали, он даст точное поле из массива, которое соответствует цвету: 'красный'.