MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Remova o campo encontrado em qualquer array mongodb


Então, eu fiz uma pergunta nos comentários, mas você parece ter se afastado, então acho que apenas respondo aos três casos possíveis que vejo.

Para começar, não tenho certeza se os elementos mostrados nas matrizes aninhadas são os somente elementos dentro do array ou de fato se arrayToDelete é o somente campo presente nesses elementos. Então, basicamente, eu preciso resumir um pouco e inclua esse caso:
{
    field: 'value',
    field2: 'value',
    scan: [
        [
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
        [
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
        [
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
    ]
}

Caso 1 - Remova os elementos da matriz interna onde o Campo está presente


Isso usaria o $pull operador, pois é isso que remove os elementos da matriz inteiramente. Você faz isso no MongoDB moderno com uma declaração como esta:
db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  {
    "$pull": {
      "scan.$[a]": { "arrayToDelete": { "$exists": true } }
    }
  },
  { "arrayFilters": [
      {  "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
    ]
  }
)

Isso altera todos os documentos correspondentes como este:
{
        "_id" : ObjectId("5ca1c36d9e31550a618011e2"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
                [
                        {
                                "somethingToKeep" : 1
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        }
                ]
        ]
}

Portanto, todos os elementos que continham esse campo agora são removidos.

Caso 2 - Basta remover o campo correspondente dos elementos internos


É aqui que você usa $unset . É apenas um pouco diferente do "indexado rígido" versão que você estava fazendo:
db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$unset": { "scan.$[].$[].arrayToDelete": ""  } }
)

O que altera todos os documentos correspondentes para serem:
{
        "_id" : ObjectId("5ca1c4c49e31550a618011e3"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
                [
                        {
                                "anotherField" : "a"
                        },
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        }
                ],
                [
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        }
                ]
        ]
}

Portanto, tudo ainda está lá, mas apenas os campos identificados foram removidos de cada documento de matriz interna.

Caso 3 - Você realmente queria remover "Tudo" na matriz.


O que é realmente apenas um caso simples de usar $set e limpando tudo o que estava lá antes:
db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$set": { "scan": []  } }
)

Onde os resultados devem ser esperados:
{
        "_id" : ObjectId("5ca1c5c59e31550a618011e4"),
        "field" : "value",
        "field2" : "value",
        "scan" : [ ]
}

Então, o que todos eles estão fazendo?


A primeira coisa que você deve ver é o predicado de consulta . Isso geralmente é uma boa ideia para ter certeza de que você não está combinando e até mesmo "tentando" ter condições de atualização atendidas em documentos que nem sequer contêm dados com o padrão que você pretende atualizar. Arrays aninhados são difíceis na melhor das hipóteses, e sempre que possível, você realmente deve evitá-los, pois o que você geralmente "realmente quer dizer" na verdade é representado em uma matriz singular com atributos adicionais representando o que você "pensa" o aninhamento está realmente fazendo por você.

Mas só porque eles são difíceis não significa impossível . É só que você precisa entender $elemMatch :
db.colelction.find(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  }}
)

Esse é o find() básico exemplo, que corresponde com base no $elemMatch condição para o externo array usa outro $elemMatch para corresponder a outra condição no interno variedade. Mesmo que isso "apareça" ser um predicado singular. Algo como:
"scan.arrayToDelete": { "$exists": true }

Apenas não vai funcionar. Nem seria:
"scan..arrayToDelete": { "$exists": true }

Com o "ponto duplo" .. porque isso basicamente não é válido.

Esse é o predicado de consulta para corresponder a "documentos" que precisam ser processados, mas o resto se aplica para determinar *quais partes atualizar".

No Caso 1 para $pull do interior array, primeiro precisamos identificar quais elementos do externo array contém os dados a serem atualizados. É isso que o "scan.$[a]" coisa está fazendo usando o $[<identifier>] filtrado posicional operador.

Esse operador basicamente transpõe os índices correspondentes (tão muitos deles ) na matriz para outro predicado que é definido na terceira seção do update comandos de estilo com os arrayFilters seção. Esta seção basicamente define as condições a serem atendidas a partir da perspectiva do identificador nomeado.

