Получить только запрошенный элемент в массиве объектов в коллекции 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"}
]
}

Сделать запрос:

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"}
]
}

Как я могу это сделать?

312

Решение

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"}
]
}
]
343

Другие решения

Новый Структура агрегации в 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
}
94

Внимание: Этот ответ предоставляет решение, которое было актуально в это время, до того, как были представлены новые функции 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()
29

Еще один интересный способ заключается в использовании $ красноломкий, которая является одной из новых функций агрегации 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 это может лишить весь документ, который мы не хотим.

27

Лучше вы можете запросить в соответствующем элементе массива, используя $slice полезно ли возвращать значимый объект в массиве.

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

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

18
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

ВЫХОДЫ

{

"shapes" : [
{
"shape" : "circle",
"color" : "red"}
]
}
12

Синтаксис для поиска в 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"}}})

Это даст вам желаемый результат.

11

Благодаря 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"}
]}
7
По вопросам рекламы [email protected]