MongoDB Aggregation - сопоставление, если значение в массиве
У меня есть коллекция, в которой я выполняю агрегацию, и я в основном получил ее до
{array:[1,2,3], value: 1},
{array:[1,2,3], value: 4}
Как выполнить сопоставление агрегации, чтобы проверить, находится ли значение в массиве? Я попытался использовать {$match: {"array: {$in: ["$value"]}}}
, но ничего не нашел.
Я хотел бы, чтобы результат (если использовать вышеприведенное в качестве примера) был:
{array:[1,2,3], value:1}
Ответы
Ответ 1
Небольшое отклонение, основанное на ответе @chridam:
db.test.aggregate([
{ "$unwind": "$array" },
{ "$group": {
_id: { "_id": "$_id", "value": "$value" },
array: { $push: "$array" },
mcount: { $sum: {$cond: [{$eq: ["$value","$array"]},1,0]}}
}
},
{ $match: {mcount: {$gt: 0}}},
{ "$project": { "value": "$_id.value", "array": 1, "_id": 0 }}
])
Идея состоит в $unwind
и $group
вернуть массив, подсчитав в mcount
количество элементов, соответствующих этому значению. После этого простой $match
на mcount > 0
будет отфильтровывать нежелательные документы.
Ответ 2
Как указано, $where
является хорошим вариантом, когда вам не нужно продолжать логику в конвейере агрегации.
Но если вы это сделаете, используйте $redact
, $map
, чтобы преобразовать значение в массив и использовать $setIsSubSet
для сравнения. Это самый быстрый способ сделать это, поскольку вам не нужно дублировать документы, используя $unwind
:
db.collection.aggregate([
{ "$redact": {
"$cond": {
"if": { "$setIsSubset": [
{ "$map": {
"input": { "$literal": ["A"] },
"as": "a",
"in": "$value"
}},
"$array"
]},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Оператор конвейера $redact
допускает выполнение логического условия в $cond
и использует специальные операции $$KEEP
, чтобы "сохранить" документ, где логическое условие истинно, или $$PRUNE
, чтобы "удалить" документ, в котором условие было ложным.
Это позволяет ему работать как $project
с последующим $match
, но на одном этапе, который более эффективен.
Учитывая, что это нативные закодированные операторы, а не JavaScript, то это, скорее всего, "самый быстрый способ выполнить ваш матч". Поэтому, если вы используете версию MongoDB 2.6 или выше, тогда вы должны это сделать, чтобы сравнить эти элементы в вашем документе.
Ответ 3
Более эффективный подход будет включать один конвейер, который использует оператор $redact
следующим образом:
db.collection.aggregate([
{
"$redact": {
"$cond": [
{
"$setIsSubset": [
["$value"],
"$array"
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
])
Для более ранних версий MongoDB, которые не поддерживают $redact
(версии < 2.6), тогда рассмотрим это который использует оператор $unwind
:
db.collection.aggregate([
{ "$unwind": "$array" },
{
"$project": {
"isInArray": {
"$cond": [
{ "$eq": [ "$array", "$value" ] },
1,
0
]
},
"value": 1,
"array": 1
}
},
{ "$sort": { "isInArray": -1 } },
{
"$group": {
"_id": {
"_id": "$_id",
"value": "$value"
},
"array": { "$push": "$array" },
"isInArray": { "$first": "$isInArray" }
}
},
{ "$match": { "isInArray": 1 } },
{ "$project": { "value": "$_id.value", "array": 1, "_id": 0 } }
])
Ответ 4
Немного поздно ответить, но это другое решение:
Используя addFields и сопоставляя их отдельно, это дает большую гибкость, чем redact. Вы можете открыть несколько полей, а затем использовать другую логику соответствия на основе результатов.
db.applications.aggregate([
{$addFields: {"containsValueInArray": {$cond:[{$setIsSubset: [["valueToMatch"], "$arrayToMatchIn"]},true,false]}}},
{$match: {"containsValueInArray":true}}
]);
Ответ 5
Вы можете использовать выражение агрегации в регулярном запросе в версии 3.6.
db.collection_name.find({"$expr": {"$in": ["$value", "$array"]}})
Использование агрегации:
Вы можете использовать $match + $expr
в текущей версии 3.6
.
db.collection_name.aggregate({"$match": {"$expr": {"$in": ["$value", "$array"]}}})
Вы можете попробовать выражение $redact + $in
в версии 3.4
.
db.collection_name.aggregate({
"$redact": {
"$cond": [
{
"$in": [
"$value",
"$array"
]
},
"$$KEEP",
"$$PRUNE"
]
}
})
Ответ 6
Вы можете использовать $where
, если агрегирование не требуется
db.collection.find({ $where: function(){
return (this.array.indexOf(this.value) !== -1)}
})
Ответ 7
Попробуйте комбинацию $eq и $setIntersection
{$group :{
_id: "$id",
yourName : { $sum:
{ $cond :[
{$and : [
{$eq:[{$setIntersection : ["$someArrayField", ["$value"]] },["$value"]]}
]
},1,0]
}
}
}
Ответ 8
Я предпочитаю без группировки, там легкий подход, так как v.3.2
...aggregate([
{
$addFields: {
arrayFilter: {
$filter: {
input: '$array',
as: 'item',
cond: ['$$item', '$value']
}
}
}
},
{
$unwind: '$arrayFilter'
},
{
$project: {
arrayFilter: 0
}
}
]);
- Добавить поле временного фильтра
- $ unwind в результирующем массиве (результаты конвейера с пустыми массивами удаляются)
- (необязательно) удалить поле фильтра из результата через проект