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

Atualizando uma matriz aninhada com o MongoDB

Escopo geral e explicação


Há algumas coisas erradas com o que você está fazendo aqui. Em primeiro lugar, suas condições de consulta. Você está se referindo a vários _id valores onde você não deveria precisar, e pelo menos um dos quais não está no nível superior.

Para entrar em um valor "aninhado" e também presumir que _id value é único e não apareceria em nenhum outro documento, seu formulário de consulta deve ser assim:
Model.update(
    { "array1.array2._id": "123" },
    { "$push": { "array1.0.array2.$.answeredBy": "success" } },
    function(err,numAffected) {
       // something with the result in here
    }
);

Agora, isso realmente funcionaria, mas na verdade é apenas um acaso que funciona, pois há boas razões pelas quais não deveria funcionar para você.

A leitura importante está na documentação oficial do $ posicional operador sob o assunto "Aninhado Arrays". O que isso diz é:

O operador posicional $ não pode ser usado para consultas que percorrem mais de uma matriz, como consultas que percorrem matrizes aninhadas em outras matrizes, porque a substituição do espaço reservado $ é um valor único

Especificamente, o que isso significa é que o elemento que será correspondido e retornado no espaço reservado posicional é o valor do índice do primeiro matriz correspondente. Isso significa que, no seu caso, o índice correspondente na matriz de nível "superior".

Portanto, se você observar a notação de consulta conforme mostrado, "codificamos" o primeiro ( ou 0 index ) na matriz de nível superior, e acontece que o elemento correspondente dentro de "array2" também é a entrada de índice zero.

Para demonstrar isso, você pode alterar o _id correspondente valor para "124" e o resultado será $push uma nova entrada no elemento com _id "123", pois ambos estão na entrada de índice zero de "array1" e esse é o valor retornado ao espaço reservado.

Então esse é o problema geral com matrizes aninhadas. Você pode remover um dos níveis e ainda poderá $push para o elemento correto em sua matriz "top", mas ainda haveria vários níveis.

Tente evitar o aninhamento de arrays, pois você terá problemas de atualização, conforme mostrado.

O caso geral é "achatar" as coisas que você "acha" que são "níveis" e realmente fazer esses "atributos" nos detalhes finais. Por exemplo, a forma "achatada" da estrutura na pergunta deve ser algo como:
 {
   "answers": [
     { "by": "success", "type2": "123", "type1": "12" }
   ]
 }

Ou mesmo quando aceitar o array interno é $push apenas, e nunca atualizado:
 {
   "array": [
     { "type1": "12", "type2": "123", "answeredBy": ["success"] },
     { "type1": "12", "type2": "124", "answeredBy": [] }
   ]
 }

Ambos se prestam a atualizações atômicas dentro do escopo do $ posicional operador

MongoDB 3.6 e superior


A partir do MongoDB 3.6, há novos recursos disponíveis para trabalhar com arrays aninhados. Isso usa o $[<identifier>] filtrado posicional sintaxe para corresponder aos elementos específicos e aplicar condições diferentes por meio de arrayFilters na declaração de atualização:
Model.update(
  {
    "_id": 1,
    "array1": {
      "$elemMatch": {
        "_id": "12","array2._id": "123"
      }
    }
  },
  {
    "$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
  },
  {
    "arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }] 
  }
)

Os "arrayFilters" como passado para as opções para .update() ou mesmo.updateOne() , .updateMany() , .findOneAndUpdate() ou .bulkWrite() O método especifica as condições para corresponder ao identificador fornecido na instrução de atualização. Quaisquer elementos que correspondam à condição fornecida serão atualizados.

Como a estrutura é "aninhada", na verdade usamos "vários filtros", conforme especificado com uma "matriz" de definições de filtro, conforme mostrado. O "identificador" marcado é usado na correspondência com o $[<identifier>] filtrado posicional sintaxe realmente usada no bloco de atualização da instrução. Neste caso inner e outer são os identificadores usados ​​para cada condição conforme especificado com a cadeia aninhada.

Essa nova expansão possibilita a atualização do conteúdo do array aninhado, mas não ajuda muito na praticidade de "consultar" esses dados, portanto, aplicam-se as mesmas advertências explicadas anteriormente.

Você normalmente realmente "pretende" expressar como "atributos", mesmo que seu cérebro inicialmente pense em "aninhamento", geralmente é apenas uma reação à forma como você acredita que as "partes relacionais anteriores" se juntam. Na realidade, você realmente precisa de mais desnormalização.

Consulte também Como atualizar vários elementos de matriz no mongodb, pois esses novos operadores de atualização realmente correspondem e atualizam "vários elementos de matriz" em vez de apenas o primeiro , que foi a ação anterior de atualizações posicionais.

OBSERVAÇÃO Um tanto ironicamente, já que isso é especificado no argumento "options" para .update() e como métodos, a sintaxe geralmente é compatível com todas as versões de driver de lançamento recentes.

No entanto, isso não é verdade para o mongo shell, já que a forma como o método é implementado lá ("ironicamente para compatibilidade com versões anteriores") o arrayFilters argumento não é reconhecido e removido por um método interno que analisa as opções para fornecer "compatibilidade com versões anteriores" com versões anteriores do servidor MongoDB e um .update() "legado" Sintaxe de chamada da API.

Então, se você quiser usar o comando no mongo shell ou outros produtos "baseados em shell" (principalmente Robo 3T), você precisa de uma versão mais recente da ramificação de desenvolvimento ou da versão de produção a partir de 3.6 ou superior.

Veja também positional all $[] que também atualiza "vários elementos de matriz", mas sem se aplicar a condições especificadas e se aplica a todos elementos na matriz onde essa é a ação desejada.