Quindi ho fatto una domanda nei commenti ma sembra che tu te ne sia andato, quindi suppongo di rispondere solo ai tre possibili casi che vedo.
Per cominciare, non sono sicuro che gli elementi mostrati all'interno degli array nidificati siano solo elementi all'interno dell'array o in effetti se arrayToDelete
è l'solo campo presente in quegli elementi. Quindi fondamentalmente ho bisogno di astrarre un po' e includi quel 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 - Rimuovere gli elementi interni dell'array in cui è presente il campo
Questo userebbe il $pull
operatore poiché questo è ciò che rimuove gli elementi dell'array interamente. Lo fai nel moderno MongoDB con un'istruzione come questa:
db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{
"$pull": {
"scan.$[a]": { "arrayToDelete": { "$exists": true } }
}
},
{ "arrayFilters": [
{ "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
]
}
)
Ciò altera tutti i documenti corrispondenti come questo:
{
"_id" : ObjectId("5ca1c36d9e31550a618011e2"),
"field" : "value",
"field2" : "value",
"scan" : [
[
{
"somethingToKeep" : 1
}
],
[
{
"somethingToKeep" : 1
}
],
[
{
"somethingToKeep" : 1
}
]
]
}
Quindi ogni elemento che conteneva quel campo viene ora rimosso.
Caso 2 - Basta rimuovere il campo abbinato dagli elementi interni
Qui è dove usi $unset
. È solo un po' diverso da "indicizzato rigido" versione che stavi facendo:
db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{ "$unset": { "scan.$[].$[].arrayToDelete": "" } }
)
Che altera tutti i documenti abbinati in modo che siano:
{
"_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"
}
]
]
}
Quindi tutto è ancora lì, ma solo i campi identificati sono stati rimossi da ogni documento dell'array interno.
Caso 3 - Volevi effettivamente rimuovere "Tutto" nell'array.
Che in realtà è solo un semplice caso di utilizzo di $set
e cancellando tutto ciò che c'era prima:
db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{ "$set": { "scan": [] } }
)
Dove ci si dovrebbe aspettare abbastanza bene i risultati:
{
"_id" : ObjectId("5ca1c5c59e31550a618011e4"),
"field" : "value",
"field2" : "value",
"scan" : [ ]
}
Allora cosa stanno facendo tutti questi?
La prima cosa che dovresti vedere è il predicato della query . Questa è generalmente una buona idea per assicurarti di non corrispondere e persino di "tentare" avere condizioni di aggiornamento soddisfatte su documenti che non contengono nemmeno dati con lo schema che si intende aggiornare. Gli array annidati sono difficili nella migliore delle ipotesi, e dove pratico dovresti davvero evitarli, poiché spesso "intendi davvero" è effettivamente rappresentato in un array singolare con attributi aggiuntivi che rappresentano ciò che "pensa" la nidificazione sta effettivamente facendo per te.
Ma solo perché sono duri non significa impossibile . È solo che devi capire $elemMatch
:
db.colelction.find(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
}}
)
Questo è il find()
di base esempio, che corrisponde in base a $elemMatch
condizione per l'esterno l'array usa un altro $elemMatch
per soddisfare un'altra condizione nell'interno Vettore. Anche se questo "appare" essere un predicato singolare. Qualcosa come:
"scan.arrayToDelete": { "$exists": true }
Semplicemente non funzionerà. Nessuno dei due:
"scan..arrayToDelete": { "$exists": true }
Con il "doppio punto" ..
perché fondamentalmente non è valido.
Questo è il predicato della query per abbinare i "documenti" che devono essere elaborati, ma il resto vale per determinare effettivamente *quali parti aggiornare".
Nel caso 1 per $pull
dall'interno array, dobbiamo prima essere in grado di identificare quali elementi dell'esterno array contengono i dati da aggiornare. Questo è ciò che il "scan.$[a]"
cosa sta facendo usando il filtro posizionale $[<identifier>]
operatore.
In pratica quell'operatore traspone gli indici corrispondenti (così tanti di essi ) nell'array a un altro predicato che è definito nella terza sezione dell'update
comandi di stile con gli arrayFilters
sezione. Questa sezione definisce sostanzialmente le condizioni da soddisfare dal punto di vista dell'identificatore denominato.
In questo caso il nostro "identificatore" è denominato a
, e questo è il prefisso utilizzato negli arrayFilters
voce:
{ "arrayFilters": [
{ "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
]
}
Nel contesto dell'attuale dichiarazione di aggiornamento parte:
{
"$pull": {
"scan.$[a]": { "arrayToDelete": { "$exists": true } }
}
},
Quindi dal punto di vista del "a"
essendo l'identificatore per l'esterno elemento dell'array prima verso l'interno da "scan"
, si applicano le stesse condizioni del predicato della query originale ma da "dentro" il primo $elemMatch
dichiarazione. Quindi puoi sostanzialmente pensare a questo come a una "interrogazione all'interno di una query" dal punto di vista già "guardando dentro" il contenuto di ogni esterno elemento.
Allo stesso modo il $pull
agisce in modo molto simile a una "query all'interno di una query" in quanto i suoi argomenti vengono applicati anche dal punto di vista dell'elemento dell'array. Quindi solo arrayToDelete
campo esistente al posto di:
// This would be wrong! and do nothing :(
{
"$pull": {
"scan.$[a]": { "$elemMatch": { "arrayToDelete": { "$exists": true } } }
}
}
Ma questo è tutto specifico di $pull
e altre cose hanno casi diversi:
Il caso 2 guarda dove vuoi solo $unset
il campo denominato. Sembra abbastanza facile dato che dai un nome al campo, giusto? Beh, non esattamente poiché quanto segue chiaramente non è corretto da quello che sappiamo in precedenza:
{ "$unset": { "scan.arrayToDelete": "" } } // Not right :(
E ovviamente notare gli indici di array per tutto è solo una seccatura:
{ "$unset": {
"scan.0.0.arrayToDelete": "",
"scan.0.1.arrayToDelete": "",
"scan.0.2.arrayToDelete": "",
"scan.0.3.arrayToDelete": "", // My fingers are tired :-<
} }
Questo è il motivo del posizionale all $[]
operatore. Questo è un po' più di "forza bruta" rispetto al filtro posizionale $[<identifier>]
in quello invece di abbinare un altro predicato fornito all'interno di arrayFilters
, ciò che fa semplicemente si applica a tutto all'interno del contenuto dell'array in quell'"indice". In pratica è un modo per dire "tutti gli indici" senza digitarli tutti come l'orribile caso mostrato sopra.
Quindi non è per tutti i casi , ma è sicuramente adatto a un $unset
poiché ha una denominazione del percorso molto specifica che ovviamente non importa se quel percorso non corrisponde a ogni singolo elemento dell'array.
Potresti usa ancora un arrayFilters
e un filtro posizionale $[<identifier>]
, ma qui sarebbe eccessivo. Inoltre non fa male dimostrare l'altro approccio.
Ma ovviamente vale la pena capire come esattamente quella dichiarazione sembrerebbe, quindi:
db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{ "$unset": { "scan.$[a].$[b].arrayToDelete": "" } },
{
"arrayFilters": [
{ "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } },
{ "b.arrayToDelete": { "$exists": true } },
]
}
)
Notando che il "b.arrayToDelete"
potrebbe non essere quello che ti aspetti all'inizio, ma dato il posizionamento in "scan.$[a].$[b]
dovrebbe davvero avere senso come da b
il nome dell'elemento verrebbe raggiunto tramite "notazione punto" proprio come mostrato. E infatti in entrambi i casi. Ancora una volta, un $unset
si applicherebbe comunque solo al campo denominato, quindi i criteri di selezione non sono davvero richiesti.
E Caso 3 . Beh, è abbastanza semplice se non ne hai necessità per mantenere qualsiasi altra cosa nell'array dopo aver rimosso questo contenuto (ad esempio un $pull
dove i campi corrispondenti a questo erano gli solo cose lì dentro, o un $unset
se è per questo ), quindi semplicemente non scherzare con nient'altro e basta cancellare l'array .
Questa è una distinzione importante se si considera che al punto di chiarire se i documenti con il campo denominato sono solo elementi all'interno degli array nidificati, e in effetti che la chiave denominata era l'unica cosa presente nei documenti.
Con il ragionamento che usando $pull
come mostrato qui e in quelle condizioni otterresti:
{
"_id" : ObjectId("5ca321909e31550a618011e6"),
"field" : "value",
"field2" : "value",
"scan" : [
[ ],
[ ],
[ ]
]
}
O con il $unset
:
{
"_id" : ObjectId("5ca322bc9e31550a618011e7"),
"field" : "value",
"field2" : "value",
"scan" : [
[{ }, { }, { }, { }],
[{ }, { }, { }, { }],
[{ }, { }, { }, { }]
]
}
Entrambi chiaramente non sono desiderabili. Quindi sta alla tua ragione se arrayToDelete
il campo era solo contenuto che era presente, quindi il modo più logico per rimuovere tutto è semplicemente sostituire l'array con uno vuoto. O addirittura $unset
l'intera proprietà del documento.
Tieni presente, tuttavia, che tutte queste "cose fantastiche" ( con l'eccezione di $set
ovviamente ) richiedono che tu devi avere almeno MongoDB 3.6 disponibile per utilizzare questa funzionalità.
Nel caso in cui tu stia ancora eseguendo una versione precedente di MongoDB di quella (e alla data di scrittura, non dovresti davvero esserlo poiché il tuo supporto ufficiale scade in soli 5 mesi da questa data), quindi altre risposte esistenti su Come aggiornare più Gli elementi array in mongodb sono in realtà per te.