Найти в двойном вложенном массиве MongoDB

У меня есть эта коллекция в mongodb

{
"_id" : "777",
"someKey" : "someValue",
"someArray" : [
    {
        "name" : "name1",
        "someNestedArray" : [
            {
                "name" : "value"
            },
            {
                "name" : "delete me"
            }
        ]
    }
  ]
}

Я хочу найти документ на основе someArray.someNestedArray.name но я не могу найти полезную ссылку для всех результатов поиска о вложенном массиве обновлений Я пытаюсь это, но ничего не возвращаю

db.mycollection.find({"someArray.$.someNestedArray":{"$elemMatch":{"name":"1"}}})
db.mycollection.find({"someArray.$.someNestedArray.$.name":"1"})

и что-то еще

как я могу найти элемент в двойном вложенном массиве mongodb?

Ответ 1

В простейшем смысле это просто следует основной форме "dot notation" , используемой MongoDB. Это будет работать независимо от того, какой член массива принадлежит внутреннему члену массива, если он соответствует значению:

db.mycollection.find({
    "someArray.someNestedArray.name": "value"
})

Это отлично подходит для значения "одного поля", для сопоставления нескольких полей вы должны использовать $elemMatch:

db.mycollection.find({
    "someArray": { 
        "$elemMatch": {
            "name": "name1",
            "someNestedArray": {
                "$elemMatch": {
                    "name": "value",
                    "otherField": 1
                }
            }
        }
    }
})

Соответствует документу, который будет содержать что-то с полем в этом "пути", соответствующем значению. Если вы намеревались "сопоставлять и фильтровать" результат, поэтому возвращался только совпадающий элемент, это невозможно с проекцией оператора позиционирования, как указано

Вложенные массивы

Оператор positional $не может использоваться для запросов, которые пересекают более одного массива, например запросов, которые пересекают массивы, вложенные в другие массивы, поскольку замена для $placeholder является единственным значением

Современный MongoDB

Мы можем сделать это, применив $filter и $map здесь. $map действительно необходим, потому что "внутренний" массив может измениться в результате "фильтрации", а "внешний" массив, конечно, не соответствует условиям, когда "внутренняя" была лишена всех элементов.

Опять же, следуя примеру фактического сопоставления нескольких свойств в каждом массиве:

db.mycollection.aggregate([
  { "$match": {
    "someArray": {
      "$elemMatch": {
         "name": "name1",
         "someNestedArray": {
           "$elemMatch": {
             "name": "value",
             "otherField": 1
           }
         }
       }
    }
  }},
  { "$addFields": {
    "someArray": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$someArray",
            "as": "sa",
            "in": {
              "name": "$$sa.name",
              "someNestedArray": {
                "$filter": {
                  "input": "$$sa.someNestedArray",
                  "as": "sn",
                  "cond": {
                    "$and": [
                      { "$eq": [ "$$sn.name", "value" ] },
                      { "$eq": [ "$$sn.otherField", 1 ] }
                    ]
                  }
                }
              }             
            }
          },
        },
        "as": "sa",
        "cond": {
          "$and": [
            { "$eq": [ "$$sa.name", "name1" ] },
            { "$gt": [ { "$size": "$$sa.someNestedArray" }, 0 ] }
          ]
        }
      }
    }
  }}
])

Поэтому на "внешнем" массиве $filter фактически смотрит на $size "внутреннего" массива после того, как он был "отфильтрован", поэтому вы можете отклонить эти результаты, когда весь внутренний массив действительно совпадает с примечанием.

Старые MongoDB

Чтобы "проецировать" только согласованный элемент, вам нужен метод .aggregate():

db.mycollection.aggregate([
    // Match possible documents
    { "$match": {
        "someArray.someNestedArray.name": "value"
    }},

    // Unwind each array
    { "$unwind": "$someArray" },
    { "$unwind": "$someArray.someNestedArray" },

    // Filter just the matching elements
    { "$match": {
        "someArray.someNestedArray.name": "value"
    }},

    // Group to inner array
    { "$group": {
        "_id": { 
            "_id": "$_id", 
            "name": "$someArray.name"
        },
        "someKey": { "$first": "$someKey" },
        "someNestedArray": { "$push": "$someArray.someNestedArray" }
    }},

    // Group to outer array
    { "$group": {
        "_id": "$_id._id",
        "someKey": { "$first": "$someKey" },
        "someArray": { "$push": {
            "name": "$_id.name",
            "someNestedArray": "$someNestedArray"
        }}
    }} 
])

Это позволяет вам "фильтровать" совпадения в вложенных массивах для одного или нескольких результатов в документе.

Ответ 2

Вы можете использовать ниже агрегатный конвейер.

В приведенном ниже запросе используется $addFields для перезаписывания существующего someArray с обновленным someArray.

Обновленный someArray создается с помощью $map + $filter. $map, чтобы сохранить существующие значения и $filter, чтобы отфильтровать someNestedArray, чтобы сохранить только соответствующее значение.

Используйте $project этап вместо $addFields в 3.2.

db.collection.aggregate([
{
    $addFields: {
        someArray: {
            $map: {
                input:"$someArray",
                as: "resultm",
                in: {
                   name: "$$resultm.name",
                   someNestedArray: {
                        $filter: {
                            input: "$$resultm.someNestedArray",
                            as: "resultf",
                            cond: {
                                $eq: ["$$resultf.name", "value"]
                            }
                        }
                    }
                }
            }
        }
    }
}
])