Comment utiliser $lookup dans MongoDB
MongoDB, une base de données NoSQL très répandue, organise les informations en collections. Ces collections sont constituées de documents, chacun contenant des données structurées en JSON. On peut voir les documents comme les lignes d'une base de données SQL, tandis que les collections correspondent aux tables.
Une fonction primordiale des bases de données est la capacité d'interroger les données. Cette interrogation permet d'extraire des informations précises, d'analyser les données, de les partager, et de les intégrer dans d'autres systèmes.
Pour une interrogation efficace, il est essentiel de pouvoir combiner les données de différentes tables (pour les bases SQL) ou collections (pour les bases NoSQL) en un ensemble de résultats unique.
Dans MongoDB, l'opérateur $lookup offre la possibilité de fusionner les informations de deux collections lors d'une requête. Il réalise une opération similaire à une jointure externe gauche dans le contexte d'une base de données SQL.
Utilisation et objectif de $lookup
Une fonction essentielle des bases de données est le traitement des données pour en extraire des informations pertinentes.
Prenons l'exemple d'une entreprise de restauration. Il serait utile d'analyser ses données pour savoir combien elle gagne chaque jour, quels plats sont les plus demandés le week-end, ou encore, le nombre de cafés vendus par heure.
Pour de telles analyses, des requêtes basiques ne suffisent pas. Il est nécessaire d'effectuer des requêtes avancées. MongoDB propose à cet effet une fonctionnalité appelée "pipeline d'agrégation".
Un pipeline d'agrégation est un mécanisme composé d'opérations appelées "étapes", qui traitent les données pour produire un résultat agrégé final. Parmi ces étapes, on trouve $sort, $match, $group, $merge, $count, et $lookup.
Ces étapes peuvent être combinées dans n'importe quel ordre au sein d'un pipeline. À chaque étape, les données subissent des transformations spécifiques.
$lookup est donc une étape du pipeline d'agrégation de MongoDB. Elle est utilisée pour effectuer une jointure externe gauche entre deux collections. Une jointure externe gauche combine tous les documents de la collection de gauche avec les documents correspondants de la collection de droite. Si une correspondance n'est pas trouvée, les champs correspondants de la collection de droite seront remplis avec la valeur "null".
Prenons l'exemple de deux collections, présentées sous forme de tableaux pour faciliter la compréhension :
commandes_collection :
| order_id | customer_id | order_date | total_amount |
| 1 | 100 | 2022-05-01 | 50.00 |
| 2 | 101 | 2022-05-02 | 75.00 |
| 3 | 102 | 2022-05-03 | 100.00 |
collection_clients :
| customer_num | customer_name | customer_email | customer_phone |
| 100 | John | [email protected] | [email protected] |
Si nous effectuons une jointure externe gauche sur ces collections, en utilisant le champ customer_id (présent dans commandes_collection) comme clé, et en considérant commandes_collection comme la collection de gauche et collection_clients comme la collection de droite, le résultat contiendra tous les documents de commandes_collection, ainsi que les documents de collection_clients dont le champ customer_num correspond à un customer_id d'un document de commandes_collection.
Le résultat final de la jointure externe gauche ressemblerait à ceci:
| order_id | customer_id | order_date | total_amount | customer_num | customer_name | customer_email | customer_phone |
| 1 | 100 | 2022-05-01 | 50.00 | 100 | John | [email protected] | [email protected] |
| 2 | 101 | 2022-05-02 | 75.00 | null | null | null | null |
| 3 | 102 | 2022-05-03 | 100.00 | null | null | null | null |
Pour le client avec customer_id 101 (dans commandes_collection) qui n'avait pas de valeur customer_num correspondante dans collection_clients, les valeurs manquantes correspondantes de la table client ont été remplies avec "null".
$lookup effectue une comparaison d'égalité stricte entre les champs et récupère l'intégralité du document correspondant, et non pas seulement les champs correspondants.
Syntaxe de $lookup
La syntaxe de $lookup est la suivante :
{
$lookup:
{
from: <collection à joindre>,
localField: <champ des documents d'entrée>,
foreignField: <champ des documents de la collection "from">,
as: <champ du tableau de sortie>
}
}
$lookup a quatre paramètres :
from– spécifie la collection à partir de laquelle on souhaite récupérer des documents. Dans notre exemple précédent, nous spécifierions la collectioncollection_clients.localField– est le champ de la collection de travail ou primaire, utilisé pour comparer les champs de la collection spécifiée dansfrom. Dans notre exemple,localFieldseraitcustomer_idprésent dansorders_collection.foreignField– est le champ à comparer dans la collection spécifiée dansfrom. Dans notre exemple, il s'agirait decustomer_numdans la collectioncollection_clients.as– est le nom du nouveau champ qui contiendra les documents résultant de la correspondance entrelocalFieldetforeignField. Toutes les correspondances sont stockées dans un tableau dans ce champ. En cas d'absence de correspondance, ce champ contiendra un tableau vide.
En reprenant nos deux collections précédentes, nous utiliserions le code suivant pour effectuer une opération $lookup sur les deux collections, avec orders_collection comme collection principale :
{
$lookup: {
from: "customers_collection",
localField: "customer_id",
foreignField: "customer_num",
as: "customer_info"
}
Notez que le champ as peut prendre n'importe quelle valeur de chaîne. Toutefois, si vous utilisez un nom de champ existant dans le document de travail, ce champ sera écrasé.
Joindre des données à partir de plusieurs collections
$lookup est une étape essentielle d'un pipeline d'agrégation dans MongoDB. Bien qu'elle ne soit pas obligatoire dans chaque pipeline, elle devient cruciale lors de requêtes complexes nécessitant de combiner des données issues de plusieurs collections.
L'étape $lookup réalise une jointure externe gauche entre deux collections, créant un nouveau champ (ou écrasant un champ existant) contenant un tableau de documents d'une autre collection. Ces documents sont sélectionnés en fonction de la correspondance de leurs valeurs avec le champ de comparaison. Le résultat est un champ contenant un tableau de documents si des correspondances sont trouvées, ou un tableau vide sinon.
Considérons les collections employés et projets ci-dessous:

Le code suivant peut être utilisé pour fusionner les deux collections :
db.projects.aggregate([
{
$lookup: {
from: "employees",
localField: "employees",
foreignField: "_id",
as: "assigned_employees"
}
}
])
Le résultat est une combinaison des deux collections, avec chaque projet listant les employés assignés sous forme de tableau.

Étapes de pipeline pouvant être utilisées avec $lookup
Comme mentionné, $lookup est une étape d'un pipeline d'agrégation MongoDB et peut être utilisé en conjonction avec d'autres étapes. Pour illustrer cela, nous utiliserons les deux collections suivantes.

Dans MongoDB, ces données sont stockées au format JSON. Voici à quoi ressemblent les collections ci-dessus dans MongoDB.

Voici quelques exemples d'étapes de pipeline d'agrégation qui peuvent être utilisées avec $lookup :
$match
$match est une étape qui permet de filtrer le flux de documents, en ne laissant passer que ceux qui répondent à une condition spécifiée. Il est recommandé d'utiliser cette étape au début du pipeline pour supprimer rapidement les documents inutiles et optimiser ainsi l'ensemble du pipeline.
En utilisant les deux collections précédentes, on peut combiner $match et $lookup de la manière suivante :
db.users.aggregate([
{
$match: {
country: "USA"
}
},
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "user_id",
as: "orders"
}
}
])
$match filtre les utilisateurs basés aux États-Unis. Le résultat est ensuite combiné avec $lookup pour obtenir les détails des commandes des utilisateurs américains. Le résultat de l'opération ci-dessus est illustré ci-dessous:

