Transformez et analysez vos données avec puissance
Module NoSQL - Ăcole d'IngĂ©nieurs
Le framework d'agrégation MongoDB permet de transformer et analyser les données à travers un pipeline d'étapes de traitement.
// Pipeline simple : chiffre d'affaires par catégorie
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: {
_id: "$category",
totalRevenue: { $sum: "$amount" }
}},
{ $sort: { totalRevenue: -1 } }
])
| Stage | Description | Exemple |
|---|---|---|
$match |
Filtre les documents | WHERE en SQL |
$group |
Regroupe et agrĂšge | GROUP BY en SQL |
$project |
Transforme les champs | SELECT en SQL |
$sort |
Trie les résultats | ORDER BY en SQL |
$limit |
Limite le nombre | LIMIT en SQL |
$lookup |
Jointure entre collections | JOIN en SQL |
// Analyse des ventes d'une librairie marocaine
db.sales.aggregate([
// 1. Filtrer les ventes de 2024
{ $match: {
date: { $gte: ISODate("2024-01-01") }
}},
// 2. Regrouper par ville
{ $group: {
_id: "$store.city",
totalSales: { $sum: "$amount" },
avgTransaction: { $avg: "$amount" },
count: { $sum: 1 }
}},
// 3. Trier par ventes décroissantes
{ $sort: { totalSales: -1 } },
// 4. Limiter au top 5
{ $limit: 5 }
])
3 / 14
// Filtrer les commandes d'un montant élevé à Casablanca
{ $match: {
"customer.city": "Casablanca",
amount: { $gte: 1000 },
status: { $in: ["shipped", "delivered"] }
}}
đĄ Placez $match le plus tĂŽt possible pour rĂ©duire les donnĂ©es Ă traiter
// Calculer et formater les données
{ $project: {
// Renommer des champs
customerName: "$customer.name",
orderDate: "$createdAt",
// Calculs
totalWithTax: {
$multiply: ["$amount", 1.2]
},
// Formatage de dates
month: {
$month: "$createdAt"
},
// Champs conditionnels
category: {
$cond: {
if: { $gte: ["$amount", 1000] },
then: "Premium",
else: "Standard"
}
},
// Exclure des champs
_id: 0,
internalNotes: 0
}}
$sum
Somme des valeurs
$avg
Moyenne
$min
Valeur minimale
$max
Valeur maximale
$first
PremiĂšre valeur
$last
DerniĂšre valeur
$push
Tableau de valeurs
$addToSet
Valeurs uniques
db.orders.aggregate([
{ $match: {
date: { $gte: ISODate("2024-01-01") }
}},
{ $group: {
_id: {
category: "$product.category",
month: { $month: "$date" }
},
// Statistiques de vente
totalRevenue: { $sum: "$amount" },
avgOrderValue: { $avg: "$amount" },
orderCount: { $sum: 1 },
// Produits uniques vendus
uniqueProducts: {
$addToSet: "$product.sku"
},
// Top client du mois
topCustomer: {
$first: "$customer.name"
},
// Toutes les villes de livraison
cities: {
$addToSet: "$delivery.city"
}
}},
// Calculer des métriques supplémentaires
{ $project: {
category: "$_id.category",
month: "$_id.month",
totalRevenue: 1,
avgOrderValue: { $round: ["$avgOrderValue", 2] },
orderCount: 1,
uniqueProductCount: { $size: "$uniqueProducts" },
geographicReach: { $size: "$cities" }
}}
])
5 / 14
// 2 requĂȘtes sĂ©parĂ©es
const order = db.orders.findOne({ _id: orderId });
const customer = db.customers.findOne({
_id: order.customerId
});
// 1 seule requĂȘte
db.orders.aggregate([
{ $lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customerInfo"
}}
])
db.orders.aggregate([
// Jointure avec la collection customers
{ $lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
}},
// Dérouler le tableau (1 client par commande)
{ $unwind: "$customer" },
// Jointure avec la collection products
{ $lookup: {
from: "products",
localField: "items.productId",
foreignField: "_id",
as: "productDetails"
}},
// Projection finale
{ $project: {
orderNumber: 1,
orderDate: 1,
customerName: "$customer.name",
customerCity: "$customer.address.city",
totalAmount: 1,
productsOrdered: { $size: "$productDetails" }
}}
])
$unwind transforme chaque élément d'un tableau en un document séparé.
{
order_id: "CMD-001",
items: [
{ product: "Livre A", qty: 2, price: 150 },
{ product: "Livre B", qty: 1, price: 200 }
]
}
{
order_id: "CMD-001",
items: { product: "Livre A", qty: 2, price: 150 }
}
{
order_id: "CMD-001",
items: { product: "Livre B", qty: 1, price: 200 }
}
db.orders.aggregate([
// Décomposer les articles
{ $unwind: "$items" },
// Regrouper par produit
{ $group: {
_id: "$items.product",
totalQuantity: { $sum: "$items.qty" },
totalRevenue: {
$sum: { $multiply: ["$items.qty", "$items.price"] }
},
avgPrice: { $avg: "$items.price" }
}},
// Trier par quantité vendue
{ $sort: { totalQuantity: -1 } },
// Top 10 best-sellers
{ $limit: 10 }
])
{ $project: {
firstItem: { $arrayElemAt: ["$items", 0] },
lastItem: { $arrayElemAt: ["$items", -1] }
}}
{ $project: {
expensiveItems: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: ["$$item.price", 500] }
}
}
}}
{ $project: {
itemsWithTax: {
$map: {
input: "$items",
as: "item",
in: {
product: "$$item.product",
priceWithTax: {
$multiply: ["$$item.price", 1.2]
}
}
}
}
}}
db.carts.aggregate([
{ $project: {
customerId: 1,
// Articles avec réduction appliquée
discountedItems: {
$map: {
input: "$items",
as: "item",
in: {
product: "$$item.product",
originalPrice: "$$item.price",
finalPrice: {
$cond: {
if: { $gte: ["$$item.qty", 3] },
then: { $multiply: ["$$item.price", 0.9] },
else: "$$item.price"
}
}
}
}
},
// Total du panier
cartTotal: {
$reduce: {
input: "$items",
initialValue: 0,
in: {
$add: [
"$$value",
{ $multiply: ["$$this.price", "$$this.qty"] }
]
}
}
}
}}
])
8 / 14
$facet permet d'exĂ©cuter plusieurs pipelines en parallĂšle sur les mĂȘmes donnĂ©es.
// Dashboard de ventes complet en une requĂȘte
db.sales.aggregate([
{ $match: {
date: {
$gte: ISODate("2024-01-01"),
$lt: ISODate("2024-02-01")
}
}},
{ $facet: {
// Facette 1 : Par catégorie
"parCategorie": [
{ $group: {
_id: "$category",
total: { $sum: "$amount" }
}},
{ $sort: { total: -1 } }
],
// Facette 2 : Par ville
"parVille": [
{ $group: {
_id: "$store.city",
total: { $sum: "$amount" },
count: { $sum: 1 }
}},
{ $sort: { total: -1 } },
{ $limit: 5 }
],
// Facette 3 : Statistiques globales
"stats": [
{ $group: {
_id: null,
totalVentes: { $sum: "$amount" },
moyenneVente: { $avg: "$amount" },
nombreVentes: { $sum: 1 }
}}
],
// Facette 4 : Ăvolution temporelle
"evolution": [
{ $group: {
_id: { $dayOfMonth: "$date" },
ventes: { $sum: "$amount" }
}},
{ $sort: { "_id": 1 } }
]
}}
])
{
"parCategorie": [
{ "_id": "Ălectronique", "total": 125000 },
{ "_id": "VĂȘtements", "total": 98000 }
],
"parVille": [
{ "_id": "Casablanca", "total": 89000, "count": 234 },
{ "_id": "Rabat", "total": 67000, "count": 189 }
],
"stats": [{
"totalVentes": 450000,
"moyenneVente": 850,
"nombreVentes": 529
}],
"evolution": [
{ "_id": 1, "ventes": 15000 },
{ "_id": 2, "ventes": 18500 }
]
}
$year
Année
$month
Mois (1-12)
$dayOfMonth
Jour du mois
$hour
Heure
$dayOfWeek
Jour semaine (1-7)
$week
Semaine année
db.orders.aggregate([
// Extraire les composants de date
{ $project: {
amount: 1,
year: { $year: "$createdAt" },
month: { $month: "$createdAt" },
dayOfWeek: { $dayOfWeek: "$createdAt" },
hour: { $hour: "$createdAt" }
}},
// Analyser par jour de la semaine
{ $group: {
_id: "$dayOfWeek",
totalSales: { $sum: "$amount" },
avgSale: { $avg: "$amount" },
count: { $sum: 1 }
}},
// Ajouter le nom du jour
{ $project: {
dayName: {
$switch: {
branches: [
{ case: { $eq: ["$_id", 1] }, then: "Dimanche" },
{ case: { $eq: ["$_id", 2] }, then: "Lundi" },
{ case: { $eq: ["$_id", 3] }, then: "Mardi" },
{ case: { $eq: ["$_id", 4] }, then: "Mercredi" },
{ case: { $eq: ["$_id", 5] }, then: "Jeudi" },
{ case: { $eq: ["$_id", 6] }, then: "Vendredi" },
{ case: { $eq: ["$_id", 7] }, then: "Samedi" }
]
}
},
totalSales: { $round: ["$totalSales", 2] },
avgSale: { $round: ["$avgSale", 2] },
count: 1
}},
{ $sort: { "_id": 1 } }
])
// Pipeline complet d'analyse
db.orders.aggregate([
// 1. Filtrer le mois en cours
{ $match: {
status: { $in: ["completed", "shipped"] },
date: {
$gte: ISODate("2024-03-01"),
$lt: ISODate("2024-04-01")
}
}},
// 2. Enrichir avec les données clients
{ $lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
}},
{ $unwind: "$customer" },
// 3. Décomposer les articles
{ $unwind: "$items" },
// 4. Enrichir avec les données produits
{ $lookup: {
from: "products",
localField: "items.productId",
foreignField: "_id",
as: "product"
}},
{ $unwind: "$product" },
// 5. Calculer les métriques
{ $group: {
_id: {
customerId: "$customerId",
customerCity: "$customer.address.city",
productCategory: "$product.category"
},
// Métriques client
totalSpent: {
$sum: { $multiply: ["$items.quantity", "$items.price"] }
},
itemsCount: { $sum: "$items.quantity" },
// Produits favoris
favoriteProducts: {
$push: {
name: "$product.name",
qty: "$items.quantity"
}
}
}},
// 6. Regrouper par ville pour le rapport final
{ $group: {
_id: "$_id.customerCity",
// KPIs par ville
totalRevenue: { $sum: "$totalSpent" },
avgBasket: { $avg: "$totalSpent" },
uniqueCustomers: { $sum: 1 },
// Top catégories par ville
categoriesBreakdown: {
$push: {
category: "$_id.productCategory",
revenue: "$totalSpent"
}
}
}},
// 7. Formater le résultat
{ $project: {
city: "$_id",
kpis: {
revenue: { $round: ["$totalRevenue", 2] },
avgBasket: { $round: ["$avgBasket", 2] },
customers: "$uniqueCustomers"
},
topCategories: {
$slice: [
{ $sortArray: {
input: "$categoriesBreakdown",
sortBy: { revenue: -1 }
}},
3
]
}
}},
{ $sort: { "kpis.revenue": -1 } }
])
11 / 14
db.orders.aggregate([
// 1. $match en premier (utilise les index)
{ $match: {
status: "completed",
amount: { $gte: 1000 }
}},
// 2. $project pour réduire les données
{ $project: {
customerId: 1,
amount: 1,
date: 1
}},
// 3. Puis les opérations coûteuses
{ $group: { ... }},
{ $sort: { ... }}
])
db.orders.aggregate([
// 1. Lookup sans filtre préalable
{ $lookup: { ... }},
// 2. Group sur tous les documents
{ $group: { ... }},
// 3. Match Ă la fin (trop tard!)
{ $match: {
totalAmount: { $gte: 1000 }
}}
])
// Options d'optimisation
db.orders.aggregate([
// Pipeline stages...
], {
allowDiskUse: true, // Utiliser le disque si nécessaire
cursor: { batchSize: 1000 }, // Batch processing
maxTimeMS: 30000 // Timeout de sécurité
})
db.dailySales.aggregate([
{ $sort: { date: 1 } },
{ $group: {
_id: null,
sales: { $push: { date: "$date", amount: "$amount" } }
}},
{ $unwind: { path: "$sales", includeArrayIndex: "index" } },
{ $sort: { "sales.date": 1 } },
{ $group: {
_id: "$sales.date",
amount: { $first: "$sales.amount" },
runningTotal: {
$sum: {
$cond: [
{ $lte: ["$sales.date", "$sales.date"] },
"$sales.amount",
0
]
}
}
}}
])
db.orders.aggregate([
{ $group: {
_id: null,
values: { $push: "$amount" }
}},
{ $project: {
min: { $min: "$values" },
max: { $max: "$values" },
avg: { $avg: "$values" },
median: {
$arrayElemAt: [
"$values",
{ $floor: { $divide: [{ $size: "$values" }, 2] } }
]
},
percentile90: {
$arrayElemAt: [
"$values",
{ $floor: {
$multiply: [{ $size: "$values" }, 0.9]
}}
]
}
}}
])
// Identifier les commandes inhabituelles
db.orders.aggregate([
// Calculer la moyenne et l'écart-type
{ $group: {
_id: null,
avgAmount: { $avg: "$amount" },
stdDev: { $stdDevPop: "$amount" },
orders: { $push: "$$ROOT" }
}},
{ $unwind: "$orders" },
// Détecter les anomalies (> 3 écarts-types)
{ $project: {
order: "$orders",
zscore: {
$divide: [
{ $subtract: ["$orders.amount", "$avgAmount"] },
"$stdDev"
]
}
}},
{ $match: {
$or: [
{ zscore: { $gt: 3 } },
{ zscore: { $lt: -3 } }
]
}}
])
13 / 14
Collection â [ Stage 1 ] â [ Stage 2 ] â [ Stage N ] â RĂ©sultat