La fonctionnalité d'agrégation de MongoDB est un outil puissant pour le traitement des données, permettant des opérations complexes sur les documents d'une collection pour renvoyer des résultats agrégés. Elle fonctionne via un pipeline d'étapes qui transforment les documents d'entrée.
Préparation des Données d'Exemple
Pour illustrer les différentes étapes du pipeline d'agrégation, nous allons initialiser une collection d'étudiants:
db.etudiants.drop(); // Supprime la collection si elle existe
db.etudiants.insertOne({_id: 1, nom: "Alice Martin", age: 12, professeurs: ["M. Dubois", "Mme Leclerc"]});
db.etudiants.insertOne({_id: 2, nom: "Bob Dupont", age: 15, professeurs: ["Mme Petit", "M. Dubois"]});
db.etudiants.insertOne({_id: 3, nom: "Carole Bernard", age: 11, professeurs: ["Mme Lefevre", "Mme Leclerc"]});
Comprendre le Pipeline d'Agrégation
Un pipeline d'agrégation est une séquence d'étapes. Chaque étape prend des documents en entrée, effectue une opération sur ces documents et passe les documents résultants à l'étape suivante. Les étapes les plus courantes incluent la filtration, la transformation et le regroupement.
L'Étape $project
L'opérateur $project sélectionne les champs à inclure ou exclure des documents transmis à l'étape suivante du pipeline. Il permet également de créer de nouveaux champs ou de renommer des champs existants.
{$project: { <specification> }}
// Les valeurs peuvent être:
// <champ>: 1 ou true (inclut le champ)
// <champ>: 0 ou false (exclut le champ)
// <champ>: <expression> (crée ou modifie le champ avec le résultat de l'expression)
Par défaut, le champ _id est toujours inclus, sauf si explicitement exclu.
Exemples d'utilisation de $project:
Affichons d'abord tous les documents de la collection etudiants:
> db.etudiants.find()
{ "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : [ "M. Dubois", "Mme Leclerc" ] }
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : [ "Mme Petit", "M. Dubois" ] }
{ "_id" : 3, "nom" : "Carole Bernard", "age" : 11, "professeurs" : [ "Mme Lefevre", "Mme Leclerc" ] }
Sélectionner uniquement le champ nom:
> db.etudiants.aggregate([{$project: {nom: 1}}])
{ "_id" : 1, "nom" : "Alice Martin" }
{ "_id" : 2, "nom" : "Bob Dupont" }
{ "_id" : 3, "nom" : "Carole Bernard" }
Exclure le champ _id tout en incluant le nom:
> db.etudiants.aggregate([{$project: {nom: true, _id: 0}}])
{ "nom" : "Alice Martin" }
{ "nom" : "Bob Dupont" }
{ "nom" : "Carole Bernard" }
Renommer un champ avec $project
Pour référencer un champ existant et lui donner un nouveau nom, utilisez le préfixe $ devant le nom du champ original.
// Une tentative sans guillemets autour de $nom résultera en une erreur car $nom serait interprété comme une variable JavaScript.
// db.etudiants.aggregate([{$project: {nouveauNom: $nom}}]) // ERREUR
// Renomme le champ 'nom' en 'nouveauNom'
> db.etudiants.aggregate([{$project: {nouveauNom: "$nom"}}])
{ "_id" : 1, "nouveauNom" : "Alice Martin" }
{ "_id" : 2, "nouveauNom" : "Bob Dupont" }
{ "_id" : 3, "nouveauNom" : "Carole Bernard" }
Variables système $$CURRENT et $$ROOT
Les variables $$CURRENT et $$ROOT représentent l'intégralité du document en cours de traitement. Dans la plupart des contextes, elles sont interchangeables dans la première étape d'un pipeline d'agrégation, représentant le document d'entrée complet.
// Utilisation de $$CURRENT
> db.etudiants.aggregate([{$project: {_id: 0, nom: 1, documentComplet: "$$CURRENT"}}])
{ "nom" : "Alice Martin", "documentComplet" : { "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : [ "M. Dubois", "Mme Leclerc" ] } }
{ "nom" : "Bob Dupont", "documentComplet" : { "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : [ "Mme Petit", "M. Dubois" ] } }
{ "nom" : "Carole Bernard", "documentComplet" : { "_id" : 3, "nom" : "Carole Bernard", "age" : 11, "professeurs" : [ "Mme Lefevre", "Mme Leclerc" ] } }
// Utilisation de $$ROOT (résultat identique ici)
> db.etudiants.aggregate([{$project: {_id: 0, nom: 1, documentRacine: "$$ROOT"}}])
{ "nom" : "Alice Martin", "documentRacine" : { "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : [ "M. Dubois", "Mme Leclerc" ] } }
{ "nom" : "Bob Dupont", "documentRacine" : { "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : [ "Mme Petit", "M. Dubois" ] } }
{ "nom" : "Carole Bernard", "documentRacine" : { "_id" : 3, "nom" : "Carole Bernard", "age" : 11, "professeurs" : [ "Mme Lefevre", "Mme Leclerc" ] } }
Créer un tableau combiné
On peut combiner des champs dans un nouveau tableau:
> db.etudiants.aggregate([{$project: {identifiantsNoms: ["$_id","$nom"]}}])
{ "_id" : 1, "identifiantsNoms" : [ 1, "Alice Martin" ] }
{ "_id" : 2, "identifiantsNoms" : [ 2, "Bob Dupont" ] }
{ "_id" : 3, "identifiantsNoms" : [ 3, "Carole Bernard" ] }
Utilisation de $substr pour l'extraction de chaînes
L'opérateur $substr extrait une sous-chaîne d'une chaîne donnée. Sa syntaxe est {$substr: [<chaîne>, <début>, <longueur>]}.
// Tronquer les noms des étudiants, en commençant à l'index 1 et prenant 3 caractères.
// (Attention: l'index commence à 0)
> db.etudiants.aggregate([{$project: {extraitNom: {$substr: ["$nom",1,3]}}}]);
{ "_id" : 1, "extraitNom" : "lic" }
{ "_id" : 2, "extraitNom" : "ob " }
{ "_id" : 3, "extraitNom" : "aro" }
Voici un exemple plus complexe avec une nouvelle collection:
db.livresInfo.drop();
db.livresInfo.insertOne({_id: 1, titre: "Les Misérables", codeISBN: "978207036496", auteur: {nomFamille: "Hugo", prenom: "Victor"}, exemplaires: 10});
> db.livresInfo.find().pretty();
{
"_id" : 1,
"titre" : "Les Misérables",
"codeISBN" : "978207036496",
"auteur" : {
"nomFamille" : "Hugo",
"prenom" : "Victor"
},
"exemplaires" : 10
}
// Extraire des parties du code ISBN
> db.livresInfo.aggregate([{$project: {titre: 1, infosISBN: {prefixe: {$substr: ["$codeISBN",0,3]}, editeur: {$substr: ["$codeISBN",3,3]}}}}]);
{ "_id" : 1, "titre" : "Les Misérables", "infosISBN" : { "prefixe" : "978", "editeur" : "207" } }
// Inclure le nom de famille de l'auteur
> db.livresInfo.aggregate([{$project: {titre: 1, infosISBN: {prefixe: {$substr: ["$codeISBN",0,3]}, editeur: {$substr: ["$codeISBN",3,3]}}, nomAuteur: "$auteur.nomFamille"}}]);
{ "_id" : 1, "titre" : "Les Misérables", "infosISBN" : { "prefixe" : "978", "editeur" : "207" }, "nomAuteur" : "Hugo" }
L'Étape $match
L'opérateur $match filtre les documents en fonction des conditions spécifiées, similaires à l'opération find(). Seuls les documents correspondant au critère passent à l'étape suivante du pipeline.
{ $match: { <prédicat de requête> } }
Optimisation du Pipeline avec $match
- Placez
$matchle plus tôt possible dans le pipeline d'agrégation. Cela réduit le nombre de documents à traiter par les étapes suivantes. - Un
$matchplacé en début de pipeline peut utiliser des index, améliorant ainsi considérablement les performances.
Exemples d'utilisation de $match:
Créons une collection de publiactions:
db.publications.drop();
db.publications.insertOne({auteur: "David", evaluation: 80, vues: 100});
db.publications.insertOne({auteur: "David", evaluation: 85, vues: 521});
db.publications.insertOne({auteur: "Anna", evaluation: 60, vues: 1000});
db.publications.insertOne({auteur: "Lucie", evaluation: 55, vues: 5000});
db.publications.insertOne({auteur: "AnneT", evaluation: 60, vues: 50});
db.publications.insertOne({auteur: "Lucie", evaluation: 94, vues: 999});
db.publications.insertOne({auteur: "Tom", evaluation: 95, vues: 1000});
Filtrer les publications par un auteur spécifique:
// Équivalent à db.publications.find({auteur: "David"})
> db.publications.aggregate([{$match: {auteur: "David"}}])
{ "_id" : ObjectId("67bd0886949fac4cce717ab0"), "auteur" : "David", "evaluation" : 80, "vues" : 100 }
{ "_id" : ObjectId("67bd0886949fac4cce717ab1"), "auteur" : "David", "evaluation" : 85, "vues" : 521 }
Utilisation de $or avec $match et projection:
> db.publications.aggregate([
{$match: {$or: [{evaluation: {$gt: 82}}, {auteur: "David"}]}},
{$project: {auteur: 1, evaluation: 1, _id: 0}} // Projeter uniquement l'auteur et l'évaluation
]);
{ "auteur" : "David", "evaluation" : 80 }
{ "auteur" : "David", "evaluation" : 85 }
{ "auteur" : "Lucie", "evaluation" : 94 }
{ "auteur" : "Tom", "evaluation" : 95 }
L'Étape $limit
L'opérateur $limit limite le nombre de documents transmis à l'étape suivante du pipeline.
> db.publications.aggregate([{$limit: 2}])
{ "_id" : ObjectId("67bd0886949fac4cce717ab0"), "auteur" : "David", "evaluation" : 80, "vues" : 100 }
{ "_id" : ObjectId("67bd088699fac4cce717ab1"), "auteur" : "David", "evaluation" : 85, "vues" : 521 }
L'Étape $skip
L'opérateur $skip avance le pointeur des documents d'entrée d'un nombre spécifié, sautant les premiers documents.
> db.publications.aggregate([{$skip: 2}])
{ "_id" : ObjectId("67bd0886949fac4cce717ab2"), "auteur" : "Anna", "evaluation" : 60, "vues" : 1000 }
{ "_id" : ObjectId("67bd0886949fac4cce717ab3"), "auteur" : "Lucie", "evaluation" : 55, "vues" : 5000 }
{ "_id" : ObjectId("67bd0886949fac4cce717ab4"), "auteur" : "AnneT", "evaluation" : 60, "vues" : 50 }
{ "_id" : ObjectId("67bd0886949fac4cce717ab5"), "auteur" : "Lucie", "evaluation" : 94, "vues" : 999 }
{ "_id" : ObjectId("67bd0886949fac4cce717ab6"), "auteur" : "Tom", "evaluation" : 95, "vues" : 1000 }
L'Étape $sort
L'opérateur $sort réorganise les documents en fonction d'un ou plusieurs champs. Un 1 indique un ordre croissant, et -1 un ordre décroissant.
db.etudiants.insertOne({_id: 4, nom: "Samuel Qi", age: 10, professeurs: ["M. Blanc", "Mme Rouge", "M. Vert"]});
db.etudiants.insertOne({_id: 5, nom: "Zoe Ba", age: 16, professeurs: ["Mme Noir", "M. Bleu"]});
db.etudiants.insertOne({_id: 6, nom: "William Jiu", age: 17, professeurs: ["Mme Noir", "Mme Rouge"]});
// Tri par 'age' décroissant
> db.etudiants.aggregate([{$sort: {age: -1}}])
{ "_id" : 6, "nom" : "William Jiu", "age" : 17, "professeurs" : [ "Mme Noir", "Mme Rouge" ] }
{ "_id" : 5, "nom" : "Zoe Ba", "age" : 16, "professeurs" : [ "Mme Noir", "M. Bleu" ] }
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : [ "Mme Petit", "M. Dubois" ] }
{ "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : [ "M. Dubois", "Mme Leclerc" ] }
{ "_id" : 3, "nom" : "Carole Bernard", "age" : 11, "professeurs" : [ "Mme Lefevre", "Mme Leclerc" ] }
{ "_id" : 4, "nom" : "Samuel Qi", "age" : 10, "professeurs" : [ "M. Blanc", "Mme Rouge", "M. Vert" ] }
// Combiner $sort, $skip et $limit
> db.etudiants.aggregate([{$sort: {age: 1}}, {$skip: 2}, {$limit: 3}])
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : [ "Mme Petit", "M. Dubois" ] }
{ "_id" : 5, "nom" : "Zoe Ba", "age" : 16, "professeurs" : [ "Mme Noir", "M. Bleu" ] }
{ "_id" : 6, "nom" : "William Jiu", "age" : 17, "professeurs" : [ "Mme Noir", "Mme Rouge" ] }
L'Étape $unwind
L'opérateur $unwind déconstruit un champ de type tableau d'un document en sortie pour chaque élément du tableau. Si un document ne contient pas le champ du tableau ou si le tableau est vide/null, $unwind l'exclut par défaut, sauf indication contraire.
{
$unwind: {
path: <chemin_du_champ_tableau>,
includeArrayIndex: <nom_du_champ_index>, // Optionnel: nomme le champ qui contient l'index de l'élément dans le tableau original
preserveNullAndEmptyArrays: <booléen> // Optionnel: inclut les documents avec des tableaux vides/null
}
}
Exemples d'utilisation de $unwind:
Nettoyons et préparons la collection etudiants pour l'exemple:
db.etudiants.remove({}); // Supprime tous les documents
db.etudiants.insertOne({_id: 1, nom: "Alice Martin", age: 12, professeurs: ["M. Dubois", "Mme Leclerc"]});
db.etudiants.insertOne({_id: 2, nom: "Bob Dupont", age: 15, professeurs: ["Mme Petit", "M. Dubois"]});
// Déconstruction du tableau 'professeurs'
> db.etudiants.aggregate([{$unwind: "$professeurs"}])
{ "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : "M. Dubois" }
{ "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : "Mme Leclerc" }
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : "Mme Petit" }
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : "M. Dubois" }
Option includeArrayIndex
Ajoute un champ contenant l'index de l'élément original dans le tableau:
> db.etudiants.aggregate([{$unwind: {path: "$professeurs", includeArrayIndex: "indexProf"}}])
{ "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : "M. Dubois", "indexProf" : NumberLong(0) }
{ "_id" : 1, "nom" : "Alice Martin", "age" : 12, "professeurs" : "Mme Leclerc", "indexProf" : NumberLong(1) }
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : "Mme Petit", "indexProf" : NumberLong(0) }
{ "_id" : 2, "nom" : "Bob Dupont", "age" : 15, "professeurs" : "M. Dubois", "indexProf" : NumberLong(1) }
Option preserveNullAndEmptyArrays
Par défaut, les documents qui n'ont pas le champ tableau ou dont le tableau est vide/null sont exclus. Cette option permet de les inclure:
db.etudiants.insertOne({_id: 3, nom: "Charlie", age: 10, professeurs: []}); // Tableau vide
db.etudiants.insertOne({_id: 4, nom: "Diana", age: 13}); // Champ manquant
> db.etudiants.aggregate([
{$unwind: {path: "$professeurs", includeArrayIndex: "indexProf", preserveNullAndEmptyArrays: true}},
{$project: {nom: 1, professeur: "$professeurs", index: "$indexProf", _id: 0}}
])
{ "nom" : "Alice Martin", "professeur" : "M. Dubois", "index" : NumberLong(0) }
{ "nom" : "Alice Martin", "professeur" : "Mme Leclerc", "index" : NumberLong(1) }
{ "nom" : "Bob Dupont", "professeur" : "Mme Petit", "index" : NumberLong(0) }
{ "nom" : "Bob Dupont", "professeur" : "M. Dubois", "index" : NumberLong(1) }
{ "nom" : "Charlie", "professeur" : null, "index" : null }
{ "nom" : "Diana", "professeur" : null, "index" : null }
L'Étape $group
L'opérateur $group regroupe les documents d'entrée par une clé spécifiée et applique des fonctions d'agrégation (accumulateurs) sur les documents de chaque groupe. Pour chaque groupe unique, $group génère un document en sortie.
{$group: { _id: <expression_clé_de_groupe>, <champ1>: {<accumulateur1>: <expression>}, ... }}
Par défaut, $group tente de travailller en mémoire. Si les opérations dépassent 100 Mo, elles échouent. L'option allowDiskUse: true permet d'utiliser le disque pour les opérations volumineuses.
Opérateurs d'accumulateur courants dans $group
| Opérateur | Description |
|---|---|
$sum |
Calcule la somme des valeurs numériques. |
$avg |
Calcule la moyenne des valeurs numériques. |
$first |
Retourne la première valeur pour chaque groupe (nécessite $sort préalable pour être déterministe). |
$last |
Retourne la dernière valeur pour chaque groupe (nécessite $sort préalable pour être déterministe). |
$max |
Retourne la valeur maximale de chaque groupe. |
$min |
Retourne la valeur minimale de chaque groupe. |
$push |
Crée un tableau de toutes les valeurs pour chaque groupe. |
$addToSet |
Crée un tableau d'éléments uniques pour chaque groupe. L'ordre des éléments n'est pas garanti. |
$stdDevPop |
Calcule l'écart-type d'une population des valeurs d'entrée. |
$stdDevSamp |
Calcule l'écart-type d'un échantillon des valeurs d'entrée. |
Exemples d'utilisation de $group:
Créons une collection de ventes:
db.ventes.drop();
db.ventes.insertOne({_id: 1, produit: "chaise", prixUnitaire: 10, quantiteVendue: 2, dateVente: ISODate("2024-03-01T08:00:00Z")});
db.ventes.insertOne({_id: 2, produit: "table", prixUnitaire: 20, quantiteVendue: 1, dateVente: ISODate("2024-03-01T09:00:00Z")});
db.ventes.insertOne({_id: 3, produit: "lampe", prixUnitaire: 5, quantiteVendue: 10, dateVente: ISODate("2024-03-15T09:00:00Z")});
db.ventes.insertOne({_id: 4, produit: "lampe", prixUnitaire: 5, quantiteVendue: 20, dateVente: ISODate("2024-04-04T11:21:39.736Z")});
db.ventes.insertOne({_id: 5, produit: "chaise", prixUnitaire: 10, quantiteVendue: 10, dateVente: ISODate("2024-04-04T21:23:13.331Z")});
// Regrouper par année, mois et jour de vente
> db.ventes.aggregate([{$group: {_id: {annee: {$year: "$dateVente"}, mois: {$month: "$dateVente"}, jour: {$dayOfMonth: "$dateVente"}}}}])
{ "_id" : { "annee" : 2024, "mois" : 3, "jour" : 15 } }
{ "_id" : { "annee" : 2024, "mois" : 4, "jour" : 4 } }
{ "_id" : { "annee" : 2024, "mois" : 3, "jour" : 1 } }
// Regrouper par date, calculer le total des ventes (prix * quantité)
> db.ventes.aggregate([{$group: {_id: {annee: {$year: "$dateVente"}, mois: {$month: "$dateVente"}, jour: {$dayOfMonth: "$dateVente"}}, totalRevenu: {$sum: {$multiply: ["$prixUnitaire", "$quantiteVendue"]}}}}])
{ "_id" : { "annee" : 2024, "mois" : 3, "jour" : 15 }, "totalRevenu" : 50 }
{ "_id" : { "annee" : 2024, "mois" : 4, "jour" : 4 }, "totalRevenu" : 200 }
{ "_id" : { "annee" : 2024, "mois" : 3, "jour" : 1 }, "totalRevenu" : 40 }
// Ajouter le nombre de commandes et la quantité moyenne vendue par jour
> db.ventes.aggregate([{$group: {_id: {annee: {$year: "$dateVente"}, mois: {$month: "$dateVente"}, jour: {$dayOfMonth: "$dateVente"}}, totalRevenu: {$sum: {$multiply: ["$prixUnitaire", "$quantiteVendue"]}}, nombreCommandes: {$sum: 1}, moyenneQuantiteVendue: {$avg: "$quantiteVendue"}}}])
{ "_id" : { "annee" : 2024, "mois" : 3, "jour" : 15 }, "totalRevenu" : 50, "nombreCommandes" : 1, "moyenneQuantiteVendue" : 10 }
{ "_id" : { "annee" : 2024, "mois" : 4, "jour" : 4 }, "totalRevenu" : 200, "nombreCommandes" : 2, "moyenneQuantiteVendue" : 15 }
{ "_id" : { "annee" : 2024, "mois" : 3, "jour" : 1 }, "totalRevenu" : 40, "nombreCommandes" : 2, "moyenneQuantiteVendue" : 1.5 }
Regrouper sans clé (avec _id: null)
Pour effectuer une agrégation sur l'ensemble de la collection sans regrouper par un champ spécifique, utilisez _id: null.
> db.ventes.aggregate([{$group: {_id: null, totalGlobalRevenu: {$sum: {$multiply: ["$prixUnitaire", "$quantiteVendue"]}}, totalArticlesVendues: {$sum: "$quantiteVendue"}, nombreTotalCommandes: {$sum: 1}, moyenneQuantiteGenerale: {$avg: "$quantiteVendue"}}}])
{ "_id" : null, "totalGlobalRevenu" : 290, "totalArticlesVendues" : 43, "nombreTotalCommandes" : 5, "moyenneQuantiteGenerale" : 8.6 }
Regrouper par un champ unique
// Compter le nombre d'occurrences pour chaque type de produit
> db.ventes.aggregate([{$group: {_id: "$produit", decompteProduits: {$sum: 1}}}])
{ "_id" : "lampe", "decompteProduits" : 2 }
{ "_id" : "table", "decompteProduits" : 1 }
{ "_id" : "chaise", "decompteProduits" : 2 }
Utilisation de $push pour créer des tableaux
Créons une collection d'ouvrages:
db.ouvrages.drop();
db.ouvrages.insertOne({_id: 8751, titre: "Le Banquet", auteur: "Dante", exemplairesDisponibles: 2});
db.ouvrages.insertOne({_id: 8752, titre: "La Divine Comédie", auteur: "Dante", exemplairesDisponibles: 1});
db.ouvrages.insertOne({_id: 8645, titre: "Églogues", auteur: "Dante", exemplairesDisponibles: 2});
db.ouvrages.insertOne({_id: 7000, titre: "L'Odyssée", auteur: "Homère", exemplairesDisponibles: 10});
db.ouvrages.insertOne({_id: 7020, titre: "L'Iliade", auteur: "Homère", exemplairesDisponibles: 10});
// Regrouper par auteur et lister tous les titres d'ouvrages
> db.ouvrages.aggregate([{$group: {_id: "$auteur", titresOuvrages: {$push: "$titre"}}}])
{ "_id" : "Homère", "titresOuvrages" : [ "L'Odyssée", "L'Iliade" ] }
{ "_id" : "Dante", "titresOuvrages" : [ "Le Banquet", "La Divine Comédie", "Églogues" ] }
Exporter les détails de chaque document groupé avec $$ROOT
Pour obtenir tous les détails des documents originaux dans le tableau regroupé, utilisez $$ROOT avec $push.
> db.ouvrages.aggregate([{$group: {_id: "$auteur", detailsOuvrages: {$push: "$$ROOT"}}}]).pretty()
{
"_id" : "Homère",
"detailsOuvrages" : [
{
"_id" : 7000,
"titre" : "L'Odyssée",
"auteur" : "Homère",
"exemplairesDisponibles" : 10
},
{
"_id" : 7020,
"titre" : "L'Iliade",
"auteur" : "Homère",
"exemplairesDisponibles" : 10
}
]
}
{
"_id" : "Dante",
"detailsOuvrages" : [
{
"_id" : 8751,
"titre" : "Le Banquet",
"auteur" : "Dante",
"exemplairesDisponibles" : 2
},
{
"_id" : 8752,
"titre" : "La Divine Comédie",
"auteur" : "Dante",
"exemplairesDisponibles" : 1
},
{
"_id" : 8645,
"titre" : "Églogues",
"auteur" : "Dante",
"exemplairesDisponibles" : 2
}
]
}
L'Étape $lookup
L'opérateur $lookup réalise une jointure de type "left outer join" avec une autre collection dans la même base de données. Il renvoie les documents des "from" collection dont le foreignField correspond au localField des documents d'entrée.
{
$lookup: {
from: <collection_a_joindre>,
localField: <champ_dans_les_documents_d_entree>,
foreignField: <champ_dans_la_collection_a_joindre>,
as: <nom_du_champ_de_sortie_tableau>
}
}
Préparation des Données de Jointure:
// Collection 'commandesClients'
db.commandesClients.drop();
db.commandesClients.insertOne({_id: 1, codeArticle: "ABC", prixUnitaire: 12, quantiteCommandee: 2});
db.commandesClients.insertOne({_id: 2, codeArticle: "JKL", prixUnitaire: 20, quantiteCommandee: 1});
db.commandesClients.insertOne({_id: 3, codeArticle: "XYZ"}); // Article sans correspondance dans le stock
// Collection 'catalogueProduits'
db.catalogueProduits.drop();
db.catalogueProduits.insertOne({_id :1, refProduit: "ABC", descriptionProduit: "Produit A", quantiteEnStock: 120});
db.catalogueProduits.insertOne({_id :2, refProduit: "DEF", descriptionProduit: "Produit B", quantiteEnStock: 80});
db.catalogueProduits.insertOne({_id :3, refProduit: "IJK", descriptionProduit: "Produit C", quantiteEnStock: 60});
db.catalogueProduits.insertOne({_id :4, refProduit: "JKL", descriptionProduit: "Produit D", quantiteEnStock: 70});
db.catalogueProduits.insertOne({_id :5, refProduit: null, descriptionProduit: "Incomplet"}); // Exemple d'un produit incomplet
db.catalogueProduits.insertOne({_id :6, refProduit: "XYZ", descriptionProduit: "Produit E", quantiteEnStock: 50});
Effectuer une Jointure simple:
> db.commandesClients.aggregate([{$lookup: {from: "catalogueProduits", localField: "codeArticle", foreignField: "refProduit", as: "detailsProduit"}}])
{ "_id" : 1, "codeArticle" : "ABC", "prixUnitaire" : 12, "quantiteCommandee" : 2, "detailsProduit" : [ { "_id" : 1, "refProduit" : "ABC", "descriptionProduit" : "Produit A", "quantiteEnStock" : 120 } ] }
{ "_id" : 2, "codeArticle" : "JKL", "prixUnitaire" : 20, "quantiteCommandee" : 1, "detailsProduit" : [ { "_id" : 4, "refProduit" : "JKL", "descriptionProduit" : "Produit D", "quantiteEnStock" : 70 } ] }
{ "_id" : 3, "codeArticle" : "XYZ", "detailsProduit" : [ { "_id" : 6, "refProduit" : "XYZ", "descriptionProduit" : "Produit E", "quantiteEnStock" : 50 } ] }
Scénario de Jointure Complexe ($unwind, $lookup, $match):
Considérons des commandes de moniteurs avec des spéciifcations et un inventaire détaillé.
db.commandesMoniteurs.drop();
db.commandesMoniteurs.insertOne({_id: 1, refModele: "MON1003", prix: 350, quantite: 2, specifications: ["27 pouces", "Écran Retina", "1920x1080"], typeProduit: "Moniteur"});
db.stockMoniteurs.drop();
db.stockMoniteurs.insertOne({_id :1, codeSKU: "MON1003", type: "Moniteur", enStock: 120, tailleEcran: "27 pouces", resolutionEcran: "1920x1080"});
db.stockMoniteurs.insertOne({_id :2, codeSKU: "MON1012", type: "Moniteur", enStock: 85, tailleEcran: "23 pouces", resolutionEcran: "1280x800"});
db.stockMoniteurs.insertOne({_id :3, codeSKU: "MON1031", type: "Moniteur", enStock: 60, tailleEcran: "23 pouces", resolutionEcran: "LED"});
// Pipeline: Déconstruire les spécifications, joindre avec le stock, puis filtrer les résultats non vides.
> db.commandesMoniteurs.aggregate([
{$unwind: "$specifications"},
{$lookup: {from: "stockMoniteurs", localField: "refModele", foreignField: "codeSKU", as: "detailsStock"}},
{$match: {detailsStock: {$ne: []}}} // Filtrer les commandes qui ont des correspondances de stock
]);
{ "_id" : 1, "refModele" : "MON1003", "prix" : 350, "quantite" : 2, "specifications" : "27 pouces", "typeProduit" : "Moniteur", "detailsStock" : [ { "_id" : 1, "codeSKU" : "MON1003", "type" : "Moniteur", "enStock" : 120, "tailleEcran" : "27 pouces", "resolutionEcran" : "1920x1080" } ] }
{ "_id" : 1, "refModele" : "MON1003", "prix" : 350, "quantite" : 2, "specifications" : "Écran Retina", "typeProduit" : "Moniteur", "detailsStock" : [ { "_id" : 1, "codeSKU" : "MON1003", "type" : "Moniteur", "enStock" : 120, "tailleEcran" : "27 pouces", "resolutionEcran" : "1920x1080" } ] }
{ "_id" : 1, "refModele" : "MON1003", "prix" : 350, "quantite" : 2, "specifications" : "1920x1080", "typeProduit" : "Moniteur", "detailsStock" : [ { "_id" : 1, "codeSKU" : "MON1003", "type" : "Moniteur", "enStock" : 120, "tailleEcran" : "27 pouces", "resolutionEcran" : "1920x1080" } ] }
L'Étape $sample
L'opérateur $sample sélectionne un échantillon aléatoire de documents d'entrée.
{ $sample: { size: <nombre_entier_positif_N> } }
Exemples d'utilisation de $sample:
Nettoyons et préparons la collection etudiants:
db.etudiants.drop();
db.etudiants.insertOne({_id: 1, nom: "Alice"});
db.etudiants.insertOne({_id: 2, nom: "Bob"});
db.etudiants.insertOne({_id: 3, nom: "Carole"});
db.etudiants.insertOne({_id: 4, nom: "David"});
db.etudiants.insertOne({_id: 5, nom: "Ève"});
db.etudiants.insertOne({_id: 6, nom: "Frank"});
// Sélectionner 2 documents aléatoires
> db.etudiants.aggregate([{$sample: {size: 2}}])
{ "_id" : 3, "nom" : "Carole" }
{ "_id" : 6, "nom" : "Frank" }
> db.etudiants.aggregate([{$sample: {size: 2}}])
{ "_id" : 1, "nom" : "Alice" }
{ "_id" : 4, "nom" : "David" }