Предположим, что в моей коллекции есть следующие документы:
{
"_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
проецирования $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
агрегации $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. Оператор $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
}
$elemMatch
- еще один вариант. На самом деле я попал сюда через вопрос группы Google, где $ elemMatch не будет работать, потому что он возвращает только первое совпадение для каждого документа.
Предостережение: Этот ответ представляет собой решение, которое было актуальным в то время, до того, как были введены новые возможности MongoDB 2.2 и выше. См. Другие ответы, если вы используете более новую версию MongoDB.
Параметр выбора поля ограничен полными свойствами. Он не может использоваться для выбора части массива, только для всего массива. Я попытался использовать $positional operator, но это не сработало.
Самый простой способ - просто отфильтровать фигуры в клиенте.
Если вам действительно нужен правильный вывод непосредственно из 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
полезно, когда вы знаете индекс элемента, но иногда вы хотите
какой бы элемент массива не соответствовал вашим критериям. Вы можете вернуть соответствующий элемент
с оператором $
.
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"}}})
Это даст вам желаемый результат.
"shapes.color":"red"
в параметре запроса (первый параметр метода find) не обязателен. Вы можете заменить его на {}
и получить те же результаты.
Здесь я просто хочу добавить более сложное использование.
// 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})
$elemMatch
и$
возвращают только первое совпадение .