Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
💥 Les 22 & 23 novembre : Accès 100% GRATUIT
à la Bibliothèque Numérique ENI. Je m'inscris !
  1. Livres et vidéos
  2. MongoDB
  3. Le framework d’agrégation
Extrait - MongoDB Comprendre et optimiser l'exploitation de vos données (avec exercices et corrigés) (2e édition)
Extraits du livre
MongoDB Comprendre et optimiser l'exploitation de vos données (avec exercices et corrigés) (2e édition) Revenir à la page d'achat du livre

Le framework d’agrégation

Introduction

MongoDB met à disposition de ses utilisateurs un puissant outil d’analyse et de traitement de l’information : le pipeline d’agrégation (également appelé framework d’agrégation).

Fonctionnement

Pour évoquer le fonctionnement du pipeline d’agrégation, MongoDB utilise la métaphore du tapis roulant dans une usine. Les documents sont sur ce tapis roulant et tout au long de l’usinage, un certain nombre de traitements leur est appliqué : ces traitements sont appelés des étapes (stages).

Ainsi, un pipeline se compose généralement d’une ou plusieurs étapes utilisant des opérateurs nommés opérateurs d’agrégation et dont certains portent des noms que nous avons déjà eu l’occasion de voir dans les chapitres précédents. La méthode qui permet d’exécuter un pipeline sur une collection se nomme aggregate et sa syntaxe est :

db.collection.aggregate(< pipeline >, < options >) 

Le paramètre pipeline est un tableau d’étapes tandis qu’options est un document. Parmi les options notables de cette méthode, nous retiendrons :

  • collation, qui permet d’affecter une collation à l’opération d’agrégation.

  • bypassDocumentValidation qui ne marche qu’avec l’opérateur $out et permis de passer outre la validation des documents.

  • allowDiskUse permet de faire déborder les opérations d’écriture sur le disque.

Nous verrons plus bas que même si l’utilité...

Les étapes du pipeline d’agrégation

Pour travailler sur l’agrégation, nous allons supprimer notre ancienne collection personnes et en créer une toute nouvelle :

db.personnes.drop() 
 
db.personnes.insertMany( 
[ 
 { 
     "nom": "Dupont", 
     "prenom": "Catherine", 
     "interets": ["cuisine"], 
     "age": 66 
 }, { 
     "nom": "Duport", 
     "prenom": "Eric", 
     "interets": ["cuisine", "pétanque"], 
     "age": 57 
 }, { 
     "nom": "Duport", 
     "prenom": "Arlette",  
     "interets": ["jardinage"], 
     "age": 80 
 }, { 
     "nom": "Lejeune", 
     "prenom": "Jean", 
     "interets": ["jardinage"], 
     "age": 75 
 }, { 
     "nom": "Lejeune", 
     "prenom": "Mariette", 
     "interets": ["jardinage", "bridge"], 
     "age": 66 
 }] 
) 

1. Le filtrage avec $match

L’étape $match est cruciale pour obtenir des pipelines performants avec des temps d’exécution courts. Elle doit venir le plus en amont possible dans le pipeline car elle agit comme un filtre et réduit ainsi le nombre de documents à traiter plus bas dans la chaîne. Elle doit idéalement figurer en premier.

La syntaxe de cette étape de correspondance est simple,  on lui passe un document contenant l’ensemble des conditions de notre requête :

{ $match: { < requête > } } 

Commençons par écrire un pipeline ne comprenant qu’une seule étape qui utilise l’opérateur $match : son but est de filtrer en amont...

Les étapes cursor-like

Ces quatre étapes du pipeline d’agrégation portent le nom de méthodes que l’on retrouve lorsque l’on travaille sur des curseurs, voilà pourquoi on y fait référence en anglais sous le terme de cursor-like (littéralement « à la manière des curseurs »).

1. Limitation des résultats avec $limit

Voici une requête limitant les résultats retournés dans un curseur à trois documents. Nous ne précisons pas de critère de recherche et nous ne retenons que les champs nom et prenom :

db.personnes.find({}, {"_id": 0, "nom": 1, "prenom": 1}).limit(3) 

Nous obtenons les documents suivants :

{ "nom" : "Durand", "prenom" : "René" } 
{ "nom" : "Durand", "prenom" : "Gisèle" } 
{ "nom" : "Dupont", "prenom" : "Gaston" } 

