Получить только запрошенный элемент в массиве объектов в коллекции 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"
}
]
}
]
новая Основы Агрегирования в MongoDB 2.2+ предоставляет альтернативу Map / Reduce. The $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
array, и $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"
}
]}
вам просто нужно запустить query
db.test.find(
{"shapes.color": "red"},
{shapes: {$elemMatch: {color: "red"}}});
вывод этого запроса -
{
"_id" : ObjectId("562e7c594c12942f08fe4192"),
"shapes" : [
{"shape" : "circle", "color" : "red"}
]
}
Как вы ожидали, он даст точное поле из массива, которое соответствует цвету: "красный".
наряду с $project будет более подходящим, другие соответствующие элементы будут объединены вместе с другими элементами в документе.
db.test.aggregate(
{ "$unwind" : "$shapes" },
{ "$match" : {
"shapes.color": "red"
}},
{"$project":{
"_id":1,
"item":1
}}
)