Подтвердить что ты не робот

MongoDB Aggregation - сопоставление, если значение в массиве

У меня есть коллекция, в которой я выполняю агрегацию, и я в основном получил ее до

{array:[1,2,3], value: 1},
{array:[1,2,3], value: 4}

Как выполнить сопоставление агрегации, чтобы проверить, находится ли значение в массиве? Я попытался использовать {$match: {"array: {$in: ["$value"]}}}, но ничего не нашел.

Я хотел бы, чтобы результат (если использовать вышеприведенное в качестве примера) был:

{array:[1,2,3], value:1}
4b9b3361

Ответ 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
        }
      }
    ]);
  1. Добавить поле временного фильтра
  2. $ unwind в результирующем массиве (результаты конвейера с пустыми массивами удаляются)
  3. (необязательно) удалить поле фильтра из результата через проект