Neste caso, o "identificador" é denominado a , e esse é o prefixo usado no arrayFilters entrada:
  { "arrayFilters": [
      {  "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
    ]
  }

Tomado no contexto com a declaração de atualização real papel:
  {
    "$pull": {
      "scan.$[a]": { "arrayToDelete": { "$exists": true } }
    }
  },

Então, da perspectiva do "a" sendo o identificador para o externo elemento array primeiro para dentro de "scan" , as mesmas condições se aplicam ao predicado de consulta original mas de "dentro" o primeiro $elemMatch demonstração. Então você pode basicamente pensar nisso como uma "consulta dentro de uma consulta" da perspectiva de já "olhar para dentro" o conteúdo de cada exterior elemento.

Da mesma forma, o $pull funciona como uma "consulta dentro de uma consulta" em que seus próprios argumentos também são aplicados da perspectiva do elemento da matriz. Portanto, apenas o arrayToDelete campo existente em vez de:
  // This would be wrong! and do nothing :(
  {
    "$pull": {
      "scan.$[a]": { "$elemMatch": { "arrayToDelete": { "$exists": true } } }
    }
  }

Mas tudo isso é específico para $pull , e outras coisas têm casos diferentes:

O Caso 2 olha para onde você quer apenas $unset o campo nomeado. Parece muito fácil, basta nomear o campo, certo? Bem, não exatamente, já que o seguinte claramente não está certo do que sabemos anteriormente:
  { "$unset": { "scan.arrayToDelete": ""  } } // Not right :(

E, claro, observar os índices de matriz para tudo é apenas uma dor:
  { "$unset": { 
    "scan.0.0.arrayToDelete": "",
    "scan.0.1.arrayToDelete": "",
    "scan.0.2.arrayToDelete": "",
    "scan.0.3.arrayToDelete": "",  // My fingers are tired :-<
  } }

Esta é a razão para o posicionamento de todos os $[] operador. Este é um pouco mais "força bruta" do que o $[<identifier>] filtrado posicional nisso, em vez de corresponder a outro predicado fornecido em arrayFilters , o que isso simplesmente faz é se aplicar a tudo dentro do conteúdo da matriz nesse "índice". É basicamente uma maneira de dizer "todos os índices" sem digitar cada um como o horrível caso mostrado acima.

Portanto, não é para todos os casos , mas certamente é adequado para um $unset uma vez que tem uma nomeação de caminho muito específica o que não importa, é claro, se esse caminho não corresponder a todos os elementos da matriz.

Você poderia ainda usa um arrayFilters e um $[<identifier>] filtrado posicional , mas aqui seria um exagero. Além disso, não custa demonstrar a outra abordagem.

Mas é claro que provavelmente vale a pena entender como exatamente essa declaração ficaria, então:
db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$unset": { "scan.$[a].$[b].arrayToDelete": ""  } },
  {
    "arrayFilters": [
      { "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } },
      { "b.arrayToDelete": { "$exists": true } },
    ]
  }
)

Observando aí que o "b.arrayToDelete" pode não ser o que você espera no início, mas dado o posicionamento em "scan.$[a].$[b] realmente deve fazer sentido a partir do b o nome do elemento seria alcançado por meio de "notação de ponto" exatamente como mostrado. E, de fato, em ambos os casos. Mais uma vez, um $unset só se aplicaria ao campo nomeado de qualquer maneira, então os critérios de seleção realmente não são necessários.

E Caso 3 . Bem, é bem simples, se você não precisar para manter qualquer outra coisa na matriz depois de remover este conteúdo (ou seja, um $pull onde os campos correspondentes a isso eram os únicos coisas lá, ou um $unset para esse assunto), então simplesmente não brinque com mais nada e apenas limpe o array .

Esta é uma distinção importante se você considerar que, de acordo com o ponto para esclarecer se os documentos com o campo nomeado, o somente elementos dentro das matrizes aninhadas e, de fato, que a chave nomeada era a somente coisa presente nos documentos.

Com o raciocínio sendo que usar $pull como mostrado aqui e nessas condições você obteria:
{
        "_id" : ObjectId("5ca321909e31550a618011e6"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
            [ ],
            [ ],
            [ ]
        ]
}

Ou com o $unset :
{
        "_id" : ObjectId("5ca322bc9e31550a618011e7"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
            [{ }, { }, { }, { }],
            [{ }, { }, { }, { }],
            [{ }, { }, { }, { }]
        ]
}

Ambos claramente não são desejáveis. Portanto, é sua razão se o arrayToDelete campo era o único conteúdo que estava lá, então a maneira mais lógica de remover tudo é simplesmente substituir o array por um vazio. Ou de fato $unset toda a propriedade do documento.

Observe, no entanto, que todas essas "coisas extravagantes" (com exceção do $set é claro ) exigem que você deve ter pelo menos o MongoDB 3.6 disponíveis para usar esta funcionalidade.

No caso de você ainda estar executando uma versão mais antiga do MongoDB do que essa (e até a data de redação deste artigo, você realmente não deveria estar, pois seu suporte oficial se esgota em apenas 5 meses a partir desta data), outras respostas existentes em Como atualizar vários Os elementos de matriz no mongodb são realmente para você.