$project
$project est une étape utilisée pour remodeler les documents en spécifiant les champs à inclure, exclure ou ajouter. Par exemple, si vous traitez des documents avec dix champs, mais que seuls quatre champs sont nécessaires, vous pouvez utiliser $project pour filtrer les champs superflus. Cela évite d'envoyer des données inutiles à l'étape suivante.
Voici comment combiner $lookup et $project :
db.users.aggregate([
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "user_id",
as: "orders"
}
},
{
$project: {
name: 1,
_id: 0,
total_spent: { $sum: "$orders.price" }
}
}
])
Cette opération combine les collections users et orders via $lookup, puis $project affiche uniquement le nom de chaque utilisateur et le montant dépensé par chacun, en supprimant le champ _id des résultats. Le résultat de l'opération ci-dessus est illustré ci-dessous:

$unwind
$unwind est une étape qui déconstruit un champ de type tableau, en créant de nouveaux documents pour chaque élément du tableau. Cela est utile si vous souhaitez effectuer une agrégation sur les valeurs d'un champ de type tableau.
Par exemple, dans l'exemple ci-dessous, si vous souhaitez exécuter une agrégation sur le champ loisirs, vous ne pouvez pas le faire car il s'agit d'un tableau. Cependant, vous pouvez l'utiliser pour le dérouler à l'aide de $unwind, puis effectuer des agrégations sur les documents résultants.

