Création de pipelines d’agrégation MongoDB avec $match, $group et $lookup
Apprenez comment les étapes d’agrégation de MongoDB filtrent, regroupent, transforment, relient et trient les documents, ainsi que comment les chaîner en un pipeline capable de répondre à des questions de rapportage réelles.
Une requête simple find() convient bien pour récupérer les documents correspondant à une condition, mais elle ne peut pas calculer le chiffre d’affaires total par client, associer des détails d’utilisateur aux commandes ou générer un rapport classé. Pour cela, MongoDB propose le framework d’agrégation. Ce guide explique les cinq étapes que vous utiliserez le plus souvent, les petits pièges à éviter dans chacune d’elles, ainsi que la manière de les combiner en un pipeline qui produit un résultat propre et prêt à l’emploi.
Fonctionnement d’un pipeline d’agrégation
Une agrégation est une liste ordonnée d’étapes. Les documents entrent dans la première étape, chaque étape transforme le flux d’une manière spécifique, et ce qu’elle émet devient l’entrée de la étape suivante. En le considérant comme une chaîne de montage de données, il est facile de comprendre l’ordre des étapes.
Le pipeline simplifié ci-dessous ne conserve que les commandes terminées, additionne le montant pour chaque client et affiche en premier ceux qui ont le plus dépensé.
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
]);
Les sections suivantes examinent chaque étape séparément.
Filtrage des documents avec $match
$match accepte le même type de filtre que celui que l’on passerait à find(). Dans cet exemple, seuls les documents dont le status est égal à "completed" passent à l’étape suivante.
{
$match: {
status: "completed"
}
}
Placer $match le plus tôt possible. Chaque document supprimé dès le début diminue le nombre de documents que les étapes ultérieures doivent traiter. Un $match placé au début d’un pipeline peut utiliser les index de la collection, tandis que les étapes suivantes fonctionnent sur des résultats intermédiaires auxquels les index ne peuvent pas être utiles.
Grouper et calculer des totaux avec $group
$group rassemble les documents qui partagent une clé et calcule des valeurs pour chaque groupe. L’exemple génère un document par client indiquant le montant total de la commande.
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
}
Deux éléments effectuent le travail :
_iddéfinit la clé de regroupement ; ici, c’est la valeur du champcustomerIdde chaque document (le préfixe$signifie « lire ce champ »).$sumest un accumulateur qui additionne les valeurs deamountpour chaque document du groupe.
D’autres accumulateurs suivent le même schéma, notamment $avg, $min, $max et $count. N’oubliez pas que la sortie de $group ne contient que _id et les champs que vous avez calculés ; tous les autres champs des documents originaux ont disparu.
Sélectionner et reformater les champs avec $project
$project détermine quels champs apparaissent dans le résultat et peut en créer de nouveaux. En définissant un champ à 1, on l’inclut, tandis que _id: 0 cache explicitement l’identifiant, qui serait normalement inclus par défaut.
{
$project: {
customerId: 1,
totalSales: 1,
_id: 0
}
}
Il peut également calculer des valeurs. Ici, $multiply génère un champ totalWithTax en appliquant un facteur de taxe de 18 % à totalSales.
{
$project: {
customerId: 1,
totalWithTax: {
$multiply: ["$totalSales", 1.18]
}
}
}
Un détail est important lorsque ces extraits suivent une étape $group : l’identifiant du client se trouve alors dans _id, et non dans customerId ; par conséquent, customerId: 1 ne produirait rien. Le pipeline combiné à la fin gère cela en écrivant customerId: "$_id", ce qui renomme le champ. Considérez $project comme l’étape qui façonne la réponse finale renvoyée par votre API.
Associer une autre collection avec $lookup
Les documents font souvent référence à des données stockées ailleurs. Avec une collection orders et une collection users, $lookup récupère l’utilisateur correspondant pour chaque commande.
{
$lookup: {
from: "users",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
}
Pour chaque commande, MongoDB compare le customerId de la commande avec le _id dans la collection users et écrit toutes les correspondances dans un nouveau champ de type tableau nommé customer. C’est l’équivalent le plus proche d’une jointure SQL. Le résultat est toujours un tableau, même lorsqu’il n’y a qu’une seule correspondance ; il est donc courant d’ajouter $unwind après la recherche ou de lire le premier élément. Assurez-vous également que les deux champs ont le même type : un customerId de type chaîne ne correspondra pas à un ObjectId dans le champ _id.
Mettre en ordre les résultats avec $sort
$sort classe les documents selon un ou plusieurs champs. La valeur 1 effectue un tri ascendant tandis que -1 le fait de manière descendante, de sorte que l’exemple classe les clients du plus haut montant de totalSales au plus bas.
{
$sort: {
totalSales: -1
}
}
Combinaison des étapes en un seul rapport
La véritable valeur apparaît lorsque les étapes sont enchaînées. Le pipeline ci-dessous transforme les commandes brutes en une liste classée des clients et de leurs ventes totales :
db.orders.aggregate([
{
$match: {
status: "completed"
}
},
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
},
{
$project: {
customerId: "$_id",
totalSales: 1,
_id: 0
}
}
]);
Étape par étape, il :
- Conserve uniquement les commandes terminées à l’aide de
$match. - Les groupe par client avec
$group. - Somme les montants des commandes de chaque client pour obtenir
totalSales. - Classe les clients en fonction de ce total, du plus élevé au plus bas.
- Utilise
$projectpour renommer_idencustomerIdet supprimer_idde la réponse.
Pour inclure des noms ou des adresses e-mail, vous pourriez ajouter une instruction $lookup en référence à la collection users. En la plaçant après $group, ainsi qu’après tout $limit, la jointure s’exécute une fois par client plutôt qu’une fois par commande.
Points clés
Une manière concise de mémoriser les étapes essentielles :
$match: filtre les données.$group: regroupe et calcule les résultats.$project: sélectionne et reformate les données.$lookup: joint des collections.$sort: ordonne les résultats.
Ensemble, ils couvrent la majeure partie des besoins liés aux rapports, tableaux de bord, points d’accès analytiques et règles métier une fois que l’on dépasse les fonctions CRUD de base. L’ordre est important : filtrez tôt, groupez une fois, effectuez les jointures dès que les données le permettent et façonnez la sortie en dernier. Lors des entretiens et des revues de conception, savoir justifier pourquoi une étape se trouve à un endroit donné est plus convaincant que de simplement énumérer ses fonctions.
Lectures complémentaires
- Prévenir les mises à jour perdues dans Node.js et MongoDB sous écritures concurrentes — Découvrez comment les mises à jour conditionnelles atomiques, le verrouillage optimiste basé sur les versions, les réponses 409 et les transactions empêchent les écritures concurrentes dans MongoDB de supprimer silencieusement des données.