Fondamentaux, requêtes avancées et agrégation
Se connecter pour s'inscrireVotre progression
Syllabus
-
-
Objectifs
-
Validations de compétences
-
Public cible
Aucun public cible spécifié pour ce module.
-
Besoins techniques
-
Prérequis
- Aucun prérequis
-
Niveau
-
Historique du module
Aucune action enregistrée
Fondamentaux, requêtes avancées et agrégation
-
Les Fondamentaux de MongoDB
-
Introduction à MongoDB et aux bases NoSQL
-
Qu'est-ce que MongoDB ?
MongoDB est une base de données NoSQL orientée document, conçue pour la flexibilité, la scalabilité horizontale et les performances élevées.
graph TD %% Niveaux principaux A[MongoDB] --> B[Base de données] B --> C[Collection] C --> D[Document] %% Documents dans la collection C --> Doc1[Document 1] C --> Doc2[Document 2] C --> Doc3[Document ...] %% Structure d'un document Doc1 --> Field1[_id: ObjectId] Doc1 --> Field2[nom] Doc1 --> Field3[email] Doc1 --> Field4[adresse] %% Styles de base style A fill:#4db8ff,stroke:#4db8ff,color:white style B fill:#4db8ff,stroke:#4db8ff,color:white style C fill:#ff6b6b,stroke:#ff6b6b,color:white style Doc1 fill:#6c5ce7,stroke:#6c5ce7,color:white,stroke-dasharray: 5 5 style Doc2 fill:#6c5ce7,stroke:#6c5ce7,color:white,stroke-dasharray: 5 5 style Doc3 fill:#6c5ce7,stroke:#6c5ce7,color:white,stroke-dasharray: 5 5Les collections MongoDBUne collection dans MongoDB est un groupe de documents, équivalent à une table dans les bases de données relationnelles. Cependant, contrairement aux tables, les collections n'imposent pas de schéma strict.
Caractéristiques des collections :
- Pas de schéma imposé : les documents d'une même collection peuvent avoir des structures différentes
- Haute performance : optimisées pour les opérations de lecture/écriture
- Indexables : permet des recherches rapides
Bon à savoir : Par convention, les noms de collections sont au pluriel (ex:users,orders).Figure 1 : Architecture de MongoDB montrant les relations entre les différents composants.
-
Pourquoi choisir MongoDB ?
-
Performance
Moteur de stockage haute performance avec :
Exemple de performance
Un site e-commerce utilisant MongoDB peut gérer jusqu'à 100 000 lectures par seconde avec des temps de réponse inférieurs à 10ms, ce qui permet une expérience utilisateur fluide même pendant les périodes de pointe comme le Black Friday.
- Support natif de la réplication
- Sharding intégré
- Indexation avancée
- Cache de requêtes
-
Scalabilité
Architecture distribuée conçue pour :
Exemple de scalabilité
Une application de médias sociaux peut commencer avec un seul serveur MongoDB. À mesure que le nombre d'utilisateurs passe de 1 000 à 10 millions, l'application peut évoluer en ajoutant des shards (partitions de données) sur plusieurs serveurs, sans temps d'arrêt.
- Échelle horizontale facile
- Répartition des charges
- Haute disponibilité
-
Modèle de données flexible
Avantages du schéma dynamique :
Exemple de flexibilité
Dans une application de blog, vous pourriez initialement stocker uniquement le titre et le contenu d'un article. Plus tard, vous pourriez ajouter des champs comme des tags, une image de couverture ou des commentaires directement dans le même document, sans avoir à modifier la structure de la base de données.
``` // Premier enregistrement { titre: "Mon premier article", contenu: "Contenu de l'article...", date: ISODate("2023-01-01") } // Plus tard, ajout de nouveaux champs sans modification de schéma { titre: "Nouvel article", contenu: "Contenu mis à jour...", date: ISODate("2023-04-15"), tags: ["technologie", "programmation"], image: "url-de-l-image.jpg", auteur: { nom: "Alice", role: "rédacteur" }, commentaires: [ { auteur: "Bob", texte: "Super article !", date: ISODate("2023-04-16") } ] } ```- Évolution sans temps d'arrêt
- Documents hétérogènes
- Modélisation naturelle des données
-
Écosystème riche
Une large gamme d'outils :
Exemples d'outils populaires
- MongoDB Compass : Interface graphique pour explorer et manipuler les données
- MongoDB Atlas : Service cloud géré avec hébergement sur AWS, Azure ou GCP
- MongoDB Charts : Outil de visualisation de données intégré
- MongoDB BI Connector : Permet d'utiliser des outils BI comme Tableau avec MongoDB
- Drivers officiels : Pour Node.js, Python, Java, C#, etc.
Astuce : Ces outils rendent MongoDB accessible aussi bien aux développeurs qu'aux analystes de données, facilitant ainsi la collaboration entre les équipes.
- Drivers pour tous les langages majeurs
- Outils de gestion (Compass, Atlas)
- Intégrations cloud
- Communauté active
-
-
Concepts clés du NoSQL
-
Modèles de données NoSQL
-
Document
Stockage de documents JSON/BSON
Ex: MongoDB, CouchDB
-
Clé-Valeur
Stockage simple clé-valeur
Ex: Redis, DynamoDB
-
Colonnes larges
Stockage par colonnes
Ex: Cassandra, HBase
-
Graphes
Relations complexes entre données
Ex: Neo4j, ArangoDB
-
-
ACID vs BASE
ACID (SQL) BASE (NoSQL) Description Atomicité Disponibilité de base Disponibilité même en cas de pannes Cohérence État souple (Soft state) Les données peuvent évoluer sans mise à jour Isolation Cohérence à terme Les données finissent par être cohérentes Durabilité Scalabilité horizontale Capacité à gérer de grandes quantités de données
-
-
Quand utiliser MongoDB ?
Idéal pour- Applications web et mobiles API RESTful
- Gestion de contenu (CMS) Contenu dynamique
- Catalogues produits E-commerce
- Données IoT et séries temporelles Big Data
- Profils utilisateurs Données utilisateurs
- Données géospatiales Géolocalisation
Moins adapté pour- Applications financières Transactions complexes
- Systèmes relationnels Jointures complexes
- Applications critiques Cohérence forte requise
- Requêtes complexes SQL avancé
- Données hautement relationnelles Modèle relationnel
Conseil :MongoDB excelle dans les cas d'utilisation nécessitant flexibilité et évolutivité. Pour les systèmes transactionnels complexes, envisagez une base de données relationnelle traditionnelle.
-
Opérations CRUD avec MongoDB
Les opérations CRUD (Create, Read, Update, Delete) sont fondamentales pour interagir avec une base de données MongoDB. Voici comment les mettre en œuvre :
-
Create - Insertion de documents
Pour insérer des documents dans une collection :
// Insérer un seul document db.collection('users').insertOne({ name: 'Jean Dupont', email: 'jean@exemple.com', age: 30, status: 'actif' }); // Insérer plusieurs documents db.collection('posts').insertMany([ { title: 'Premier article', content: 'Contenu...', likes: 0 }, { title: 'Deuxième article', content: 'Contenu...', tags: ['tech', 'mongodb'] } ]);MongoDB génère automatiquement un_idunique pour chaque document si aucun n'est spécifié. -
Read - Lecture de documents
Pour rechercher des documents :
// Trouver tous les documents db.collection('users').find({}); // Avec des critères de recherche db.collection('users').find({ status: 'actif' }); // Avec projection (sélection des champs) db.collection('users').find( { age: { $gt: 25 } }, { projection: { name: 1, email: 1, _id: 0 } } ); // Trier et limiter les résultats db.collection('posts') .find({}) .sort({ created_at: -1 }) .limit(10); -
Update - Mise à jour de documents
Pour mettre à jour des documents :
// Mettre à jour un seul document db.collection('users').updateOne( { email: 'jean@exemple.com' }, { $set: { status: 'inactif' } } ); // Mettre à jour plusieurs documents db.collection('posts').updateMany( { views: { $lt: 100 } }, { $inc: { views: 1 } } ); // Opérateurs de mise à jour courants : // $set, $unset, $inc, $push, $pull, $addToSet -
Delete - Suppression de documents
Pour supprimer des documents :
// Supprimer un seul document db.collection('users').deleteOne({ email: 'jean@exemple.com' }); // Supprimer plusieurs documents db.collection('posts').deleteMany({ status: 'archived' }); // Attention : Sans filtre, cela supprime tous les documents de la collection ! // db.collection('users').deleteMany({});Attention : Les opérations de suppression sont irréversibles. Toujours vérifier ses critères de sélection avant de supprimer. -
Bonnes pratiques CRUD
- Utilisez des index pour optimiser les requêtes de lecture
- Privilégiez les opérations en masse (
insertMany,updateMany) pour améliorer les performances - Utilisez les transactions pour les opérations atomiques sur plusieurs documents
- Limitez les champs retournés avec la projection pour réduire la charge réseau
- Pensez à la cohérence des données dans un environnement distribué
-
-
-
Structure des documents MongoDB
-
Introduction au format BSON
MongoDB utilise le format BSON (Binary JSON) pour stocker les documents. C'est une représentation binaire de JSON avec des types de données supplémentaires.
Avantages du BSON :
- Plus rapide à analyser que JSON
- Supporte plus de types de données
- Efficace pour le stockage et la récupération
-
Anatomie d'un document
Un document MongoDB typique :
{ _id: ObjectId("507f1f77bcf86cd799439011"), nom: "Jean Dupont", age: 30, email: "jean.dupont@example.com", adresse: { rue: "123 Rue Exemple", ville: "Paris", codePostal: "75000" }, competences: ["MongoDB", "JavaScript", "Node.js"], dateInscription: ISODate("2023-01-15T10:30:00Z") } -
Types de données
-
Types scalaires
- String : Chaînes de caractères UTF-8
- Number : Entiers, décimaux, doubles
- Boolean : true/false
- Date : Horodatage
- Null : Valeur nulle
-
Types composés
- Document : Objet imbriqué
- Array : Tableau de valeurs
-
Types spéciaux
- ObjectId : Identifiant unique
- Binary Data : Données binaires
- Regular Expression : Expressions régulières
- Timestamp : Horodatage interne
-
Types scalaires
-
Bonnes pratiques de modélisation
-
Conception orientée documents
Regrouper les données fréquemment accédées ensemble
-
Éviter les jointures excessives
Privilégier l'imbrication quand c'est pertinent
-
Utilisation des références
Pour les relations un-à-plusieurs ou plusieurs-à-plusieurs
-
Considérations sur la taille des documents
Éviter les documents trop volumineux (>16Mo)
-
-
-
Guide Complet des Opérations MongoDB
Ce guide couvre en détail les opérations CRUD (Create, Read, Update, Delete) dans MongoDB.Recherche de Documents (find)
-
Syntaxe de Base
// Syntaxe de base de la méthode find() // db.collection.find(query, projection) // Exemple 1: Trouver tous les documents const allDocuments = db.users.find({}); // Exemple 2: Trouver avec des critères de recherche const activeUsers = db.users.find({ status: 'active', lastLogin: { $gt: new Date('2023-01-01') } }); // Exemple 3: Utilisation de la projection const userNames = db.users.find( { role: 'user' }, { name: 1, email: 1, _id: 0 // Exclure le champ _id } ); // Exemple 4: Trier et limiter les résultats const recentOrders = db.orders .find({ status: 'completed' }) .sort({ orderDate: -1 }) .limit(10); // Exemple 5: Compter les documents const activeUserCount = db.users.countDocuments({ status: 'active' }); // Exemple 6: Utilisation d'opérateurs de requête const highValueOrders = db.orders.find({ total: { $gt: 1000 }, // Montant supérieur à 1000 status: { $in: ['pending', 'processing'] }, // Statut dans la liste orderDate: { $gte: new Date('2023-01-01'), $lt: new Date('2024-01-01') } }); // Exemple 7: Recherche avec expression régulière const nameStartsWithJ = db.users.find({ name: { $regex: /^J/i } // Noms commençant par J (insensible à la casse) }); // Exemple 8: Requête sur des tableaux const usersWithSpecificTags = db.users.find({ tags: { $all: ['premium', 'verified'] } // Doit avoir tous les tags spécifiés }); // Exemple 9: Requête avec opérateurs logiques const complexQuery = db.products.find({ $or: [ { category: 'electronics', price: { $lt: 500 } }, { category: 'books', rating: { $gte: 4 } } ], inStock: true }); // Exemple 10: Utilisation de $elemMatch pour les tableaux d'objets const usersWithSpecificAddress = db.users.find({ addresses: { $elemMatch: { type: 'billing', city: 'Paris' } } }); -
Exemple Simple
// Trouver tous les utilisateurs actifs db.users.find({ status: 'active', lastLogin: { $gt: new Date('2023-01-01') } }).sort({ createdAt: -1 }); -
Avec Projection
// Sélectionner uniquement certains champs db.users.find( { role: 'customer' }, { name: 1, email: 1, _id: 0 // Exclure le champ _id } ).limit(10); -
Opérateurs de Requête Courants
Opérateur Description Exemple $eqÉgal à { age: { $eq: 25 } }$gt, $gtePlus grand (ou égal) que { age: { $gt: 21 } }$in, $ninDans / pas dans un tableau { role: { $in: ["admin", "editor"] } }$and, $orOpérateurs logiques { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } -
Bonnes Pratiques
- Utilisez des index pour améliorer les performances
- Limitez les champs retournés avec la projection
- Évitez les opérations qui scannent toute la collection (COLLSCAN)
- Utilisez
.explain()pour analyser les performances des requêtes
-
Les Commandes d'Insertion
-
Méthodes Disponibles
-
insertOne()
Insère un seul document// Insérer un seul document const result = await db.users.insertOne({ name: "Jean Dupont", email: "jean@example.com", age: 30, roles: ["user", "admin"], createdAt: new Date() }); console.log(`Document inséré avec l'ID: ${result.insertedId}`); // Résultat attendu : // Document inséré avec l'ID: 5f8d... (ObjectId) -
insertMany()
Insère plusieurs documents// Insérer plusieurs documents const result = await db.products.insertMany([ { name: "Ordinateur portable", category: "Électronique", price: 999.99, inStock: true, tags: ["nouveau", "promo"] }, { name: "Souris sans fil", category: "Périphériques", price: 49.99, inStock: true }, { name: "Clavier mécanique", category: "Périphériques", price: 89.99, inStock: false } ], { writeConcern: { w: "majority" }, ordered: true // Annule l'insertion en cas d'erreur }); console.log(`${result.insertedCount} documents insérés`); console.log("IDs des documents insérés:", result.insertedIds); // Résultat attendu : // 3 documents insérés // IDs des documents insérés: { // '0': ObjectId("..."), // '1': ObjectId("..."), // '2': ObjectId("...") // }
-
-
Options d'Insertion
Option Type Description Valeur par défaut writeConcernDocument Niveau de garantie d'écriture { w: 1 }orderedBoolean Arrêter à la première erreur si false truebypassDocumentValidationBoolean Contourner la validation du schéma false -
Bonnes Pratiques
- Utilisez
insertMany()pour insérer plusieurs documents en une seule opération - Désactivez
orderedpour ignorer les erreurs et continuer l'insertion - Validez les données avant l'insertion
- Utilisez des transactions pour des opérations atomiques sur plusieurs collections
- Utilisez
-
Génération des _id
Si non spécifié, MongoDB génère un
_idunique de typeObjectId:{ _id: ObjectId("507f1f77bcf86cd799439011"), // Format 12-byte // 4 bytes: timestamp // 5 bytes: random value // 3 bytes: counter ... }Pour utiliser un identifiant personnalisé :
// Insérer un seul document const result = await db.users.insertOne({ name: "Jean Dupont", email: "jean@example.com", age: 30, roles: ["user", "admin"], createdAt: new Date() }); console.log(`Document inséré avec l'ID: ${result.insertedId}`); // Résultat attendu : // Document inséré avec l'ID: 5f8d... (ObjectId)
-
-
Les Commandes de Mise à Jour
-
Méthodes Principales
-
updateOne()
Met à jour un seul document// Mettre à jour un document spécifique const result = await db.products.updateOne( { _id: ObjectId("507f1f77bcf86cd799439011") }, // Filtre { $set: { // Opérateur de mise à jour price: 1099.99, lastModified: new Date() }, $inc: { viewCount: 1 }, $addToSet: { tags: "nouveau" } }, { upsert: false, // Ne pas créer le document s'il n'existe pas writeConcern: { w: "majority" } } ); console.log(`${result.matchedCount} document(s) correspondant(s)`); console.log(`${result.modifiedCount} document(s) modifié(s)`); console.log(`ID du document inséré: ${result.upsertedId || 'Aucun'}`); // Résultat attendu : // 1 document(s) correspondant(s) // 1 document(s) modifié(s) // ID du document inséré: Aucun -
updateMany()
Met à jour plusieurs documents// Mettre à jour plusieurs documents correspondant aux critères const result = await db.products.updateMany( { category: "Électronique", price: { $lt: 1000 } // Produits électroniques à moins de 1000€ }, { $set: { onSale: true, discount: 0.1, // 10% de réduction lastPromoUpdate: new Date() }, $inc: { price: 50 } // Augmenter le prix de 50€ }, { writeConcern: { w: "majority" }, upsert: false } ); console.log(`${result.matchedCount} document(s) correspondant(s)`); console.log(`${result.modifiedCount} document(s) modifié(s)`); console.log(`ID du document inséré: ${result.upsertedId || 'Aucun'}`); // Résultat attendu (exemple) : // 15 document(s) correspondant(s) // 15 document(s) modifié(s) // ID du document inséré: Aucun
-
-
Opérateurs de Mise à Jour
-
Opérateurs de Champs
$set: Définit la valeur d'un champ$unset: Supprime un champ$rename: Renomme un champ$inc: Incrémente une valeur numérique$mul: Multiplie une valeur numérique$min/$max: Met à jour si la valeur est plus petite/plus grande
-
Opérateurs de Tableaux
$push: Ajoute un élément à un tableau$addToSet: Ajoute si non présent$pop: Supprime le premier/dernier élément$pull: Supprime les éléments correspondants$pullAll: Supprime plusieurs valeurs$each: Utilisé avec $push et $addToSet pour ajouter plusieurs éléments
-
Exemple Complet avec Opérateurs
// Mettre à jour un profil utilisateur complexe await db.users.updateOne( { _id: userId }, { $set: { "profile.lastUpdated": new Date(), "contact.email": "new.email@example.com", "settings.theme": "dark" }, $inc: { "stats.profileViews": 1 }, $addToSet: { tags: { $each: ["premium", "verified"] } }, $pull: { notifications: { type: "promo", status: "unread" } } }, { upsert: true, // Crée le document s'il n'existe pas writeConcern: { w: "majority" } } );
-
-
Pièges Courants
- Les mises à jour atomiques ne sont pas garanties sans
_iddans le filtre - Les opérations de mise à jour sont atomiques au niveau du document
- Les index peuvent affecter les performances des mises à jour
- Les validations de schéma s'appliquent aux mises à jour
- Les mises à jour atomiques ne sont pas garanties sans
-
Mise à Jour avec Agrégation (MongoDB 4.2+)
Permet des mises à jour complexes en utilisant le pipeline d'agrégation :
// Mettre à jour en fonction des valeurs existantes await db.orders.updateMany ( { status: "processing" }, [ { $set: { lastUpdated: "$$NOW", total: { $add: [ "$subtotal", { $multiply: ["$subtotal", "$taxRate"] }, "$shipping" ] } } }, { $unset: ["tempField1", "tempField2"] } ] );
-
-
Suppression de Documents
Attention : Les opérations de suppression sont irréversibles. Utilisez ces commandes avec précaution.-
deleteOne()
Suppression d'un document// Supprimer un document spécifique const result = await db.users.deleteOne( { _id: ObjectId("507f1f77bcf86cd799439011") }, // Filtre { writeConcern: { w: "majority" }, collation: { locale: "fr", strength: 1 } // Optionnel : pour la sensibilité à la casse } ); console.log(`${result.deletedCount} document(s) supprimé(s)`); // Exemple avec journalisation avant suppression try { const user = await db.users.findOne({ _id: ObjectId("507f1f77bcf86cd799439011") }); if (user) { await db.deletedUsers.insertOne({ ...user, deletedAt: new Date(), deletedBy: "admin@example.com" }); const result = await db.users.deleteOne({ _id: user._id }); console.log(`Utilisateur ${user.email} supprimé avec succès`); } } catch (error) { console.error("Erreur lors de la suppression:", error); } // Résultat attendu (exemple) : // 1 document(s) supprimé(s) // OU // Utilisateur test@example.com supprimé avec succès -
deleteMany()
Suppression de plusieurs documents// Supprimer plusieurs documents correspondant aux critères const result = await db.logs.deleteMany( { // Supprimer les logs plus anciens que 30 jours timestamp: { $lt: new Date(new Date() - 30 * 24 * 60 * 60 * 1000) }, // Ne pas supprimer les logs importants priority: { $ne: "high" } }, { writeConcern: { w: "majority", wtimeout: 5000 // Timeout de 5 secondes }, comment: "Nettoyage des logs mensuel" // Pour le suivi des opérations } ); console.log(`${result.deletedCount} logs supprimés`); // Exemple avec journalisation avancée const batchSize = 1000; let deletedCount = 0; let lastId = null; while (true) { // Récupérer un lot de documents à supprimer const batch = await db.logs .find(lastId ? { _id: { $gt: lastId }, timestamp: { $lt: new Date(new Date() - 30 * 24 * 60 * 60 * 1000) } } : { timestamp: { $lt: new Date(new Date() - 30 * 24 * 60 * 60 * 1000) } }) .sort({ _id: 1 }) .limit(batchSize) .toArray(); if (batch.length === 0) break; // Récupérer les IDs à supprimer const idsToDelete = batch.map(doc => doc._id); // Journaliser avant suppression await db.deletedLogs.insertMany(batch.map(doc => ({ ...doc, deletedAt: new Date(), deletedBy: "cleanup-script" }))); // Supprimer le lot const result = await db.logs.deleteMany({ _id: { $in: idsToDelete } }); deletedCount += result.deletedCount; // Mettre à jour le dernier ID traité lastId = batch[batch.length - 1]._id; console.log(`Lot de ${result.deletedCount} logs supprimés (total: ${deletedCount})`); // Pause pour réduire la charge sur le serveur await new Promise(resolve => setTimeout(resolve, 100)); } console.log(`Opération terminée. Total supprimé: ${deletedCount} logs.`); // Résultat attendu (exemple) : // Lot de 1000 logs supprimés (total: 1000) // Lot de 1000 logs supprimés (total: 2000) // Lot de 345 logs supprimés (total: 2345) // Opération terminée. Total supprimé: 2345 logs. -
Bonnes Pratiques
- Toujours sauvegarder les données avant des suppressions en masse
- Utiliser des transactions pour des suppressions atomiques sur plusieurs collections
- Envisagez d'utiliser des "suppressions logiques" (champ "deleted") plutôt que des suppressions physiques
- Testez toujours les critères de suppression avec une requête find() d'abord
-
-
Requêtes Avancées MongoDB
Cette section couvre des requêtes complexes combinant plusieurs opérateurs et étapes d'agrégation.-
Jointures Complexes avec $lookup et $unwind
Scénario :
Récupérer les commandes avec les détails des produits et les informations clients, en calculant le total par catégorie.
db.orders.aggregate([ // Étape 1: Filtrer les commandes des 30 derniers jours { $match: { orderDate: { $gte: new Date(new Date() - 30 * 24 * 60 * 60 * 1000) } } }, // Étape 2: Joindre avec la collection des clients { $lookup: { from: "customers", localField: "customerId", foreignField: "_id", as: "customer" } }, // Étape 3: Déplier le tableau des articles commandés { $unwind: "$items" }, // Étape 4: Joindre avec la collection des produits { $lookup: { from: "products", localField: "items.productId", foreignField: "_id", as: "product" } }, // Étape 5: Déplier le tableau des produits { $unwind: "$product" }, // Étape 6: Calculer le total pour chaque ligne de commande { $addFields: { "items.total": { $multiply: ["$items.quantity", "$product.price"] } } }, // Étape 7: Grouper par catégorie de produit et calculer les totaux { $group: { _id: { category: "$product.category" }, totalSales: { $sum: "$items.total" }, orderCount: { $sum: 1 }, averageOrderValue: { $avg: "$items.total" } } }, // Étape 8: Trier par total des ventes (décroissant) { $sort: { totalSales: -1 } } ])Explication :
- $match : Filtre les commandes récentes
- $lookup : Jointure avec la collection des clients
- $unwind : Déploie le tableau des articles
- $lookup : Jointure avec la collection des produits
- $group : Agrège par catégorie avec calculs
- $project : Formate le résultat final
-
Requêtes Géospatiales Avancées
Scénario :
Trouver les 10 restaurants les plus proches dans un rayon de 2 km, avec une note moyenne supérieure à 4.
Recherche géospatiale avec agrégation// 1. Création d'un index géospatial 2dsphere (à exécuter une seule fois) // db.restaurants.createIndex({ location: "2dsphere" }); // 2. Requête pour trouver les restaurants à proximité const pointInteret = { type: "Point", coordinates: [2.3522, 48.8566] // Paris, France }; const result = await db.restaurants.aggregate([ { $geoNear: { near: pointInteret, distanceField: "distance", maxDistance: 2000, // 2 km en mètres spherical: true, query: { 'grades.score': { $gte: 4 }, cuisine: { $exists: true } } } }, { $lookup: { from: "neighborhoods", localField: "neighborhood", foreignField: "name", as: "neighborhood_info" } }, { $unwind: "$neighborhood_info" }, { $project: { name: 1, cuisine: 1, "address.street": 1, distance: { $round: ["$distance", 0] }, neighborhood: "$neighborhood_info.name", borough: 1, lastGrade: { $arrayElemAt: ["$grades", -1] } } }, { $sort: { distance: 1 } }, { $limit: 10 } ]); console.log(JSON.stringify(result, null, 2)); -
Recherche de Texte Avancée
Scénario :
Recherche avancée d'articles de blog avec pondération des champs, filtrage par date, catégorie et popularité.
Recherche de texte avancée avec agrégation// 1. Création d'un index de recherche en texte (à exécuter une seule fois) // db.articles.createIndex( // { // title: "text", // content: "text", // tags: "text" // }, // { // weights: { // title: 5, // Le titre a plus de poids // tags: 3, // Les tags ont un poids moyen // content: 1 // Le contenu a le poids le plus faible // }, // name: "text_search_index" // } // ); // 2. Requête de recherche avancée avec agrégation const searchTerm = "développement web"; const minDate = new Date(new Date().setFullYear(new Date().getFullYear() - 1)); // Dernière année const result = await db.articles.aggregate([ { $search: { text: { query: searchTerm, path: ["title", "content", "tags"], fuzzy: { maxEdits: 2, // Tolère jusqu'à 2 fautes de frappe prefixLength: 3, // Nécessite les 3 premières lettres exactes maxExpansions: 100 // Limite l'expansion des termes }, score: { // Boost pour les correspondances exactes boost: { value: 2 } } }, // Filtres supplémentaires compound: { filter: [ { range: { path: "publishedAt", gte: minDate, lte: new Date() } }, { range: { path: "readTime", lte: 15 // Temps de lecture max de 15 minutes } }, { text: { query: ["tutoriel", "guide", "exemple"], path: "tags" } } ], // Boost pour les articles populaires should: [ { range: { path: "views", boost: { linear: { origin: 0, scale: 10000, decay: 0.5 } } } }, { range: { path: "claps", boost: { linear: { origin: 0, scale: 1000, decay: 0.5 } } } } ] } } }, // Jointure avec la collection des auteurs { $lookup: { from: "users", localField: "authorId", foreignField: "_id", as: "author" } }, { $unwind: "$author" }, // Projection des champs pertinents { $project: { _id: 1, title: 1, excerpt: 1, readTime: 1, publishedAt: 1, views: 1, claps: 1, tags: 1, author: { name: 1, avatar: 1, title: 1 }, // Calcul du score de pertinence relevance: { $meta: "searchScore" }, // Calcul d'un score personnalisé score: { $add: [ { $multiply: [{ $meta: "searchScore" }, 0.6] }, { $multiply: [{ $divide: ["$views", 1000] }, 0.2] }, { $multiply: [{ $divide: ["$claps", 100] }, 0.2] } ] } } }, // Tri par score personnalisé { $sort: { score: -1 } }, // Pagination { $skip: 0 }, { $limit: 10 } ]); console.log(JSON.stringify(result, null, 2)); -
Agrégations Temporelles
Scénario :
Analyser les ventes par période (jour, semaine, mois) avec des fenêtres glissantes.
Agrégation de séries temporelles avancée// 1. Création d'une collection de séries temporelles (à exécuter une seule fois) // db.createCollection("sensorReadings", { // timeseries: { // timeField: "timestamp", // metaField: "sensorId", // granularity: "minutes" // } // }); // 2. Agrégation avancée sur les séries temporelles const startDate = new Date(); startDate.setMonth(startDate.getMonth() - 6); // Données des 6 derniers mois const result = await db.sensorReadings.aggregate([ // Étape 1: Filtrer par plage de dates { $match: { timestamp: { $gte: startDate }, status: "active" } }, // Étape 2: Grouper par fenêtres de temps et capteur { $group: { _id: { // Arrondir à l'heure la plus proche date: { $dateTrunc: { date: "$timestamp", unit: "hour", binSize: 1 } }, sensorId: "$sensorId", location: "$location" }, // Calculer les métriques avgValue: { $avg: "$value" }, maxValue: { $max: "$value" }, minValue: { $min: "$value" }, sampleCount: { $sum: 1 }, // Détecter les anomalies (valeurs en dehors de 2 écarts-types) stdDev: { $stdDevSamp: "$value" }, // Garder le premier et dernier timestamp de la fenêtre firstTimestamp: { $first: "$timestamp" }, lastTimestamp: { $last: "$timestamp" }, // Collecter les 5 premières valeurs pour référence sampleValues: { $push: { $slice: ["$value", 5] } } } }, // Étape 3: Calculer les anomalies { $addFields: { isAnomaly: { $or: [ { $gt: [ "$maxValue", { $add: ["$avgValue", { $multiply: [2, "$stdDev"] }] } ] }, { $lt: [ "$minValue", { $subtract: ["$avgValue", { $multiply: [2, "$stdDev"] }] } ] } ] }, // Calculer la tendance sur la fenêtre valueChange: { $subtract: [ { $arrayElemAt: ["$sampleValues", -1] }, { $arrayElemAt: ["$sampleValues", 0] } ] } } }, // Étape 4: Projeter les champs pertinents { $project: { _id: 0, timestamp: "$_id.date", sensorId: "$_id.sensorId", location: "$_id.location", metrics: { average: { $round: ["$avgValue", 2] }, max: { $round: ["$maxValue", 2] }, min: { $round: ["$minValue", 2] }, sampleCount: 1, standardDeviation: { $round: ["$stdDev", 2] }, isAnomaly: 1, valueChange: { $round: ["$valueChange", 2] }, timeRange: { start: "$firstTimestamp", end: "$lastTimestamp" } } } }, // Étape 5: Trier par date et capteur { $sort: { timestamp: 1, sensorId: 1 } }, // Étape 6: Optionnel - Fenêtres mobiles { $setWindowFields: { partitionBy: "$sensorId", sortBy: { timestamp: 1 }, output: { movingAvg: { $avg: "$metrics.average", window: { documents: ["unbounded", "current"] } }, movingStdDev: { $stdDevSamp: "$metrics.average", window: { documents: ["unbounded", "current"] } } } } } ]); console.log(JSON.stringify(result, null, 2));
-
-
-
Indexation
Types d'index
- Index simple : Sur un champ unique
- Index composé : Sur plusieurs champs
- Index textuel : Pour les recherches textuelles
- Index géospatial : Pour les données de localisation
Création d'un index
// 1. Création d'un index simple sur un champ unique // Crée un index croissant (1) sur le champ 'email' db.utilisateurs.createIndex({ email: 1 }); // 2. Création d'un index composé sur plusieurs champs db.commandes.createIndex({ clientId: 1, date: -1 }); // - clientId en ordre croissant // - date en ordre décroissant (pour avoir les commandes les plus récentes en premier) // 3. Création d'un index unique avec contrainte db.utilisateurs.createIndex( { username: 1 }, { unique: true, name: "username_unique" } ); // 4. Création d'un index TTL (Time-To-Live) pour l'expiration automatique des documents db.sessions.createIndex( { lastActivity: 1 }, { expireAfterSeconds: 3600 * 24 * 7 } // Supprime après 7 jours d'inactivité ); // 5. Création d'un index de texte pour la recherche plein texte db.articles.createIndex( { titre: "text", contenu: "text" }, { weights: { titre: 3, contenu: 1 } } // Le titre a plus de poids que le contenu ); // 6. Création d'un index géospatial 2dsphere pour les requêtes géographiques db.restaurants.createIndex({ localisation: "2dsphere" }); // 7. Création d'un index partiel (seulement sur les documents qui correspondent au filtre) db.articles.createIndex( { vues: 1 }, { partialFilterExpression: { etat: "publié" } } ); // 8. Création d'un index avec options avancées db.produits.createIndex( { nom: 1, categorie: 1 }, { name: "idx_nom_categorie", background: true, // Construction en arrière-plan sparse: true, // N'inclut que les documents où le champ existe collation: { // Règles de tri spécifiques locale: "fr", strength: 2 } } ); // Vérification des index existants sur une collection db.utilisateurs.getIndexes(); // Suppression d'un index db.utilisateurs.dropIndex("email_1"); // Suppression de tous les index (sauf _id) // db.utilisateurs.dropIndexes();Meilleures pratiques
- Indexer les champs fréquemment utilisés
- Éviter les index trop complexes
- Surveiller l'utilisation des index
- Supprimer les index inutilisés
-
Aggregation Pipeline
Opérateurs principaux
$match: Filtrage des documents$group: Agrégation de données$sort: Tri des résultats$project: Sélection des champs$limit: Limite des résultats
Exemple d'aggregation
db.collection.aggregate([ { $match: { age: { $gt: 25 } } }, { $group: { _id: "$city", averageAge: { $avg: "$age" } } }, { $sort: { averageAge: -1 } }, { $limit: 5 } ]); -
Best Practices
Performance
- Utiliser des index appropriés
- Optimiser les requêtes
- Éviter les documents trop volumineux
- Utiliser les projections
Structure des données
- Choisir une structure adaptée
- Gérer les relations entre documents
- Éviter les redondances inutiles
- Documenter la structure
Sécurité
- Utiliser des schémas sécurisés
- Restreindre les accès
- Utiliser l'authentification
- Activer l'encryption
-
Quiz
Quelles sont les principales opérations CRUD en MongoDB ? -
QCM
-
-
Requêtes avancées et agrégation
Requêtes avancées & Agrégation dans MongoDB
Aggregation Framework
- Pipeline de traitements sur les documents (similaire à SQL GROUP BY, HAVING, etc.)
- Principaux stages :
$match(filtrage),$group(agrégation),$project(projection de champs) - Stages avancés :
$sort,$limit,$unwind,$lookup(jointure)
db.commandes.aggregate([ { $match: { statut: "livrée" } }, { $group: { _id: "$client", total: { $sum: "$montant" } } }, { $sort: { total: -1 } } ]);Optimisation des requêtes
- Utilisation de
$explainpour analyser les plans d’exécution - Indexation avancée : index composés, textuels, géospatiaux
- Covered queries : requêtes totalement servies par l’index
Transactions
- Support des transactions multi-documents depuis MongoDB 4.x
- Exemple :
const session = db.getMongo().startSession(); session.startTransaction(); try { db.comptes.updateOne({ _id: 1 }, { $inc: { solde: -100 } }); db.comptes.updateOne({ _id: 2 }, { $inc: { solde: 100 } }); session.commitTransaction(); } catch (e) { session.abortTransaction(); }
Mini-lab : Pipeline d’agrégation
- Télécharge un jeu de données (ex : tweets.json)
- Importe-le dans MongoDB :
mongoimport --db test --collection tweets --file tweets.json --jsonArray - Construis un pipeline d’agrégation pour compter le nombre de tweets par utilisateur :
- Modifie le pipeline pour filtrer les tweets contenant un mot-clé (ex : "mongodb")
db.tweets.aggregate([ { $group: { _id: "$user", nb: { $sum: 1 } } }, { $sort: { nb: -1 } } ]);Quiz interactif
-
Recherche avancée : find() et findOne()
La Commande find()
-
Syntaxe Complète
// Syntaxe de base de find() db.collection.find( query, // Critères de sélection (optionnel) { // Projection (optionnel) field1: 1, // Inclure le champ (1 ou true) field2: 0, // Exclure le champ (0 ou false) // ... } ).options(); // Options de requête -
Paramètres Principaux
Paramètre Type Description Valeur par défaut queryDocument Critères de sélection (opérateurs de requête) {}(tous les documents)projectionDocument Champs à inclure/exclure (1/0 ou true/false) Tous les champs -
Options de Curseur
.limit(n): Limite le nombre de résultats.skip(n): Saute les n premiers documents.sort({field: 1/-1}): Trie les résultats.project({}): Alternative à la projection dans find()
-
Exemples Concrets
// Trouver tous les documents dans une collection const allUsers = await db.users.find({}).toArray(); console.log(`Nombre total d'utilisateurs: ${allUsers.length}`); // Trouver avec des critères de recherche const activeUsers = await db.users.find({ status: "active", lastLogin: { $gt: new Date(new Date() - 30 * 24 * 60 * 60 * 1000) } }).toArray(); console.log(`Utilisateurs actifs ce mois-ci: ${activeUsers.length}`); // Utilisation de la projection pour ne récupérer que certains champs const userEmails = await db.users.find( { role: "admin" }, { projection: { email: 1, name: 1, _id: 0 // Exclure l'ID par défaut } } ).toArray(); console.log("Emails des administrateurs:", userEmails); // Trier et limiter les résultats const recentOrders = await db.orders .find({ status: "completed" }) .sort({ orderDate: -1 }) // -1 pour décroissant, 1 pour croissant .limit(10) // Seulement les 10 premiers résultats .toArray(); console.log("10 commandes récentes:", recentOrders); // Pagination avec skip et limit const page = 2; const perPage = 20; const products = await db.products .find({ category: "electronics" }) .sort({ price: 1 }) .skip((page - 1) * perPage) .limit(perPage) .toArray(); console.log(`Page ${page} des produits électroniques:`, products); // Compter les documents sans les récupérer const inactiveUserCount = await db.users.countDocuments({ status: "inactive" }); console.log(`Nombre d'utilisateurs inactifs: ${inactiveUserCount}`); // Utilisation de l'opérateur $in pour plusieurs valeurs const featuredCategories = ["Électronique", "Informatique", "Téléphonie"]; const featuredProducts = await db.products .find({ category: { $in: featuredCategories }, stock: { $gt: 0 } }) .toArray(); console.log(`Produits en vedette en stock: ${featuredProducts.length}`); // Recherche de texte const searchResults = await db.articles .find({ $text: { $search: "MongoDB introduction" }, status: "published" }) .sort({ score: { $meta: "textScore" } }) .toArray(); console.log(`Résultats de la recherche: ${searchResults.length} articles trouvés`); -
Opérateurs de Requête Courants
$eq: Égal à$ne: Différent de$gt, $gte: Plus grand (ou égal) que$lt, $lte: Plus petit (ou égal) que$in, $nin: Dans / pas dans un tableau
$and, $or, $nor: Opérateurs logiques$exists: Champ existant$regex: Expression régulière$elemMatch: Correspondance dans un tableau
-
Remarques Importantes
- Les requêtes sont sensibles à la casse par défaut
- Les index peuvent grandement améliorer les performances
- Évitez les opérations qui scannent toute la collection (COLLSCAN)
- Utilisez la projection pour limiter les données retournées
-
La Méthode findOne()
La méthode
findOne()est similaire àfind()mais retourne uniquement le premier document correspondant aux critères de recherche, ounullsi aucun document n'est trouvé. -
Différences avec find()
- Retourne un document unique plutôt qu'un curseur
- Plus efficace lorsque vous n'avez besoin que d'un seul résultat
- Ne nécessite pas d'appel à
toArray()ounext() - Retourne
nullsi aucun document n'est trouvé (au lieu d'un curseur vide)
-
Exemples d'Utilisation
// Trouver un utilisateur par son email const user = await db.users.findOne({ email: "jean.dupont@example.com" }); if (user) { console.log(`Utilisateur trouvé: ${user.name}`); } else { console.log("Aucun utilisateur trouvé avec cet email"); } // Utilisation avec projection pour ne récupérer que certains champs const userProfile = await db.users.findOne( { username: "johndoe" }, { projection: { name: 1, email: 1, lastLogin: 1, _id: 0 } } ); console.log("Profil utilisateur:", userProfile); // Trouver le document avec le plus grand score const topScore = await db.scores.findOne( {}, { sort: { score: -1 }, projection: { player: 1, score: 1, _id: 0 } } ); console.log("Meilleur score:", topScore); // Utilisation avec des opérateurs de requête const recentOrder = await db.orders.findOne({ customerId: "user123", status: { $in: ["processing", "shipped"] }, orderDate: { $gte: new Date(new Date() - 7 * 24 * 60 * 60 * 1000) } }, { sort: { orderDate: -1 }, projection: { orderId: 1, total: 1, status: 1, items: 1 } }); if (recentOrder) { console.log("Dernière commande récente:", recentOrder); } // Recherche avec un index texte const article = await db.articles.findOne( { $text: { $search: "MongoDB performance" }, status: "published" }, { sort: { score: { $meta: "textScore" } }, projection: { title: 1, excerpt: 1, score: { $meta: "textScore" }, _id: 0 } } ); if (article) { console.log("Article trouvé:", article.title); console.log(`Score de pertinence: ${article.score.toFixed(2)}`); } // Utilisation avec des opérateurs de mise à jour pour une opération "trouver et mettre à jour" const updatedUser = await db.users.findOneAndUpdate( { email: "jean.dupont@example.com" }, { $inc: { loginCount: 1 }, $set: { lastLogin: new Date() } }, { returnNewDocument: true, // Retourne le document après la mise à jour projection: { password: 0 } // Exclure le mot de passe } ); if (updatedUser) { console.log(`Bienvenue de nouveau, ${updatedUser.name}!`); console.log(`Nombre de connexions: ${updatedUser.loginCount}`); }
-
Exercices, examens et projets
Atelier MongoDB
Concevoir un schéma documentaire et manipuler une collection.
Obligatoire ConsulterExamen MongoDB
Évaluer les requêtes, agrégations, indexations et transactions.
Obligatoire ConsulterProjet MongoDB
Développer une API documentaire avec données de démonstration.
Livrables :
- Schéma
- API
- Jeu de données