To było trudne dla kogoś nowego w MongoDb
aggregate
. Podzielę moją odpowiedź na kroki, aby zademonstrować innym próbom agregacji tablicy z odwoływaniem się do wielu kolekcji.
Krok 1 – $dopasuj do filtrowania według kolekcji
$match akceptuje te same zapytania, co db.collection.find({})
i zwraca tablicę pasujących wyników w poniższym przypadku, wybieram tutaj 4 konkretne rekordy
{ '$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 – $odpręż się, abyśmy mogli iterować za pomocą pętli $
W tym zestawie wyników znajduje się tablica obiektów z { _id: <ObjectId>, watercraftType: <ModelName> }
aby zapętlić tablicę i połączyć każdy z tych obiektów z odpowiednim rekordem kolekcji, musimy podzielić tablicę na pojedyncze niezależne rekordy. $unwind
funkcja utworzy nowy zestaw danych dla następnego etapu agregacji
{ '$unwind': '$watercraftContexts' },
$rozwiń wynik
Jak widać $unwind
teraz tworzy rekord z jednym watercraftContext
teraz używamy $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 — Dołącza do każdego rekordu z kolekcji zagranicznej
Ważne jest, aby pamiętać, że musimy $unwind
przed wywołaniem $lookup
dla każdej innej kolekcji, do której musimy dołączyć. Ponieważ chcemy połączyć wiele kolekcji, musimy przechowywać wynik w zakwestionowanym kluczu kolekcji w celu późniejszej agregacji.
// 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 – Powtórz $unwind i $lookup dla innych złączeń
Powtórz powyższe kroki dla dodatkowych sprzężeń i wprowadź nazwę kolekcji. Połączyłem etapy zbiorcze, aby zademonstrować powtórzenie.
{ '$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' } }
Wyniki kroku 4 i 5
Jeśli przyjrzysz się uważnie, zauważysz, że jeden z Captain
rekordy istnieją 3 razy z innym watercraftType
. $lookup
zwróci tylko rekordy pasujące do określonej nazwy kolekcji. Dlatego przechowuj je w Object
kluczowany przez 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 — Użyj projektu, aby spłaszczyć mapę obiektów złączeń
Możemy użyć projektu, aby wybrać wszystkie istniejące dane i spłaszczyć mapę obiektów wyników łączenia w jedną tablicę.
{ '$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'
]
}
}
}
$projekt Wynik
Wyniki powyższego $project
zastąpi watercrafts
obiekt ze spłaszczoną tablicą watercrafts
, ale ważne jest, aby pamiętać, że nadal istnieją zduplikowane rekordy Captain
gdzie dopasowuje się wiele różnych wyszukiwań. W następnym kroku połączymy je ponownie.
[ { _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 $odpręż się i $grupuj
$unwind
dzięki czemu możemy teraz pogrupować wszystkie watercrafts
należący do tego samego Captain
. Musimy również użyć $mergeObjects
tymczasowo przechowywać dodatkowe dane od Captain
kolekcja pod nową zmienną tymczasową, aby przygotować się do końcowych etapów.
{ '$unwind': '$watercrafts' },
{ '$group':
{ _id: '$_id',
data:
{ '$mergeObjects':
{ name: '$name',
license: '$license',
classes: '$classes',
watercraftContexts: '$watercraftContexts',
__v: '$__v' } },
watercrafts: { '$push': '$watercrafts' } } }
$unwind
i $group
Wynik
Teraz naprawdę do czegoś dochodzimy. Zredukowaliśmy naszą transformację do naszych początkowych 4 Captain
s i spłaszczyliśmy nasze złączenia w jedną tablicę.
[ { _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 i $projekt
Pozostało nam tylko scalić nasze data
do katalogu głównego każdego rekordu i usuń tymczasową zmienną 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 i $wynik projektu
Teraz mamy wynik, który ustaliliśmy dla... Captain
z tablicą mieszanych powiązanych typów 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 } ] } ]
I masz to... tylko 2 dni zajęło rozgryzienie tego. Mam nadzieję, że zaoszczędzi ci to trochę czasu, jeśli spróbujesz podobnego skojarzenia zbiorczego. Miłego kodowania!
Końcowy potok
[
{ '$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 } }
]