Atualizando uma Matriz Nested com o MongoDB

Eu estou tentando atualizar um valor na matriz aninhada, mas não consigo fazê-lo funcionar.

Meu object é assim

{ "_id": { "$oid": "1" }, "array1": [ { "_id": "12", "array2": [ { "_id": "123", "answeredBy": [], }, { "_id": "124", "answeredBy": [], } ], } ] } 

Eu preciso empurrar um valor para “answerBy” array.

No exemplo abaixo, tentei empurrar a string “success” para a matriz “answeredBy” do object “123 _id”, mas ela não funciona.

 callback = function(err,value){ if(err){ res.send(err); }else{ res.send(value); } }; conditions = { "_id": 1, "array1._id": 12, "array2._id": 123 }; updates = { $push: { "array2.$.answeredBy": "success" } }; options = { upsert: true }; Model.update(conditions, updates, options, callback); 

Eu encontrei este link , mas sua resposta apenas diz que eu deveria usar object como estrutura em vez de array. Isso não pode ser aplicado na minha situação. Eu realmente preciso que meu object seja nested em matrizes

Seria ótimo se você pudesse me ajudar aqui. Eu tenho passado horas para descobrir isso.

Agradeço antecipadamente!

Escopo Geral e Explicação

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

Para entrar em um valor “nested” e presumir que o valor _id é único e não apareceria em nenhum outro documento, o 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 golpe de sorte, pois há boas razões para que não funcione para você.

A leitura importante está na documentação oficial para o operador $ posicional sob o assunto de “Arrays nesteds”. O que isto diz é:

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

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

Portanto, se você observar a notação da consulta como mostrada, teremos “codificado” a primeira posição (ou índice 0) na matriz de nível superior, e acontece que o elemento correspondente dentro de “array2” também é a input do índice zero.

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

Então esse é o problema geral com matrizes de aninhamento. Você poderia remover um dos níveis e ainda seria capaz de $push para o elemento correto em sua matriz “superior”, mas ainda haveria vários níveis.

Tente evitar matrizes de aninhamento, pois você terá problemas de atualização, como é mostrado.

O caso geral é “achatar” as coisas que você “pensa” são “níveis” e realmente fazer esses “atributos” nos itens 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 only 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 operador $ posicional


MongoDB 3.6 e Acima

A partir do MongoDB 3.6, há novos resources disponíveis para trabalhar com matrizes aninhadas. Isso usa a syntax filtrada posicional $[] para corresponder aos elementos específicos e aplicar condições diferentes por meio de arrayFilters na instruçã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" }] } ) 

O "arrayFilters" conforme transmitido para as opções do .update() ou até mesmo .updateOne() , .updateMany() , .findOneAndUpdate() ou .bulkWrite() , especifica as condições a serem correspondidas no identificador fornecido na instrução de atualização. Quaisquer elementos que correspondam à condição dada 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 a syntax filtrada posicional $[] realmente usada no bloco de atualização da instrução. Nesse caso, inner e outer são os identificadores usados ​​para cada condição, conforme especificado na cadeia aninhada.

Essa nova expansão torna possível a atualização do conteúdo de matrizes aninhadas, mas não ajuda muito com a praticidade de “consultar” esses dados, de modo que as mesmas restrições se aplicam conforme explicado anteriormente.

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

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

OBSERVAÇÃO Ironicamente, como isso é especificado no argumento “options” para .update() e methods semelhantes, a syntax é geralmente compatível com todas as versões recentes do driver de liberação.

No entanto, isso não é verdade para o mongo shell, já que o método é implementado lá (“ironicamente para compatibilidade com versões anteriores”) o argumento arrayFilters não é reconhecido e removido por um método interno que analisa as opções para fornecer “compatibilidade reversa” com versões anteriores do servidor MongoDB e uma syntax de chamada de API “. legacy” .update() .

Portanto, se você quiser usar o comando no shell mongo ou em outros produtos “baseados em shell” (especialmente o Robo 3T), você precisará de uma versão mais recente do branch de desenvolvimento ou do release de produção a partir de 3.6 ou superior.

Veja também positional all $[] que também atualiza “múltiplos elementos de matriz”, mas sem aplicar a condições especificadas e se aplica a todos os elementos da matriz, onde essa é a ação desejada.

Eu sei que esta é uma questão muito antiga, mas eu apenas lutei com esse problema e encontrei, o que eu acredito ser, uma resposta melhor.

Uma maneira de resolver esse problema é usar Sub-Documents . Isso é feito pelos esquemas de aninhamento em seus esquemas

 MainSchema = new mongoose.Schema({ array1: [Array1Schema] }) Array1Schema = new mongoose.Schema({ array2: [Array2Schema] }) Array2Schema = new mongoose.Schema({ answeredBy": [...] }) 

Desta forma, o object será parecido com o que você mostra, mas agora cada array é preenchido com sub-documentos. Isso possibilita que você insira o sub-documento desejado. Em vez de usar um .update , use um .find ou .findOne para obter o documento que deseja atualizar.

 Main.findOne(( { _id: 1 } ) .exec( function(err, result){ result.array1.id(12).array2.id(123).answeredBy.push('success') result.save(function(err){ console.log(result) }); } ) 

Eu não usei a function .push( ) dessa maneira, então a syntax pode não estar certa, mas eu usei tanto .set() quanto .remove() , e ambos funcionam perfeitamente.