En utilisant les collections users et orders, on peut combiner $lookup et $unwind comme suit :
db.users.aggregate([
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "user_id",
as: "orders"
}
},
{
$unwind: "$orders"
}
])
Dans ce code, $lookup retourne un champ de type tableau appelé orders. $unwind est ensuite utilisé pour déconstruire ce champ tableau. Le résultat de cette opération est illustré ci-dessous : Remarquez qu'Alice apparaît deux fois car elle a passé deux commandes.

Exemples de cas d'utilisation de $lookup
$lookup est un outil précieux lors du traitement de données. Par exemple, si vous avez deux collections que vous souhaitez fusionner en fonction de champs contenant des données similaires, une simple étape $lookup peut accomplir cette tâche et ajouter un nouveau champ aux collections primaires, contenant des documents provenant d'une autre collection.
Considérez les collections users et orders présentées ci-dessous :

Les deux collections peuvent être combinées à l'aide de $lookup pour obtenir le résultat suivant :

$lookup peut également être utilisé pour effectuer des jointures plus complexes. Il ne se limite pas à deux collections ; plusieurs étapes $lookup peuvent être utilisées pour fusionner plus de deux collections. Considérons les trois collections suivantes :

Le code ci-dessous permet de réaliser une jointure complexe sur ces trois collections afin d'obtenir toutes les commandes passées ainsi que les détails des produits commandés :
db.orders.aggregate([
{
$lookup: {
from: "order_items",
localField: "_id",
foreignField: "order_id",
as: "order_items"
}
},
{
$unwind: "$order_items"
},
{
$lookup: {
from: "products",
localField: "order_items.product_id",
foreignField: "_id",
as: "product_details"
}
},
{
$group: {
_id: "$_id",
customer: { $first: "$customer" },
total: { $sum: "$order_items.price" },
products: { $push: "$product_details" }
}
}
])
Le résultat de cette opération est illustré ci-dessous :

Conclusion
Lors du traitement de données impliquant plusieurs collections, $lookup est un outil précieux. Il permet de fusionner des données et de tirer des conclusions basées sur des données stockées dans plusieurs collections, car le traitement des données repose rarement sur une seule collection.
Pour obtenir des informations significatives, le regroupement des données issues de plusieurs collections est crucial. Ainsi, l'utilisation de l'étape $lookup dans votre pipeline d'agrégation MongoDB améliorera le traitement de vos données et vous permettra d'extraire des informations pertinentes des données brutes stockées dans vos collections.
N'hésitez pas à explorer davantage les commandes et requêtes MongoDB.