Я собираюсь упростить ваш набор данных, чтобы читателям было легче понять пример, но логика все еще остается той же
{ "a" : [ { "sample" : 1 } ], "b" : [ { "sample" : 1 } ] } // No color
{ "a" : [ { "color" : "b" } ], "b" : [ { "color" : "b" } ] } // YES - Valid color
{ "b" : [ { "color" : "" }, { "color" : "a" } ] } // Empty color
{ "b" : [ { "color" : null }, { "color" : "a" } ] } // Null color
{ "b" : [ { "sample" : 1 } ] } // No color
{ "b" : [ { "color" : "b" } ] } // YES - Valid Color
{ "a" : [ { "color" : "" }, { "color" : "a" } ] } // Empty color
{ "a" : [ { "color" : null }, { "color" : "a" } ] } // Null Color
{ "a" : [ { "sample" : 1 } ] } // No color
{ "a" : [ { "color" : "b" } ] } // YES - Valid color
В основном все те же комбинации, что и у васищем, так как здесь есть два свойства, где одно или оба содержат объекты в массиве, где вы хотите, чтобы ВСЕ элементы в массиве имели свойство color
:
- существующий
- Не пусто
- Не пусто
Короче говоря, только три из этих документов соответствуют требованиям. Вот как их получить:
db.collection.find({
"$or": [
{
"a.color": { "$exists": true },
"a": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
},
{
"b.color": { "$exists": true },
"b": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
}
]
})
При $or
у нас есть два парных условия. Будучи отдельно (очень важно) для проверки существования именованного пути, а затем для поиска случаев, когда ЛЮБЫЕ объекты в массиве соответствуют условиям, которые исключают их (ноль или пусто) и отклонить эти документы, используя выражение $not
.
$or
используется только для представления пар для каждого дискретного поля, содержащего массив.
Результаты, конечно, следующие:
{ "a" : [ { "color" : "b" } ], "b" : [ { "color" : "b" } ] }
{ "b" : [ { "color" : "b" } ] }
{ "a" : [ { "color" : "b" } ] }
Будучи только теми документами, в которых один или оба из предоставленных внешних ключей содержали массивы, где ВСЕ элементы имеют свойство color
со значением не null
.
В том случае, если вы действительно имеете в виду, что такие поля, как things1
и things2
, могут динамически меняться от документа к документу и, возможно, иметь things3
, а вам нужно ALL свойств с их содержащими массивами для соответствия условиям, тогда вам в основном не повезло для стандартного запроса, и вам нужно будет вернуться к aggregate()
.
ВНапример, если бы мы добавили документ, подобный следующему:
{ "a": [{ "color": "b" }, "c": [{ "color": "" }] }
Тогда приведенная выше базовая форма запроса все равно вернет этот документ, поэтому вместо этого вы будете использовать aggregate()
:
db.collection.aggregate([
{ "$addFields": {
"comb": {
"$reduce": {
"input": {
"$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$in": [ "$$this.k", [ "a", "b", "c" ] ] }
}
},
"as": "el",
"in": {
"$map": {
"input": "$$el.v",
"in": {
"$mergeObjects": [
{ "type": "$$el.k" },
"$$this"
]
}
}
}
}
},
"initialValue": [],
"in": { "$concatArrays": [ "$$value", "$$this" ] }
}
}
}},
{ "$match": {
"comb.color": { "$exists": true },
"comb": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
}},
{ "$addFields": {
"comb": "$$REMOVE"
}}
])
Но это действительно не желательно. Обратите внимание, что для динамического обхода клавиш вам нужен $objectToArray
, чтобы эффективно превратить все ключи в документе в одну запись массива. Затем, конечно, $filter
для ожидаемых клавиш или, в качестве альтернативы, просто измените логику на , исключите такие вещи, как _id
и другие значения, которые не будут применяться.
Это тогда объединит эти массивы вместе, в то время как переназначит имя key
как свойство в массиве. Отмечая главным образом, что реальная главная точка здесь - это не "a.color"
и "b.color"
, у нас просто есть одиночный путь, который теперь "comb"
представляет комбинированное отображение.
Это приведет кожидаемый результат, но реальное решение здесь должно быть видно в реализованной логике конвейера, и вместо нескольких свойств документа с массивами, лучший подход - это отдельный массив , в котором используется только thing1
или thing2
(или в данном случае "a"
или "b"
или "c"
), чтобы быть просто другим значением свойства с последовательно именованным в этом массиве.
Таким образом, эта форма хранения ваших данных далекоболее эффективный:
{ "data" : [ { "type" : "a", "color" : "" }, { "type" : "a", "color" : "a" }, { "type" : "b", "color" : "" }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "color" : null }, { "type" : "a", "color" : "a" }, { "type" : "b", "color" : null }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "sample" : 1 }, { "type" : "b", "sample" : 1 } ] }
{ "data" : [ { "type" : "a", "color" : "b" }, { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "b", "color" : "" }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "b", "color" : null }, { "type" : "b", "color" : "a" } ] }
{ "data" : [ { "type" : "b", "sample" : 1 } ] }
{ "data" : [ { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "a", "color" : "" }, { "type" : "a", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "color" : null }, { "type" : "a", "color" : "a" } ] }
{ "data" : [ { "type" : "a", "sample" : 1 } ] }
{ "data" : [ { "type" : "a", "color" : "b" } ] }
{ "data" : [ { "type" : "a", "color" : "b" }, { "type" : "c", "color" : "" } ] }
Если это была структура вашей коллекции, то запрос элементов массива ALL становится очень простым и является в основном самым последним этапом aggregate()
, который по сути переводитсяваша существующая структура в этой форме:
db.collection.find({
"data.color": { "$exists": true },
"data": { "$not": { "$elemMatch": { "color": { "$in": [null, ""] } } } }
})
И тогда это по существу те же три документа, которые соответствуют требованиям:
{ "data" : [ { "type" : "a", "color" : "b" }, { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "b", "color" : "b" } ] }
{ "data" : [ { "type" : "a", "color" : "b" } ] }
Нетздесь следует отметить, что непротиворечивое имя пути, такое как "data.type"
, имеет много преимуществ, которые также делают это имя полезным для параметров запроса. Вы также можете просто добавить его в условия фильтрации, если вы просто хотите искать вещи типа "где" равно thing1
, и это также значительно упрощает обновление документов.
Стоит рассмотреть, так как базовыеформы запросов, которые не полагаются на манипуляции с операторами aggregate()
, работают намного лучше в долгосрочной перспективе.