N’ayant pas été explicitement triés, ils apparaissent dans l’ordre naturel, c’est-à-dire celui dans lequel ils ont été insérés dans la collection.

Voilà la même requête une fois traduite sous une forme exploitable par le framework d’agrégation :

pipeline = [{ 
   $project: { 
       "_id": 0, 
       "nom": 1, 
       "prenom": 1 
   } 
}, 
{ 
   $limit:...

Les opérateurs du pipeline d’agrégation

1. Évaluer une expression avec $cond

Cet opérateur agit comme le ferait un branchement conditionnel de type if dans n’importe quel langage de programmation : il évalue une expression booléenne puis effectue des actions si elle est vraie et si elle est fausse. Sa forme simplifiée est :

{ $cond: [ < expression >, < si vrai >, < si faux > ] } 

Cette syntaxe condensée est bien connue des programmeurs C++, PHP ou encore Java sous le nom d’opérateur ternaire.

Reprenons la collection achats et supposons qu’un bon de réduction soit offert à tous les clients selon les conditions suivantes : toute personne ayant effectué au moins 300 euros d’achats aura un bon d’une valeur faciale de 10 euros et dans le cas contraire, cette valeur sera seulement de 5 euros. Voici le pipeline correspondant :

db.achats.aggregate([{ 
   $addFields: { 
       "total_achats": { $sum: "$achats" } 
   } 
}, 
{ 
   $project: { 
       "_id": 0, 
       "nom": 1, 
       "prenom": 1, 
       "bon_red": {$cond: [{$gte: ["$total_achats", 300]}, 10, 5]} 
   } 
} 
]) 

2. Parcourir et transformer les éléments d’un tableau avec $map

L’opérateur $map permet de parcourir les différents éléments d’un tableau en leur appliquant une expression. Il renvoie ensuite un tableau avec les changements intervenus. Voici sa signature :

{ 
   $map: { 
       "input": < expression générant un tableau >, 
       "as": < nom donné à la variable représentant chaque élément 
du tableau >, 
       "in": < expression appliquée à chaque élément > 
   } 
} 

Seul le champ as est optionnel. S’il n’est pas précisé, il faudra...

La recherche par facettes

Vous utilisez ce type de recherche dès lors que vous naviguez sur un site web marchand et que vous recherchez des produits dans un catalogue en appliquant des filtres sur les propriétés, nommées facettes (de l’anglais facet), qu’ils contiennent. Ces propriétés peuvent être une taille, une couleur, un prix, une catégorie, etc. Voici par exemple la recherche par facettes du site marchand Discogs, place de marché spécialisée dans la vente en ligne de produits musicaux (disques, CD, cassettes et bien d’autres formats encore).

images/05EP01.png

Les facettes sont listées sur la gauche de l’image et sont ici prédéfinies, elles nous permettent de chercher parmi les 30194 références que l’utilisateur sélectionné ici propose à la vente. Pour chacune d’entre elles, nous avons quelques-unes des valeurs prises avec en face un décompte.

Voici un exemple de recherche combinant plusieurs filtres appliqués à certaines des facettes mises à notre disposition par le site Discogs, elles restreignent considérablement la taille du jeu de résultats puisque nous avons désormais 3 produits dans le domaine qui nous intéresse, à savoir les disques vinyle valant plus de 40 dollars de groupes jouant du rock psychédélique.

images/05EP02.png

Lors d’un clic sur une valeur de certaines facettes, d’autres facettes sont mises à jour afin de permettre à l’utilisateur de filtrer à nouveau pour creuser davantage dans le catalogue (drill down en anglais). Ainsi, la sélection du genre « Rock » mettra à jour les valeurs de la facette « Style » qui ne gardera que les styles présents dans le genre choisi :

images/05EP03.png

De la même manière, un clic sur le style « Heavy Metal » aura des répercussions sur les valeurs de la facette « Style » elle-même, car elle ne contiendra plus que les sous-styles du style sélectionné :

images/05EP04.png

Basons-nous à nouveau sur la collection instruments et simulons ces catégories avec le décompte associé. Une telle simulation peut se réaliser à l’aide d’un pipeline assez simple :

db.instruments.aggregate([{ ...