Получить только запрошенный элемент в массиве объектов в коллекции MongoDB
-
10-10-2019 - |
Вопрос
Предположим, у вас есть следующие документы в моей коллекции:
{
"_id":ObjectId("562e7c594c12942f08fe4192"),
"shapes":[
{
"shape":"square",
"color":"blue"
},
{
"shape":"circle",
"color":"red"
}
]
},
{
"_id":ObjectId("562e7c594c12942f08fe4193"),
"shapes":[
{
"shape":"square",
"color":"black"
},
{
"shape":"circle",
"color":"green"
}
]
}
Сделать запрос:
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"}
]
}
Как я могу это сделать?
Решение
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+ предоставляет альтернативу карту/уменьшению. А $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 и UP. Посмотрите другие ответы, если вы используете более недавнюю версию 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, вам не нужно раскрутить $, что может вызвать проблемы с производительностью, если у вас есть большие массивы.
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
Полезно, когда вы знаете индекс элемента, но иногда вы хотите, чтобы какой -либо элемент массива соответствовал вашим критериям. Вы можете вернуть соответствующий элемент с $
оператор.
db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
Выходы
{
"shapes" : [
{
"shape" : "circle",
"color" : "red"
}
]
}
Синтаксис для находки в 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"}}})
Это даст вам желаемый результат.
Благодаря Джонни.
Здесь я просто хочу добавить более сложное использование.
// 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"}
]
}
Как вы ожидали, это даст точное поле из массива, соответствующего цвету: «красный».
Наряду с $ Project будет более подходящим другим элементам подходящего подходящего, будет забит вместе с другими элементами в документе.
db.test.aggregate(
{ "$unwind" : "$shapes" },
{ "$match" : {
"shapes.color": "red"
}},
{"$project":{
"_id":1,
"item":1
}}
)
db.test.find( {"shapes.color": "red"}, {_id: 0})