MongoDB elemmatch несколько элементов в массиве
У меня есть документ Mongodb, как
{
"_id" : ObjectId("54e66b2da7b5f3a92e09dc6c"),
"SomeMetric" : [
{
//some object
}
{
//some object
}
],
"FilterMetric" : [
{
"min" : "0.00",
"max" : "16.83",
"avg" : "0.00",
"class" : "s1"
},
{
"min" : "0.00",
"max" : "16.83",
"avg" : "0.00",
"class" : "s2"
},
{
"min" : "0.00",
"max" : "16.83",
"avg" : "0.00",
"class" : "s1"
},
{
"min" : "0.00",
"max" : "16.83",
"avg" : "0.00",
"class" : "s2"
}
]
}
Обычно он содержит много таких вложенных массивов. Я хочу спроектировать одну метрику в одиночку, только с массивами, которые имеют мои критерии поиска. У меня есть запрос
db.sample.find(
{"filtermetric.class" : "s2"},{"filtermetric" : { $elemMatch : {class: "s2"}}}
)
Это дает мне только первый объект в массиве. Второй объект с классом: s2 не возвращается.
Если я попробую
db.sample.find(
{"filtermetric" : { $elemMatch : {class: "s2"}}}
)
Это дает мне все 4 объекта в массиве.
Как мне получить все объекты, которые соответствуют критериям в таком случае?
1 ответ
Вы не можете вернуть несколько элементов массива, соответствующих вашим критериям, в любой форме основного .find()
запрос. Чтобы соответствовать более чем одному элементу, вам нужно использовать .aggregate()
метод вместо.
Основное отличие здесь заключается в том, что "запрос" делает именно то, для чего он предназначен, и соответствует "документам", которые соответствуют вашим условиям. Вы можете попробовать использовать позиционный $
оператор в аргументе проекции, но правила таковы, что он будет соответствовать только "первому" элементу массива, который соответствует условиям запроса.
Чтобы "отфильтровать" несколько элементов массива, выполните следующие действия:
db.sample.aggregate([
// Filter possible documents
{ "$match": { "filtermetric.class": "s2" } },
// Unwind the array to denormalize
{ "$unwind": "$filtermetric" },
// Match specific array elements
{ "$match": { "filtermetric.class": "s2" } },
// Group back to array form
{ "$group": {
"_id": "$_id",
"filtermetric": { "$push": "$filtermetric" }
}}
])
В современных версиях MongoDB версии 2.6 или выше вы можете сделать это с $redact
:
db.sample.aggregate([
// Filter possible documents
{ "$match": { "filtermetric.class": "s2" } },
// Redact the entries that do not match
{ "$redact": {
"$cond": [
{ "$eq": [ { "$ifNull": [ "$class", "s2" ] }, "s2" ] },
"$$DESCEND",
"$$PRUNE"
]
}}
])
Вероятно, это наиболее эффективный вариант, но он рекурсивный, поэтому сначала рассмотрите структуру документа, поскольку одноименное поле не может существовать с какими-либо другими условиями на любом уровне.
Возможно, безопаснее, но только в том случае, если результаты в массиве "действительно уникальны", если этот метод $map
а также $setDifference
:
db.sample.aggregate([
{ "$project": {
"filtermetric": { "$setDifference": [
{ "$map": [
"input": "$filtermetric",
"as": "el",
"in": {"$cond": [
{ "$eq": [ "$$el.class", "s2" ] },
"$$el",
false
]}
]},
[false]
]}
}}
])
Также отмечая, что в обоих $group
а также $project
Этапы рабочего конвейера: вам нужно указать все поля, которые вы намереваетесь вернуть в ваших документах с результатами этого этапа.
Последнее замечание заключается в том, что $elemMatch
не требуется, когда вы запрашиваете значение только одного ключа в массиве. "Точечная нотация" является предпочтительной и рекомендуется при доступе только к одному ключу массива. $elemMatch
должен быть необходим, только когда "несколько" ключей в документе в "элементе" массива должны соответствовать условию запроса.