Documents de cours 2020-2021 - FX Jollois
Il est bien évidemment possible de réaliser des calculs d’agrégats (de type somme, moyenne, minimum et maximum) dans MongoDB,
avec la fonction aggregate()
. Celle-ci permet beaucoup d’autres opérations.
Cette fonction va prendre en paramètre un tableau nommé pipeline
: tableau composé d’une suite d’opérations.
Chaque opération sera faite après la précédente. L’ordre des opérations a donc une importance cruciale. Et le même opérateur peut apparaître plusieurs fois.
Voici quelques unes des opérations possibles :
Fonction | Opération |
---|---|
$limit |
restriction à un petit nombre de documents (très utiles pour tester son calcul) |
$sort |
tri sur les documents |
$match |
restriction sur les documents à utiliser |
$unwind |
séparation d’un document en plusieurs sur la base d’un tableau |
$addFields |
ajout d’un champs dans les documents |
$project |
redéfinition des documents |
$group |
regroupements et calculs d’aggégrats |
$sortByCount |
agrégat + tri |
$lookup |
jointure avec une autre collection |
… |
pipeline
$limit
On indique juste avec un entier le nombre de document que l’on veut afficher.
db.restaurants.aggregate([
{ $limit: 10 }
])
Comme déjà vu dans le précédent TP, on peut ajouter la fonction pretty()
au résultat pour avoir un affichage plus clair.
db.restaurants.aggregate([
{ $limit: 10 }
]).pretty()
$sort
On indique de façon identique à celle du paramètre sort
de la fonction find()
db.restaurants.aggregate([
{ $limit: 10 },
{ $sort: { name: 1 }}
]).pretty()
$match
Ici, c’est identique à celle du paramètre query
des autres fonctions
db.restaurants.aggregate([
{ $limit: 10 },
{ $sort: { name: 1 }},
{ $match: { borough: "Brooklyn" }}
]).pretty()
db.restaurants.aggregate([
{ $match: { borough: "Brooklyn" }},
{ $limit: 10 },
{ $sort: { name: 1 }}
]).pretty()
$unwind
Le but de cette opération est d’exploser un tableau dans un document.
Un document avec un tableau à n éléments deviendra n documents avec chacun un des éléments du tableau en lieu et place de celui-ci
Nous devons mettre le nom du tableau servant de base pour le découpage (précédé d’un $
)
grades
)
db.restaurants.aggregate([
{ $limit: 10 },
{ $unwind: "$grades" }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $unwind: "$grades" },
{ $match: { "grades.grade": "B" }}
]).pretty()
$unwind
et $match
, le résultat est clairement différentdb.restaurants.aggregate([
{ $limit: 10 },
{ $match: { "grades.grade": "B" }},
{ $unwind: "$grades" }
]).pretty()
$addFields
et $project
On souhaite ici rédéfinir les documents en ajoutant des éléments ($addFields
) ou en se restreignant à certains éléments
($project
)
{ "champs" : 1 }
: conservation du champs (0 si suppression - idem que dans find()
, pas de mélange sauf pour _id
)
$project
{ "champs": { "$opérateur" : expression }}
: permet de définir un nouveau champs{ "nouveau_champs": "$ancien_champs" }
: renommage d’un champsQuelques opérateurs utiles pour la projection (plus d’info ici)
$arrayElemAt
: élément d’un tableau$first
et $last
: premier ou dernier élément du tableau$size
: taille d’un tableau$substr
: sous-chaîne de caractères$cond
: permet de faire une condition (genre de if then else)…
db.restaurants.aggregate([
{ $limit: 10 },
{ $addFields: { nb_grades: { $size: "$grades" } } }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $project: { name: 1, borough: 1 } }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $project: { address: 0, grades: 0 } }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $project: { name: 1, borough: 1 , street: "$address.street"} }
]).pretty()
grades
)db.restaurants.aggregate([
{ $limit: 10 },
{ $project: { name: 1, borough: 1, nb_grades: { $size: "$grades" } } }
]).pretty()
grades
est préent mais égal à NULL
)grades
db.restaurants.aggregate([
{ $project: { name: 1, borough: 1, nb_grades: { $size: "$grades" } } },
{ $sort: { nb_grades: 1 }},
{ $limit: 10 }
]).pretty()
grades
(indicé 0)db.restaurants.aggregate([
{ $limit: 10 },
{ $project: { name: 1, borough: 1, grade: { $arrayElemAt: [ "$grades", 0 ]} } }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $project: { nom: { $toUpper: "$name" }, borough: 1 } }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $project: {
nom: { $toUpper: "$name" },
quartier: { $substr: [ "$borough", 0, 3 ] }
} }
]).pretty()
db.restaurants.aggregate([
{ $limit: 10 },
{ $addFields: { quartier: { $toUpper: { $substr: [ "$borough", 0, 3 ] } } }},
{ $project: {
nom: { $toUpper: "$name" },
quartier: { $cond: { if: { $eq: ["$borough", "Bronx"] }, then: "BRX", else: "$quartier" } },
borough: 1
} }
]).pretty()
$group
Cet opérateur permet le calcul d’agrégats tel qu’on le connaît.
_id
: déclaration du critère de regroupement
$champs
: regroupement selon ce champs{ a1: "$champs1", ... }
: regroupement multiple (avec modification des valeurs possible)$sum
: somme (soit de valeur fixe - 1 pour faire un décompte donc, soit d’un champs spécifique)$avg, $min, $max
$addToSet
: regroupement des valeurs distinctes d’un champs dans un tableau$push
: aggrégation de champs dans un tableaudb.restaurants.aggregate([
{ $group: { _id: "Total", NbRestos: { $sum: 1 }}}
])
db.restaurants.aggregate([
{ $group: { _id: "$borough", NbRestos: { $sum: 1 }}}
])
db.restaurants.aggregate([
{ $match: { borough: "Queens" }},
{ $unwind: "$grades" },
{ $group: { _id: "null", score: { $avg: "$grades.score" }}}
])
db.restaurants.aggregate([
{ $unwind: "$grades" },
{ $group: { _id: "$borough", score: { $avg: "$grades.score" }}},
{ $sort: { score: -1 }}
])
$match
permet de supprimer les restaurants sans évaluations (ce qui engendrerait des moyennes = NA
)db.restaurants.aggregate([
{ $project: {
borough: 1, street: "$address.street",
eval: { $arrayElemAt: [ "$grades", 0 ]}
} },
{ $match: { eval: { $exists: true } } },
{ $match: { "eval.score": { $gte: 0 } } },
{ $group: {
_id: { quartier: "$borough", rue: "$street" },
score: { $avg: "$eval.score" }
}},
{ $sort: { score: 1 }},
{ $limit: 10 }
])
$addToSet
et $push
, on les applique sur les grades obtenus pour les 10 premiers restaurants
$addToSet
: valeurs distinctes$push
: toutes les valeurs présentesdb.restaurants.aggregate([
{ $limit: 10 },
{ $unwind: "$grades" },
{ $group: {
_id: "$name",
avec_addToSet: { $addToSet: "$grades.grade" },
avec_push: { $push: "$grades.grade" }
}}
])
$sortBycount
Cet opérateur réalise un regroupement sur le champs spécifié (précédé d’un $
), compte le nombre de document pour chaque
modalité de ce champs, puis fait un tri décroissant sur le nombre calculé. Il est clairement fait pour réaliser des TOPs donc.
db.restaurants.aggregate([
{ $sortByCount: "$borough" }
])
db.restaurants.aggregate([
{ $group: { _id: "$borough", count: { $sum: 1 } } },
{ $sort: { count: -1 }}
])
addToSet
)grades
addToSet
, et restriction à ceux pour lequel le tableau créé = [“A”] - par exemple)push
slice
pour prendre une partie d’un tableau