Това беше трудно за някой нов в MongoDb
aggregate
на . Ще разделя отговора си на стъпки, за да демонстрирам на други, които се опитват да агрегират масив с препратка към множество колекции.
Стъпка 1 - $match за филтриране на колекция
$match приема същите заявки като db.collection.find({})
и връща масив от съвпадащи резултати в случая по-долу, избирам 4 конкретни записа тук
{ '$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 }
]
Стъпка 2 - $unwind, за да можем да повторим с $loopup
В този набор от резултати има масив от обекти с { _id: <ObjectId>, watercraftType: <ModelName> }
за да преминем през масива и да обединим всеки от тези обекти със съответния запис на колекцията, трябва да разделим масива на отделни независими записи. $unwind
ще създаде нов набор от данни за следващия етап на обобщаване
{ '$unwind': '$watercraftContexts' },
$ unwind Резултат
Както можете да видите $unwind
сега създава запис с един watercraftContext
вече сме настроени да използваме $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 } ]
Стъпка 4 $lookup - Обединява всеки запис от чуждестранната колекция
Важно е да се отбележи, че трябва да $unwind
преди да извикате $lookup
за всяка различна колекция, към която трябва да се присъединим. Тъй като искаме да обединим множество колекции, трябва да съхраним резултата в обект с ключ от колекцията за по-късно агрегиране.
// 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
}
}
Стъпка 5 - Повторете $unwind и $lookup за другите присъединявания
Повторете горните стъпки за допълнителните съединения и въведете името на колекцията. Комбинирах обобщените етапи, за да демонстрирам повторението.
{ '$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' } }
Резултати от стъпка 4 и 5
Ако се вгледате внимателно, забелязвате, че един от Captain
записи съществуват 3 пъти с различен watercraftType
. $lookup
ще върне само записи, съответстващи на конкретно име на колекция. Ето защо ги съхранявайте в Object
ключ от 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: [] } } ]
Стъпка 6 $project - Използвайте project за изравняване на обектната карта на съединения
Можем да използваме project, за да изберем всички съществуващи данни и да изравним обектната карта на резултатите от обединяването в един масив.
{ '$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'
]
}
}
}
$project Резултат
Резултатите от горния $project
ще замени watercrafts
обект с изравнен масив от watercrafts
, но е важно да се отбележи, че все още има дублирани записи на Captain
където съответства на много различни търсения. Ще ги сглобим отново в следващата стъпка.
[ { _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 } ] } ]
Стъпка 7 $unwind и $group
Ние $unwind
така че сега можем да групираме всички watercrafts
принадлежащи на същия Captain
. Също така трябва да използваме $mergeObjects
за временно съхраняване на допълнителните данни от Captain
колекция под нова временна променлива за подготовка за последните етапи.
{ '$unwind': '$watercrafts' },
{ '$group':
{ _id: '$_id',
data:
{ '$mergeObjects':
{ name: '$name',
license: '$license',
classes: '$classes',
watercraftContexts: '$watercraftContexts',
__v: '$__v' } },
watercrafts: { '$push': '$watercrafts' } } }
$unwind
и $group
Резултат
Сега наистина стигаме някъде. Намалихме нашата трансформация до нашите първоначални 4 Captain
s и сплеска нашите съединения в единичен масив.
[ { _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 } ] } ]
Стъпка 8 $replaceRoot и $project
Всичко, което ни остава, е да обединим нашите data
в корена на всеки запис и премахнете временната променлива 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 Резултат
Сега имаме резултата, който поставихме за... Captain
с масив от смесени свързани типове 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 } ] } ]
И ето го...отне само 2 дни, за да разбера това. Надявам се, че ще ви спести малко време, ако се опитвате да направите подобно обобщено свързване. Приятно кодиране!
Краен тръбопровод
[
{ '$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 } }
]