Toto bylo složité pro někoho nového v MongoDb
aggregate
. Svou odpověď rozdělím do kroků, které ukážu ostatním při pokusu o agregaci pole s odkazem na více kolekcí.
Krok 1 – $match pro filtrování sbírky
$match is přijímá stejné dotazy jako db.collection.find({})
a vrátí pole odpovídajících výsledků v případě níže, zde vyberu 4 konkrétní záznamy
{ '$match':
{ _id:
{ '$in':
[
ObjectId('5f7bdb3eea134b5a5c976285'),
ObjectId('5f7bdb3eea134b5a5c976283'),
ObjectId('5f7bdb3eea134b5a5c976284'),
ObjectId('5f7bdb3eea134b5a5c976289')
]
}
}
}
$match Result[
{ _id: ObjectId('5f7be0b37e2bdf5b19e4724d'),
name: 'CAPTAIN_SAIL',
classes: [ 'sail' ],
license: 'WC-1',
watercraftContexts:
[ { _id: ObjectId('5f7be0b37e2bdf5b19e47241'),
watercraftType: 'Sailboat',
ref: 'sailboats' } ],
__v: 0 },
{ _id: ObjectId('5f7be0b37e2bdf5b19e4724e'),
name: 'CAPTAIN_YATCH',
classes: [ 'yatch' ],
license: 'WC-2',
watercraftContexts:
[ { _id: ObjectId('5f7be0b37e2bdf5b19e47242'),
watercraftType: 'Yatch',
ref: 'yatches' } ],
__v: 0 },
{ _id: ObjectId('5f7be0b37e2bdf5b19e4724f'),
name: 'CAPTAIN_SHIP',
classes: [ 'ship' ],
license: 'WC-3',
watercraftContexts:
[ { _id: ObjectId('5f7be0b37e2bdf5b19e47243'),
watercraftType: 'Ship',
ref: 'ships' } ],
__v: 0 },
{ _id: ObjectId('5f7be0b37e2bdf5b19e47253'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
[ { _id: ObjectId('5f7be0b37e2bdf5b19e4724a'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
{ _id: ObjectId('5f7be0b37e2bdf5b19e4724b'),
watercraftType: 'Yatch',
ref: 'yatches' },
{ _id: ObjectId('5f7be0b37e2bdf5b19e4724c'),
watercraftType: 'Ship',
ref: 'ships' } ],
__v: 0 }
]
Krok 2 – $unwind, abychom mohli iterovat pomocí $loopup
V této sadě výsledků je pole objektů s { _id: <ObjectId>, watercraftType: <ModelName> }
pro smyčku přes pole a spojení každého z těchto objektů s příslušným záznamem kolekce, musíme pole rozdělit na jednotlivé nezávislé záznamy. $unwind
funkce vytvoří nový soubor dat pro další agregovanou fázi
{ '$unwind': '$watercraftContexts' },
$unwind Výsledek
Jak můžete vidět $unwind
nyní vytvoří záznam s jediným watercraftContext
nyní jsme nastaveni na použití $lookup
[ { _id: ObjectId('5f7be2231da37c5b5915bf9b'),
name: 'CAPTAIN_SAIL',
classes: [ 'sail' ],
license: 'WC-1',
watercraftContexts:
{ _id: ObjectId('5f7be2231da37c5b5915bf8f'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0 },
{ _id: ObjectId('5f7be2231da37c5b5915bf9c'),
name: 'CAPTAIN_YATCH',
classes: [ 'yatch' ],
license: 'WC-2',
watercraftContexts:
{ _id: ObjectId('5f7be2231da37c5b5915bf90'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0 },
{ _id: ObjectId('5f7be2231da37c5b5915bf9d'),
name: 'CAPTAIN_SHIP',
classes: [ 'ship' ],
license: 'WC-3',
watercraftContexts:
{ _id: ObjectId('5f7be2231da37c5b5915bf91'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0 },
{ _id: ObjectId('5f7be2231da37c5b5915bfa1'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7be2231da37c5b5915bf98'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0 },
{ _id: ObjectId('5f7be2231da37c5b5915bfa1'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7be2231da37c5b5915bf99'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0 },
{ _id: ObjectId('5f7be2231da37c5b5915bfa1'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7be2231da37c5b5915bf9a'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0 } ]
Krok 4 $lookup - Připojí každý záznam ze zahraniční sbírky
Je důležité si uvědomit, že musíme $unwind
před voláním $lookup
pro každou jinou sbírku se musíme připojit. Protože chceme spojit více kolekcí, potřebujeme uložit výsledek v objektovém klíči kolekcí pro pozdější agregaci.
// Only performs $lookup on 'ships' collection
{ '$lookup':
{ from: 'ships', // Collection Name - Note: repeat for each collection
localField: 'watercraftContexts._id', // The field with id to link
foreignField: '_id', // The field on the foreign collection to match
as: 'watercrafts.ships' // The path where to store the lookup result
}
}
Krok 5 – Opakujte $unwind a $lookup pro ostatní spojení
Opakujte výše uvedené kroky pro další spojení a zadejte název kolekce. Zkombinoval jsem souhrnné fáze, abych demonstroval opakování.
{ '$unwind': '$watercraftContexts' },
{ '$lookup':
{ from: 'yatches',
localField: 'watercraftContexts._id',
foreignField: '_id',
as: 'watercrafts.yatches' } },
{ '$unwind': '$watercraftContexts' },
{ '$lookup':
{ from: 'sailboats',
localField: 'watercraftContexts._id',
foreignField: '_id',
as: 'watercrafts.sailboats' } }
Krok 4 a 5 Výsledky
Když se podíváte pozorně, všimnete si, že jeden z Captain
záznamy existují 3krát s jiným watercraftType
. $lookup
vrátí pouze záznamy odpovídající konkrétnímu názvu kolekce. To je důvod, proč je ukládat do Object
klíčovaný collectionName
[
{ _id: ObjectId('5f7be7145320a65b942bb450'),
name: 'CAPTAIN_SAIL',
classes: [ 'sail' ],
license: 'WC-1',
watercraftContexts:
{ _id: ObjectId('5f7be7145320a65b942bb444'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0,
watercrafts:
{ ships: [],
yatches: [],
sailboats:
[ { _id: ObjectId('5f7be7145320a65b942bb444'),
class: 'sail',
name: 'Gone with the Wind',
__v: 0 } ] } },
{ _id: ObjectId('5f7be7145320a65b942bb451'),
name: 'CAPTAIN_YATCH',
classes: [ 'yatch' ],
license: 'WC-2',
watercraftContexts:
{ _id: ObjectId('5f7be7145320a65b942bb445'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0,
watercrafts:
{ ships: [],
yatches:
[ { _id: ObjectId('5f7be7145320a65b942bb445'),
class: 'yatch',
name: 'Liquid Gold',
__v: 0 } ],
sailboats: [] } },
{ _id: ObjectId('5f7be7145320a65b942bb452'),
name: 'CAPTAIN_SHIP',
classes: [ 'ship' ],
license: 'WC-3',
watercraftContexts:
{ _id: ObjectId('5f7be7145320a65b942bb446'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0,
watercrafts:
{ ships:
[ { _id: ObjectId('5f7be7145320a65b942bb446'),
class: 'ship',
name: 'Jenny',
__v: 0 } ],
yatches: [],
sailboats: [] } },
{ _id: ObjectId('5f7be7145320a65b942bb456'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7be7145320a65b942bb44d'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0,
watercrafts:
{ ships: [],
yatches: [],
sailboats:
[ { _id: ObjectId('5f7be7145320a65b942bb44d'),
class: 'sail',
name: 'Swell Shredder',
__v: 0 } ] } },
{ _id: ObjectId('5f7be7145320a65b942bb456'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7be7145320a65b942bb44e'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0,
watercrafts:
{ ships: [],
yatches:
[ { _id: ObjectId('5f7be7145320a65b942bb44e'),
class: 'yatch',
name: 'Audrey',
__v: 0 } ],
sailboats: [] } },
{ _id: ObjectId('5f7be7145320a65b942bb456'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7be7145320a65b942bb44f'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0,
watercrafts:
{ ships:
[ { _id: ObjectId('5f7be7145320a65b942bb44f'),
class: 'ship',
name: 'Jenny IV',
__v: 0 } ],
yatches: [],
sailboats: [] } } ]
Krok 6 $project - Použijte projekt ke zploštění mapy objektů spojení
Můžeme použít projekt k výběru všech existujících dat a sloučit mapu objektů výsledků spojení do jednoho pole.
{ '$project':
// keys with the value 'true' will be included
{ name: true,
license: true,
classes: true,
_id: true,
watercraftContexts: true,
__v: true,
watercrafts: // Re-assigns value of watercrafts
{ '$setUnion': // Accepts an array of arrays to flatten
[
'$watercrafts.ships',
'$watercrafts.yatches',
'$watercrafts.sailboats'
]
}
}
}
$výsledek projektu
Výsledky výše uvedeného $project
nahradí watercrafts
objekt se zploštěným polem watercrafts
, ale je důležité poznamenat, že stále existují duplicitní záznamy Captain
kde se shoduje mnoho různých vyhledávání. V dalším kroku je znovu poskládáme.
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9695'),
name: 'CAPTAIN_SAIL',
classes: [ 'sail' ],
license: 'WC-1',
watercraftContexts:
{ _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0,
watercrafts:
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),
class: 'sail',
name: 'Gone with the Wind',
__v: 0 } ] },
{ _id: ObjectId('5f7bea8d79dfe25bf3cb9696'),
name: 'CAPTAIN_YATCH',
classes: [ 'yatch' ],
license: 'WC-2',
watercraftContexts:
{ _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0,
watercrafts:
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),
class: 'yatch',
name: 'Liquid Gold',
__v: 0 } ] },
{ _id: ObjectId('5f7bea8d79dfe25bf3cb9697'),
name: 'CAPTAIN_SHIP',
classes: [ 'ship' ],
license: 'WC-3',
watercraftContexts:
{ _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0,
watercrafts:
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),
class: 'ship',
name: 'Jenny',
__v: 0 } ] },
{ _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0,
watercrafts:
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),
class: 'sail',
name: 'Swell Shredder',
__v: 0 } ] },
{ _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0,
watercrafts:
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),
class: 'yatch',
name: 'Audrey',
__v: 0 } ] },
{ _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
name: 'CAPTAIN_SAIL_YATCH_SHIP',
classes: [ 'sail', 'yatch', 'ship' ],
license: 'WC-7',
watercraftContexts:
{ _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0,
watercrafts:
[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),
class: 'ship',
name: 'Jenny IV',
__v: 0 } ] } ]
Krok 7 $unwind and $group
$unwind
takže nyní můžeme seskupit všechna watercrafts
patřící stejnému Captain
. Musíme také použít $mergeObjects
dočasně uložit další data od Captain
shromažďování pod novou dočasnou proměnnou pro přípravu na závěrečné fáze.
{ '$unwind': '$watercrafts' },
{ '$group':
{ _id: '$_id',
data:
{ '$mergeObjects':
{ name: '$name',
license: '$license',
classes: '$classes',
watercraftContexts: '$watercraftContexts',
__v: '$__v' } },
watercrafts: { '$push': '$watercrafts' } } }
$unwind
a $group
Výsledek
Teď se opravdu někam dostáváme. Snížili jsme naši transformaci na naše počáteční 4 Captain
s a srovnal naše spojení do jediného pole.
[ { _id: ObjectId('5f7bed5e271dd95c306c25a4'),
data:
{ name: 'CAPTAIN_SHIP',
license: 'WC-3',
classes: [ 'ship' ],
watercraftContexts:
{ _id: ObjectId('5f7bed5e271dd95c306c2598'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0 },
watercrafts:
[ { _id: ObjectId('5f7bed5e271dd95c306c2598'),
class: 'ship',
name: 'Jenny',
__v: 0 } ] },
{ _id: ObjectId('5f7bed5e271dd95c306c25a8'),
data:
{ name: 'CAPTAIN_SAIL_YATCH_SHIP',
license: 'WC-7',
classes: [ 'sail', 'yatch', 'ship' ],
watercraftContexts:
{ _id: ObjectId('5f7bed5e271dd95c306c25a1'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0 },
watercrafts:
[ { _id: ObjectId('5f7bed5e271dd95c306c259f'),
class: 'sail',
name: 'Swell Shredder',
__v: 0 },
{ _id: ObjectId('5f7bed5e271dd95c306c25a0'),
class: 'yatch',
name: 'Audrey',
__v: 0 },
{ _id: ObjectId('5f7bed5e271dd95c306c25a1'),
class: 'ship',
name: 'Jenny IV',
__v: 0 } ] },
{ _id: ObjectId('5f7bed5e271dd95c306c25a2'),
data:
{ name: 'CAPTAIN_SAIL',
license: 'WC-1',
classes: [ 'sail' ],
watercraftContexts:
{ _id: Object('5f7bed5e271dd95c306c2596'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0 },
watercrafts:
[ { _id: ObjectId('5f7bed5e271dd95c306c2596'),
class: 'sail',
name: 'Gone with the Wind',
__v: 0 } ] },
{ _id: ObjectId('5f7bed5e271dd95c306c25a3'),
data:
{ name: 'CAPTAIN_YATCH',
license: 'WC-2',
classes: [ 'yatch' ],
watercraftContexts:
{ _id: ObjectId('5f7bed5e271dd95c306c2597'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0 },
watercrafts:
[ { _id: ObjectId('5f7bed5e271dd95c306c2597'),
class: 'yatch',
name: 'Liquid Gold',
__v: 0 } ] } ]
Krok 8 $replaceRoot a $project
Zbývá nám pouze sloučit naše data
do kořenového adresáře každého záznamu a odstraňte dočasnou proměnnou data
// Merges 'data' into the root of each record
{ '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },
// Use $project to remove data (include only the fields we want)
{ '$project':
{ name: true,
license: true,
classes: true,
_id: true,
watercraftContexts: true,
__v: true,
watercrafts: true }
}
$replaceRoot &$project Result
Nyní máme výsledek, který jsme si stanovili pro...A Captain
s řadou smíšených přidružených typů watercrafts
[
{ name: 'CAPTAIN_SAIL_YATCH_SHIP',
license: 'WC-7',
classes: [ 'sail', 'yatch', 'ship' ],
watercraftContexts:
{ _id: ObjectId('5f7bf3b3680b375ca1755ea6'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0,
_id: ObjectId('5f7bf3b3680b375ca1755ead'),
watercrafts:
[ { _id: ObjectId('5f7bf3b3680b375ca1755ea4'),
class: 'sail',
name: 'Swell Shredder',
__v: 0 },
{ _id: ObjectId('5f7bf3b3680b375ca1755ea5'),
class: 'yatch',
name: 'Audrey',
__v: 0 },
{ _id: ObjectId('5f7bf3b3680b375ca1755ea6'),
class: 'ship',
name: 'Jenny IV',
__v: 0 } ] },
{ name: 'CAPTAIN_SAIL',
license: 'WC-1',
classes: [ 'sail' ],
watercraftContexts:
{ _id: ObjectId('5f7bf3b3680b375ca1755e9b'),
watercraftType: 'Sailboat',
ref: 'sailboats' },
__v: 0,
_id: ObjectId('5f7bf3b3680b375ca1755ea7'),
watercrafts:
[ { _id: ObjectId('5f7bf3b3680b375ca1755e9b'),
class: 'sail',
name: 'Gone with the Wind',
__v: 0 } ] },
{ name: 'CAPTAIN_YATCH',
license: 'WC-2',
classes: [ 'yatch' ],
watercraftContexts:
{ _id: ObjectId('5f7bf3b3680b375ca1755e9c'),
watercraftType: 'Yatch',
ref: 'yatches' },
__v: 0,
_id: ObjectId('5f7bf3b3680b375ca1755ea8'),
watercrafts:
[ { _id: ObjectId('5f7bf3b3680b375ca1755e9c'),
class: 'yatch',
name: 'Liquid Gold',
__v: 0 } ] },
{ name: 'CAPTAIN_SHIP',
license: 'WC-3',
classes: [ 'ship' ],
watercraftContexts:
{ _id: ObjectId('5f7bf3b3680b375ca1755e9d'),
watercraftType: 'Ship',
ref: 'ships' },
__v: 0,
_id: ObjectId('5f7bf3b3680b375ca1755ea9'),
watercrafts:
[ { _id: ObjectId('5f7bf3b3680b375ca1755e9d'),
class: 'ship',
name: 'Jenny',
__v: 0 } ] } ]
A tady to máte... trvalo to jen 2 dny, než jsem na to přišel. Doufám, že vám to ušetří čas, pokud se pokoušíte o podobnou agregovanou asociaci. Hodně štěstí při kódování!
Final Pipeline
[
{ '$match':
{ _id:
{ '$in':
[ ObjectId('5f7bf3b3680b375ca1755ea9'),
ObjectId('5f7bf3b3680b375ca1755ea7'),
ObjectId('5f7bf3b3680b375ca1755ea8'),
ObjectId('5f7bf3b3680b375ca1755ead')
]
}
}
},
{ '$unwind': '$watercraftContexts' },
{ '$lookup':
{ from: 'ships',
localField: 'watercraftContexts._id',
foreignField: '_id',
as: 'watercrafts.ships' } },
{ '$unwind': '$watercraftContexts' },
{ '$lookup':
{ from: 'yatches',
localField: 'watercraftContexts._id',
foreignField: '_id',
as: 'watercrafts.yatches' } },
{ '$unwind': '$watercraftContexts' },
{ '$lookup':
{ from: 'sailboats',
localField: 'watercraftContexts._id',
foreignField: '_id',
as: 'watercrafts.sailboats' } },
{ '$project':
{ name: true,
license: true,
classes: true,
_id: true,
watercraftContexts: true,
__v: true,
watercrafts:
{ '$setUnion':
[ '$watercrafts.ships',
'$watercrafts.yatches',
'$watercrafts.sailboats' ] } } },
{ '$unwind': '$watercrafts' },
{ '$group':
{ _id: '$_id',
data:
{ '$mergeObjects':
{ name: '$name',
license: '$license',
classes: '$classes',
watercraftContexts: '$watercraftContexts',
__v: '$__v' } },
watercrafts: { '$push': '$watercrafts' } } },
{ '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },
{ '$project':
{ name: true,
license: true,
classes: true,
_id: true,
watercraftContexts: true,
__v: true,
watercrafts: true } }
]