cours-2020-2021

Documents de cours 2020-2021 - FX Jollois

View the Project on GitHub fxjollois/cours-2020-2021

TP2 : Agrégats dans Mongo

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.

Fonctions possibles

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
 

Syntaxe des opérations dans le 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 $)

db.restaurants.aggregate([
    { $limit: 10 },
    { $unwind: "$grades" }
]).pretty()
db.restaurants.aggregate([
    { $limit: 10 },
    { $unwind: "$grades" },
    { $match: { "grades.grade": "B" }}
]).pretty()
db.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)

Quelques opérateurs utiles pour la projection (plus d’info ici)

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()
db.restaurants.aggregate([
    { $limit: 10 },
    { $project: { name: 1, borough: 1, nb_grades: { $size: "$grades" } } }
]).pretty()
db.restaurants.aggregate([
    { $project: { name: 1, borough: 1, nb_grades: { $size: "$grades" } } },
    { $sort: { nb_grades: 1 }},
    { $limit: 10 }
]).pretty()
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.

db.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 }}
])
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 }
])
db.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 }}
])

A faire

  1. Quelles sont les 10 plus grandes chaines de restaurants (nom identique) ?
    • TOP 10 classique (2 façons de faire donc)
  2. Donner le Top 5 et le Flop 5 des types de cuisine, en terme de nombre de restaurants
    • idem, avec le tri qui change entre les 2 demandes
  3. Quelles sont les 10 rues avec le plus de restaurants ?
    • TOP 10 aussi
  4. Quelles sont les rues situées sur strictement plus de 2 quartiers ?
    • Essayez d’ajouter le nom des quartiers de chaque rue (cf addToSet)
  5. Lister par quartier le nombre de restaurants et le score moyen
    • Attention à bien découper le tableau grades
  6. Donner les dates de début et de fin des évaluations
    • min et max sont dans un bateau
  7. Quels sont les 10 restaurants (nom, quartier, addresse et score) avec le plus petit score moyen ?
    • découpage, regroupement par restaurant, tri et limite
  8. Quels sont les restaurants (nom, quartier et addresse) avec uniquement des grades “A” ?
    • restriction à ceux qui ont A, découpage, suppression des autres grades que “A” et affichage des infos
    • on peut envisager d’autres choses (découpage, addToSet, et restriction à ceux pour lequel le tableau créé = [“A”] - par exemple)
  9. Compter le nombre d’évaluation par jour de la semaine
    • petite recherche sur l’extraction du jour de la semaine à partir d’une date à faire
  10. Donner les 3 types de cuisine les plus présents par quartier
    • simple à dire, compliqué à faire
    • une piste
      1. double regroupement à prévoir
      2. tri à prévoir
      3. regroupement avec push
      4. slice pour prendre une partie d’un tableau