1. Configuration initiale
Installation et connexion
1# Connexion au serveur MongoDB local
2mongosh
3
4# Ou connexion à MongoDB Atlas
5mongosh "mongodb+srv://cluster.xxxxx.mongodb.net/shop_maroc" --username etudiant
Création et utilisation de la base de données
1// Créer/utiliser la base de données
2use shop_maroc
3
4// Vérifier la base courante
5db.getName()
2. Structure des données
Collection products (Produits)
1{
2 "_id": ObjectId(),
3 "sku": "CAF-001",
4 "name": "Caftan Marocain Premium",
5 "category": "Vêtements",
6 "subcategory": "Traditionnel",
7 "price": {
8 "amount": 2500,
9 "currency": "MAD"
10 },
11 "stock": {
12 "quantity": 15,
13 "warehouse": "Casablanca"
14 },
15 "attributes": {
16 "color": ["Bleu", "Vert", "Rouge"],
17 "size": ["S", "M", "L", "XL"],
18 "material": "Soie naturelle"
19 },
20 "ratings": {
21 "average": 4.8,
22 "count": 127
23 },
24 "tags": ["traditionnel", "mariage", "luxe"],
25 "createdAt": ISODate("2024-01-15"),
26 "lastModified": ISODate("2024-03-20")
27}
Collection customers (Clients)
1{
2 "_id": ObjectId(),
3 "customerId": "CLT-10234",
4 "firstName": "Fatima",
5 "lastName": "Benali",
6 "email": "fatima.benali@email.com",
7 "phone": "+212 6 12 34 56 78",
8 "address": {
9 "street": "45 Boulevard Mohammed V",
10 "city": "Rabat",
11 "postalCode": "10000",
12 "country": "Maroc"
13 },
14 "preferences": {
15 "newsletter": true,
16 "language": "fr",
17 "currency": "MAD"
18 },
19 "loyaltyPoints": 1250,
20 "registeredAt": ISODate("2023-05-10"),
21 "lastPurchase": ISODate("2024-03-15")
22}
Collection orders (Commandes)
1{
2 "_id": ObjectId(),
3 "orderNumber": "CMD-2024-0543",
4 "customerId": "CLT-10234",
5 "items": [
6 {
7 "productId": ObjectId("..."),
8 "sku": "CAF-001",
9 "name": "Caftan Marocain Premium",
10 "quantity": 1,
11 "unitPrice": 2500,
12 "total": 2500
13 },
14 {
15 "productId": ObjectId("..."),
16 "sku": "BAB-003",
17 "name": "Babouche artisanale",
18 "quantity": 2,
19 "unitPrice": 350,
20 "total": 700
21 }
22 ],
23 "totals": {
24 "subtotal": 3200,
25 "shipping": 50,
26 "tax": 640,
27 "total": 3890
28 },
29 "shipping": {
30 "method": "Amana",
31 "address": {
32 "street": "45 Boulevard Mohammed V",
33 "city": "Rabat",
34 "postalCode": "10000"
35 },
36 "estimatedDelivery": ISODate("2024-03-25")
37 },
38 "payment": {
39 "method": "CMI",
40 "status": "completed",
41 "transactionId": "TRX-789456"
42 },
43 "status": "processing",
44 "statusHistory": [
45 {
46 "status": "pending",
47 "date": ISODate("2024-03-20T10:00:00Z")
48 },
49 {
50 "status": "paid",
51 "date": ISODate("2024-03-20T10:05:00Z")
52 },
53 {
54 "status": "processing",
55 "date": ISODate("2024-03-20T10:30:00Z")
56 }
57 ],
58 "createdAt": ISODate("2024-03-20T10:00:00Z")
59}
3. Opérations CREATE (Insertion)
3.1 Insertion simple (insertOne)
1// Insérer un nouveau produit
2db.products.insertOne({
3 sku: "THÉ-001",
4 name: "Thé à la menthe - Boîte luxe",
5 category: "Alimentation",
6 subcategory: "Boissons",
7 price: {
8 amount: 150,
9 currency: "MAD"
10 },
11 stock: {
12 quantity: 50,
13 warehouse: "Marrakech"
14 },
15 attributes: {
16 weight: "500g",
17 origin: "Maroc",
18 type: "Bio"
19 },
20 ratings: {
21 average: 0,
22 count: 0
23 },
24 tags: ["thé", "menthe", "bio", "cadeau"],
25 createdAt: new Date(),
26 lastModified: new Date()
27})
28
29// Vérifier l'insertion
30db.products.findOne({ sku: "THÉ-001" })
3.2 Insertion multiple (insertMany)
1// Insérer plusieurs clients
2db.customers.insertMany([
3 {
4 customerId: "CLT-10235",
5 firstName: "Mohammed",
6 lastName: "Alaoui",
7 email: "m.alaoui@email.com",
8 phone: "+212 6 22 33 44 55",
9 address: {
10 street: "12 Rue des Consuls",
11 city: "Casablanca",
12 postalCode: "20000",
13 country: "Maroc"
14 },
15 preferences: {
16 newsletter: true,
17 language: "ar",
18 currency: "MAD"
19 },
20 loyaltyPoints: 500,
21 registeredAt: new Date(),
22 lastPurchase: null
23 },
24 {
25 customerId: "CLT-10236",
26 firstName: "Amina",
27 lastName: "Rachidi",
28 email: "amina.r@email.com",
29 phone: "+212 6 33 44 55 66",
30 address: {
31 street: "78 Avenue Hassan II",
32 city: "Fès",
33 postalCode: "30000",
34 country: "Maroc"
35 },
36 preferences: {
37 newsletter: false,
38 language: "fr",
39 currency: "MAD"
40 },
41 loyaltyPoints: 0,
42 registeredAt: new Date(),
43 lastPurchase: null
44 }
45])
46
47// Vérifier le nombre de documents insérés
48db.customers.countDocuments()
3.3 Insertion avec génération d'ID personnalisé
1// Créer une nouvelle commande avec ID personnalisé
2const orderId = new ObjectId()
3const orderNumber = `CMD-2024-${Date.now()}`
4
5db.orders.insertOne({
6 _id: orderId,
7 orderNumber: orderNumber,
8 customerId: "CLT-10235",
9 items: [
10 {
11 productId: ObjectId(),
12 sku: "THÉ-001",
13 name: "Thé à la menthe - Boîte luxe",
14 quantity: 3,
15 unitPrice: 150,
16 total: 450
17 }
18 ],
19 totals: {
20 subtotal: 450,
21 shipping: 0, // Gratuit pour commandes > 400 MAD
22 tax: 90,
23 total: 540
24 },
25 shipping: {
26 method: "CTM Messagerie",
27 address: {
28 street: "12 Rue des Consuls",
29 city: "Casablanca",
30 postalCode: "20000"
31 },
32 estimatedDelivery: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000) // +3 jours
33 },
34 payment: {
35 method: "COD", // Cash on Delivery
36 status: "pending"
37 },
38 status: "pending",
39 statusHistory: [
40 {
41 status: "pending",
42 date: new Date()
43 }
44 ],
45 createdAt: new Date()
46})
47
48print(`Commande créée avec succès : ${orderNumber}`)
4. Opérations READ (Lecture)
4.1 Requêtes simples
1// Trouver tous les produits
2db.products.find()
3
4// Limiter le nombre de résultats
5db.products.find().limit(5)
6
7// Trouver un produit spécifique
8db.products.findOne({ sku: "CAF-001" })
9
10// Trouver par catégorie
11db.products.find({ category: "Vêtements" })
12
13// Compter les documents
14db.products.countDocuments({ category: "Vêtements" })
4.2 Requêtes avec opérateurs de comparaison
1// Produits avec prix supérieur à 1000 MAD
2db.products.find({
3 "price.amount": { $gt: 1000 }
4})
5
6// Produits entre 500 et 2000 MAD
7db.products.find({
8 "price.amount": { $gte: 500, $lte: 2000 }
9})
10
11// Produits en rupture de stock (quantité < 5)
12db.products.find({
13 "stock.quantity": { $lt: 5 }
14})
15
16// Clients avec plus de 1000 points de fidélité
17db.customers.find({
18 loyaltyPoints: { $gt: 1000 }
19})
4.3 Requêtes avec opérateurs logiques
1// Produits traditionnels OU de luxe
2db.products.find({
3 $or: [
4 { tags: "traditionnel" },
5 { tags: "luxe" }
6 ]
7})
8
9// Produits en stock À Casablanca ET prix < 500 MAD
10db.products.find({
11 $and: [
12 { "stock.warehouse": "Casablanca" },
13 { "price.amount": { $lt: 500 } }
14 ]
15})
16
17// Produits qui NE SONT PAS dans la catégorie Vêtements
18db.products.find({
19 category: { $ne: "Vêtements" }
20})
4.4 Requêtes sur les tableaux
1// Produits disponibles en taille "L"
2db.products.find({
3 "attributes.size": "L"
4})
5
6// Produits avec TOUS ces tags
7db.products.find({
8 tags: { $all: ["traditionnel", "luxe"] }
9})
10
11// Produits avec AU MOINS UN de ces tags
12db.products.find({
13 tags: { $in: ["cadeau", "mariage"] }
14})
15
16// Commandes avec exactement 2 articles
17db.orders.find({
18 items: { $size: 2 }
19})
4.5 Requêtes sur les documents imbriqués
1// Clients de Casablanca
2db.customers.find({
3 "address.city": "Casablanca"
4})
5
6// Commandes payées par carte CMI
7db.orders.find({
8 "payment.method": "CMI",
9 "payment.status": "completed"
10})
11
12// Requête complexe : commandes en cours de livraison vers Rabat
13db.orders.find({
14 "shipping.address.city": "Rabat",
15 status: "shipping"
16})
4.6 Projections (sélection de champs)
1// Afficher seulement nom et prix des produits
2db.products.find(
3 { category: "Vêtements" },
4 { name: 1, price: 1, _id: 0 }
5)
6
7// Exclure certains champs
8db.customers.find(
9 {},
10 { loyaltyPoints: 0, preferences: 0 }
11).limit(3)
12
13// Projection sur documents imbriqués
14db.orders.find(
15 { status: "processing" },
16 {
17 orderNumber: 1,
18 "totals.total": 1,
19 "shipping.estimatedDelivery": 1,
20 _id: 0
21 }
22)
1// Trier par prix décroissant
2db.products.find().sort({ "price.amount": -1 })
3
4// Trier par plusieurs critères
5db.products.find().sort({
6 category: 1,
7 "price.amount": -1
8})
9
10// Pagination
11const pageSize = 10
12const pageNumber = 2
13
14db.products.find()
15 .skip((pageNumber - 1) * pageSize)
16 .limit(pageSize)
17 .sort({ createdAt: -1 })
18
19// Combinaison complexe
20db.products.find({
21 "stock.quantity": { $gt: 0 }
22})
23 .sort({ "ratings.average": -1 })
24 .limit(5)
25 .project({ name: 1, price: 1, ratings: 1 })
4.8 Requêtes avec expressions régulières
1// Produits contenant "Caftan" dans le nom
2db.products.find({
3 name: /Caftan/i // i = insensible à la casse
4})
5
6// Clients dont l'email se termine par @gmail.com
7db.customers.find({
8 email: /@gmail\.com$/
9})
10
11// Produits commençant par "Thé"
12db.products.find({
13 name: /^Thé/
14})
5. Opérations UPDATE (Mise à jour)
5.1 Mise à jour simple (updateOne)
1// Mettre à jour le prix d'un produit
2db.products.updateOne(
3 { sku: "CAF-001" },
4 {
5 $set: {
6 "price.amount": 2800,
7 lastModified: new Date()
8 }
9 }
10)
11
12// Incrémenter la quantité en stock
13db.products.updateOne(
14 { sku: "THÉ-001" },
15 {
16 $inc: { "stock.quantity": 20 }
17 }
18)
19
20// Ajouter un nouveau tag
21db.products.updateOne(
22 { sku: "CAF-001" },
23 {
24 $push: { tags: "bestseller" }
25 }
26)
5.2 Mise à jour multiple (updateMany)
1// Appliquer une réduction de 10% sur tous les vêtements
2db.products.updateMany(
3 { category: "Vêtements" },
4 {
5 $mul: { "price.amount": 0.9 },
6 $set: {
7 promotion: true,
8 lastModified: new Date()
9 }
10 }
11)
12
13// Ajouter des points de fidélité à tous les clients actifs
14db.customers.updateMany(
15 { lastPurchase: { $ne: null } },
16 {
17 $inc: { loyaltyPoints: 100 }
18 }
19)
5.3 Opérations sur les tableaux
1// Ajouter plusieurs couleurs
2db.products.updateOne(
3 { sku: "CAF-001" },
4 {
5 $addToSet: {
6 "attributes.color": {
7 $each: ["Noir", "Blanc", "Or"]
8 }
9 }
10 }
11)
12
13// Retirer un élément d'un tableau
14db.products.updateOne(
15 { sku: "CAF-001" },
16 {
17 $pull: { tags: "bestseller" }
18 }
19)
20
21// Mettre à jour le premier élément correspondant dans un tableau
22db.orders.updateOne(
23 {
24 orderNumber: "CMD-2024-0543",
25 "items.sku": "CAF-001"
26 },
27 {
28 $set: { "items.$.quantity": 2 }
29 }
30)
5.4 Mise à jour ou insertion (upsert)
1// Créer ou mettre à jour un produit
2db.products.updateOne(
3 { sku: "ARG-001" },
4 {
5 $set: {
6 name: "Huile d'Argan Bio 100ml",
7 category: "Cosmétique",
8 price: { amount: 450, currency: "MAD" },
9 stock: { quantity: 30, warehouse: "Agadir" }
10 },
11 $setOnInsert: {
12 createdAt: new Date(),
13 ratings: { average: 0, count: 0 }
14 }
15 },
16 { upsert: true }
17)
5.5 Mise à jour complexe avec pipeline
1// Calculer et mettre à jour le total d'une commande
2db.orders.updateOne(
3 { orderNumber: "CMD-2024-0543" },
4 [
5 {
6 $set: {
7 "totals.subtotal": {
8 $sum: "$items.total"
9 }
10 }
11 },
12 {
13 $set: {
14 "totals.tax": {
15 $multiply: ["$totals.subtotal", 0.2]
16 }
17 }
18 },
19 {
20 $set: {
21 "totals.total": {
22 $add: ["$totals.subtotal", "$totals.tax", "$totals.shipping"]
23 }
24 }
25 }
26 ]
27)
5.6 FindAndModify operations
1// Trouver et mettre à jour, retourner le document modifié
2const updatedProduct = db.products.findOneAndUpdate(
3 { sku: "THÉ-001" },
4 {
5 $inc: { "stock.quantity": -1 },
6 $set: { lastSold: new Date() }
7 },
8 { returnDocument: "after" }
9)
10
11print("Stock mis à jour:", updatedProduct.stock.quantity)
6. Opérations DELETE (Suppression)
6.1 Suppression simple (deleteOne)
1// Supprimer un produit spécifique
2db.products.deleteOne({ sku: "TEST-001" })
3
4// Supprimer la première commande annulée trouvée
5db.orders.deleteOne({ status: "cancelled" })
6.2 Suppression multiple (deleteMany)
1// Supprimer tous les produits en rupture de stock
2db.products.deleteMany({ "stock.quantity": 0 })
3
4// Supprimer les clients inactifs (pas d'achat depuis 2 ans)
5const twoYearsAgo = new Date()
6twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2)
7
8db.customers.deleteMany({
9 $or: [
10 { lastPurchase: { $lt: twoYearsAgo } },
11 { lastPurchase: null }
12 ]
13})
6.3 FindAndDelete
1// Trouver et supprimer, retourner le document supprimé
2const deletedOrder = db.orders.findOneAndDelete(
3 {
4 status: "pending",
5 createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
6 }
7)
8
9if (deletedOrder) {
10 print(`Commande expirée supprimée: ${deletedOrder.orderNumber}`)
11}
7. Opérations avancées sur documents imbriqués
7.1 Manipulation de tableaux d'objets
1// Ajouter un article à une commande existante
2db.orders.updateOne(
3 { orderNumber: "CMD-2024-0543" },
4 {
5 $push: {
6 items: {
7 productId: ObjectId(),
8 sku: "ARG-001",
9 name: "Huile d'Argan Bio",
10 quantity: 1,
11 unitPrice: 450,
12 total: 450
13 }
14 },
15 $inc: {
16 "totals.subtotal": 450,
17 "totals.tax": 90,
18 "totals.total": 540
19 }
20 }
21)
22
23// Mettre à jour un article spécifique dans une commande
24db.orders.updateOne(
25 {
26 orderNumber: "CMD-2024-0543",
27 "items.sku": "CAF-001"
28 },
29 {
30 $inc: {
31 "items.$.quantity": 1,
32 "items.$.total": 2500
33 }
34 }
35)
36
37// Supprimer un article d'une commande
38db.orders.updateOne(
39 { orderNumber: "CMD-2024-0543" },
40 {
41 $pull: {
42 items: { sku: "BAB-003" }
43 }
44 }
45)
7.2 Mise à jour de l'historique des statuts
1// Ajouter un nouveau statut à l'historique
2db.orders.updateOne(
3 { orderNumber: "CMD-2024-0543" },
4 {
5 $set: {
6 status: "shipped"
7 },
8 $push: {
9 statusHistory: {
10 status: "shipped",
11 date: new Date(),
12 note: "Expédié via Amana - Numéro de suivi: AM123456"
13 }
14 }
15 }
16)
17
18// Requête pour trouver l'historique d'une commande
19db.orders.findOne(
20 { orderNumber: "CMD-2024-0543" },
21 { statusHistory: 1, status: 1 }
22)
8. Opérations de validation et cohérence
8.1 Vérification de l'intégrité des données
1// Vérifier les commandes avec des clients inexistants
2const customerIds = db.customers.distinct("customerId")
3
4db.orders.find({
5 customerId: { $nin: customerIds }
6})
7
8// Vérifier les produits avec des prix invalides
9db.products.find({
10 $or: [
11 { "price.amount": { $lte: 0 } },
12 { "price.amount": { $exists: false } },
13 { "price.currency": { $ne: "MAD" } }
14 ]
15})
8.2 Nettoyage et maintenance
1// Nettoyer les tags en double
2db.products.find().forEach(product => {
3 if (product.tags) {
4 const uniqueTags = [...new Set(product.tags)]
5 if (uniqueTags.length !== product.tags.length) {
6 db.products.updateOne(
7 { _id: product._id },
8 { $set: { tags: uniqueTags } }
9 )
10 }
11 }
12})
13
14// Recalculer les moyennes de ratings
15db.products.updateMany(
16 { "ratings.count": 0 },
17 { $set: { "ratings.average": 0 } }
18)
9. Cas pratiques complets
9.1 Processus de commande complet
1// 1. Vérifier la disponibilité du stock
2const product = db.products.findOne({ sku: "CAF-001" })
3if (product.stock.quantity < 1) {
4 print("Produit en rupture de stock!")
5} else {
6 // 2. Créer la commande
7 const order = {
8 orderNumber: `CMD-${new Date().getFullYear()}-${Date.now()}`,
9 customerId: "CLT-10234",
10 items: [{
11 productId: product._id,
12 sku: product.sku,
13 name: product.name,
14 quantity: 1,
15 unitPrice: product.price.amount,
16 total: product.price.amount
17 }],
18 totals: {
19 subtotal: product.price.amount,
20 shipping: 50,
21 tax: product.price.amount * 0.2,
22 total: product.price.amount * 1.2 + 50
23 },
24 status: "pending",
25 createdAt: new Date()
26 }
27
28 // 3. Insérer la commande
29 const result = db.orders.insertOne(order)
30
31 // 4. Mettre à jour le stock
32 db.products.updateOne(
33 { sku: "CAF-001" },
34 {
35 $inc: { "stock.quantity": -1 },
36 $set: { lastModified: new Date() }
37 }
38 )
39
40 // 5. Mettre à jour les points de fidélité du client
41 db.customers.updateOne(
42 { customerId: "CLT-10234" },
43 {
44 $inc: { loyaltyPoints: Math.floor(order.totals.total / 10) },
45 $set: { lastPurchase: new Date() }
46 }
47 )
48
49 print(`Commande créée avec succès: ${order.orderNumber}`)
50}
9.2 Rapport des meilleures ventes
1// Trouver les 5 produits les plus vendus ce mois
2const startOfMonth = new Date()
3startOfMonth.setDate(1)
4startOfMonth.setHours(0, 0, 0, 0)
5
6// Utilisation du framework d'agrégation (aperçu)
7db.orders.aggregate([
8 {
9 $match: {
10 createdAt: { $gte: startOfMonth }
11 }
12 },
13 { $unwind: "$items" },
14 {
15 $group: {
16 _id: "$items.sku",
17 productName: { $first: "$items.name" },
18 totalQuantity: { $sum: "$items.quantity" },
19 totalRevenue: { $sum: "$items.total" }
20 }
21 },
22 { $sort: { totalQuantity: -1 } },
23 { $limit: 5 }
24])
10. Bonnes pratiques et conseils
Conventions de nommage
- Collections au pluriel :
products, customers, orders
- Champs en camelCase :
firstName, createdAt
- Constantes en MAJUSCULES :
MAD, PENDING
Performance
- Toujours utiliser des projections pour limiter les données retournées
- Créer des index sur les champs fréquemment interrogés
- Éviter les requêtes qui scannent toute la collection
Sécurité
- Valider les entrées avant l'insertion
- Utiliser des transactions pour les opérations critiques
- Ne jamais stocker de mots de passe en clair
Debugging
1// Activer le profiling pour analyser les performances
2db.setProfilingLevel(1, { slowms: 100 })
3
4// Voir les opérations lentes
5db.system.profile.find().limit(5).sort({ ts: -1 }).pretty()
6
7// Expliquer une requête
8db.products.find({ category: "Vêtements" }).explain("executionStats")
Exercices de synthèse
- Gestion de panier : Créer un système de panier d'achat temporaire
- Système de notation : Implémenter l'ajout et le calcul de moyennes de ratings
- Gestion de stock : Créer des alertes pour les produits en rupture
- Rapport client : Extraire l'historique d'achat d'un client spécifique
- Migration de données : Transformer des données relationnelles en documents MongoDB