Utilizzare la proiezione mongodb per nidificare i documenti intere?
-
21-12-2019 - |
Domanda
Ho una raccolta piatta di documenti, in cui alcuni documenti hanno un campo parent: ObjectId
, che punta un altro documento dalla stessa collezione, I.e.:
{id: 1, metadata: {text: "I'm a parent"}}
{id: 2, metadata: {text: "I'm child 1", parent: 1}}
.
Ora vorrei recuperare tutti i genitori dove metadata.text = "I'm a parent"
Plus It's Child Elements .Ma voglio che i dati in un formato nidificato, così posso semplicemente elaborarlo in seguito senza dare un'occhiata a metadata.parent
.L'output dovrebbe essere simile a:
{
id: 1,
metadata: {text: "I'm a parent"},
children: [
{id: 2, metadata: {text: "I'm child 1", parent: 1}}
]
}
.
(children
potrebbe anche essere parte dell'oggetto metadata
del genitore se è più facile)
Perché non salvare i documenti in una struttura annidata? non voglio archiviare i dati in un formato nidificato in DB, poiché questi documenti fanno parte di GRIDFS.
Il problema principale è: Come posso dire a mongodb di nidificare un intero documento ?Oppure devo usare il framework di aggregazione del mongo per quel compito?
Soluzione
Per il tipo di "proiezione" che stai chiedendo, allora il framework di aggregazione è lo strumento corretto come questo tipo di "documento ri-shaping" è solo supportato lì.
L'altro caso è la cosa "genitore / figlio", in cui hai ancora bisogno di essere "creativo" durante il raggruppamento utilizzando il quadro di aggregazione. Le operazioni complete mostrano ciò che è essenzialmente coinvolto:
db.collection.aggregate([
// Group parent and children together with conditionals
{ "$group": {
"_id": { "$ifNull": [ "$metadata.parent", "$_id" ] },
"metadata": {
"$addToSet": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
false,
"$metadata"
]
}
},
"children": {
"$push": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
"$$ROOT",
false
]
}
}
}},
// Filter out "false" values
{ "$project": {
"metadata": { "$setDifference": [ "$metadata", [false] ] },
"children": { "$setDifference": [ "$children", [false] ] }
}},
// metadata is an array but should only have one item
{ "$unwind": "$metadata" },
// This is essentially sorting the children as "sets" are un-ordered
{ "$unwind": "$children" },
{ "$sort": { "_id": 1, "children._id": 1 } },
{ "$group": {
"_id": "$_id",
"metadata": { "$first": "$metadata" },
"children": { "$push": "$children" }
}}
])
.
La cosa principale qui è il $ifNull
< / A> Operatore utilizzato sul gruppo _id
. Questo sceglierà di $group
sul Campo "Parent" dove presente, altrimenti utilizzando il documento generale _id
.
Le cose simili vengono eseguite con il $cond
< / A> Operatore in un secondo momento in cui è fatta la valutazione con cui i dati da aggiungere all'array o "Set". In quanto segue $project
I valori false
sono filtrati dall'uso del $setDifference
Operatore.
Se l'ultimo $sort
e $group
Sembrano confuso, quindi Il motivo attuale è dovuto all'operatore utilizzato è un operatore "impostato" che il "set impostato" risultante è considerato non ordinato. Quindi, in realtà, quella parte è solo lì per assicurarsi che il contenuto dell'array appaia in ordine del proprio campo _id
.
Senza gli operatori aggiuntivi di Mongodb 2.6 Questo può ancora essere fatto, ma solo un po 'in modo diverso.
db.collection.aggregate([
{ "$group": {
"_id": { "$ifNull": [ "$metadata.parent", "$_id" ] },
"metadata": {
"$addToSet": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
false,
"$metadata"
]
}
},
"children": {
"$push": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
{ "_id": "$_id","metadata": "$metadata" },
false
]
}
}
}},
{ "$unwind": "$metadata" },
{ "$match": { "metadata": { "$ne": false } } },
{ "$unwind": "$children" },
{ "$match": { "children": { "$ne": false } } },
{ "$sort": { "_id": 1, "children._id": 1 } },
{ "$group": {
"_id": "$_id",
"metadata": { "$first": "$metadata" },
"children": { "$push": "$children" }
}}
])
.
Essenzialmente la stessa cosa ma senza gli operatori più recenti introdotti a Mongodb 2.6, quindi questo funzionerebbe anche nelle versioni precedenti.
Questo andrà tutto bene finché le tue relazioni sono un singolo livello di genitore e figlio. Per i livelli nidificati è necessario richiamare invece un processo di Mapreduce.
Altri suggerimenti
Volevo un risultato simile alla risposta di Neil Lunn, tranne che volevo recuperare tutti i genitori indipendentemente da loro che hanno figli o meno.Volevo anche generalizzarlo per lavorare su qualsiasi collezione che aveva un unico livello di bambini annidati.
Ecco la mia query basata sulla risposta di Neil Lunn
db.collection.aggregate([
{
$group: {
_id: {
$ifNull: ["$parent", "$_id"]
},
parent: {
$addToSet: {
$cond: [
{
$ifNull: ["$parent", false]
}, false, "$$ROOT"
]
}
},
children: {
$push: {
$cond: [
{
$ifNull: ["$parent", false]
}, "$$ROOT", false
]
}
}
}
}, {
$project: {
parent: {
$setDifference: ["$parent", [false]]
},
children: {
$setDifference: ["$children", [false]]
}
}
}, {
$unwind: "$parent"
}
])
.
Ciò si traduce in ogni genitore restituito dove il campo principale contiene l'intero documento genitore e il campo dei bambini che restituisce un array vuoto se il genitore non ha figli o una serie di documenti figlio.
{
_id: PARENT_ID
parent: PARENT_OBJECT
children: [CHILD_OBJECTS]
}
.