C'était délicat pour quelqu'un qui ne connaissait pas MongoDb
aggregate
de . Je vais décomposer ma réponse en étapes pour montrer à d'autres qui tentent d'agréger un tableau en référençant plusieurs collections.
Étape 1 : $match pour filtrer la collecte
Le $match accepte les mêmes requêtes que db.collection.find({})
et renvoie un tableau de résultats correspondants dans le cas ci-dessous, je sélectionne 4 enregistrements spécifiques ici
{ '$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 }
]
Étape 2 - $unwind afin que nous puissions itérer avec $loopup
Dans cet ensemble de résultats, il existe un tableau d'objets avec { _id: <ObjectId>, watercraftType: <ModelName> }
pour boucler sur le tableau et joindre chacun de ces objets avec leur enregistrement de collection respectif, nous devons diviser le tableau en enregistrements individuels indépendants. Le $unwind
la fonctionnalité créera un nouvel ensemble de données pour la prochaine étape agrégée
{ '$unwind': '$watercraftContexts' },
$dérouler Résultat
Comme vous pouvez le voir, $unwind
crée maintenant un enregistrement avec un seul watercraftContext
nous sommes maintenant prêts à utiliser le $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 } ]
Étape 4 $lookup - Joint chaque enregistrement de la collection étrangère
Il est important de noter que nous devons $unwind
avant d'appeler $lookup
pour chaque collection différente que nous devons rejoindre. Puisque nous voulons joindre plusieurs collections, nous devons stocker le résultat dans un objet indexé par la collection pour une agrégation ultérieure.
// 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
}
}
Étape 5 - Répétez $unwind et $lookup pour les autres jointures
Répétez les étapes ci-dessus pour les jointures supplémentaires et entrez le nom de la collection. J'ai combiné les étapes agrégées pour démontrer la répétition.
{ '$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' } }
Résultats des étapes 4 et 5
Si vous regardez attentivement, vous remarquez que l'un des Captain
les enregistrements existent 3 fois avec un watercraftType
différent . $lookup
renverra uniquement les enregistrements correspondant à un nom de collection spécifique. C'est pourquoi pourquoi les stocker dans un Object
saisi par 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: [] } } ]
Étape 6 $project - Utilisez project pour aplatir la carte d'objets des jointures
Nous pouvons utiliser le projet pour sélectionner toutes les données existantes et aplatir la carte d'objets des résultats de jointure en un seul tableau.
{ '$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'
]
}
}
}
$projet Résultat
Les résultats du $project
ci-dessus remplacera les watercrafts
objet avec un tableau aplati de watercrafts
, mais il est important de noter qu'il existe encore des enregistrements en double de Captain
où la correspondance de nombreuses recherches différentes. Nous les reconstituerons à l'étape suivante.
[ { _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 } ] } ]
Étape 7 $dérouler et $grouper
Nous $unwind
afin que nous puissions maintenant regrouper tous les watercrafts
appartenant au même Captain
. Nous devons également utiliser $mergeObjects
pour stocker temporairement les données supplémentaires du Captain
collecte sous une nouvelle variable temporaire pour préparer les étapes finales.
{ '$unwind': '$watercrafts' },
{ '$group':
{ _id: '$_id',
data:
{ '$mergeObjects':
{ name: '$name',
license: '$license',
classes: '$classes',
watercraftContexts: '$watercraftContexts',
__v: '$__v' } },
watercrafts: { '$push': '$watercrafts' } } }
$unwind
et $group
Résultat
Maintenant, nous allons vraiment quelque part. Nous avons réduit notre transformation à nos 4 premiers Captain
s et aplati nos jointures en un seul tableau.
[ { _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 } ] } ]
Étape 8 $replaceRoot et $project
Il ne nous reste plus qu'à fusionner nos data
à la racine de chaque enregistrement et supprimez la variable temporaire 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 Résultat
Nous avons maintenant le résultat que nous avons prévu pour... Un Captain
avec un tableau de types mixtes associés 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 } ] } ]
Et voilà... il n'a fallu que 2 jours pour comprendre cela. J'espère que cela vous fera gagner du temps si vous essayez une association agrégée similaire. Bon codage !
Projet final
[
{ '$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 } }
]