TD-TP 3 Correction
TD-TP 3 Correction
TD-TP 3 Correction
NOSQL - MONGODB
3. Interrogez cette collection afin de voir à quoi ressemble un document de la collection restaurants. Utilisez
la méthode "findOne()".
TP3 : Les bases de données NoSQL MongoDB
En effet cette base regroupe des informations sur les restaurants de la ville de New York. Chaque restaurant a
un nom, un quartier (borough), le type de cuisine, une adresse (document imbriqué, avec coordonnées GPS,
rue et code postale), et un ensemble de notes (résultats des inspections).
4. Cherchez parmi les restaurants dans le quartier de Manhattan qui sont spécialisés dans les fruits de mer
« Seafood »
2
TP3 : Les bases de données NoSQL MongoDB
5. Si l'on souhaite ne récupérer les restaurants de cuisine « Italian » dont le nom contient Pizza, qui ont des
scores inférieurs à 10 et qui n'ont pas de score supérieur à 10, il faut alors combiner la première opération
avec une négation de la condition " ≥10 ". La condition est alors vérifiée sur chaque élément de la liste.
6. On pourrait même corser la chose en cherchant les restaurants qui ont un grade ‘C’ avec un score
inférieur à 30.
>db.resto.find({"borough" : "Manhattan","grades.grade" :"C", "grades.score" : {$lt :
30}}, {_id : 0,"name" : 1, "grades.grade" : 1, "grades.score" : 1}).pretty()
Le résultat est pour le moins étrange concernant le premier document car il y a bien un grade C, mais avec un
score de 56 ! Si l’on regarde la requête de plus près, ce n’est pas étonnant. Y a-t-il un grade ‘C’ ? oui. Y a-t-il
un score inférieur à 40 ? oui… Nous avons oublié de préciser qu’il fallait que ce soit vérifié sur chaque
élément !
La vérification se fait sur l’intérieur de la liste, pas sur chaque élément de la liste. Pour cela, un opérateur
permet de faire une vérification instance par instance : $elemMatch.
3
TP3 : Les bases de données NoSQL MongoDB
Si nous voulons chercher les noms et quartiers des restaurants dont la dernière inspection (la plus récente,
donc la première de la liste) a donné un grade ‘C’. Il faut donc chercher dans le premier élément de la liste.
Pour cela il est possible de rajouter l’indice recherché (indice 0) dans la clé.
3. DISTINCT
Quels sont les différentes spécialités des restaurants de la ville de New York ?
Quels sont les différents grades de notation des restaurants ?
…
Pour répondre à ces requêtes, on peut utiliser la fonction "distinct()" avec la clé recherchée pour avoir une liste
de valeur :
4
TP3 : Les bases de données NoSQL MongoDB
• {$match : {} } : C’est le plus simple, il correspond au premier paramètre de la requête find que nous
avons fait jusqu’ici. Il permet donc de filtrer le contenu d’une collection.
• {$project : {} } : C’est le second paramètre du find. Il donne le format de sortie des documents (projec-
tion). Il peut par ailleurs être utilisé pour changer le format d’origine.
• {$sort : {} } : Trier le résultat final sur les valeurs d’une clé choisi.
• {$group : {} } : C’est l’opération d’agrégation. Il va permettre de grouper les documents par valeur, et ap-
pliquer des fonctions d’agrégat. La sortie est une nouvelle collection avec les résultats de l’agrégation.
• {$unwind : {} } : Cet opérateur prend une liste de valeur et produit pour chaque élément de la liste un
nouveau document en sortie avec cet élément. Il pourrait correspondre à une jointure, à ceci près que
celle-ci ne filtre pas les données d’origine, juste un complément qui est imbriqué dans le document. On
pourrait le comparer à une jointure externe avec imbrication et listes
La liste complète de ces operateurs vous la retrouverez dans le lien de la documentation officielle
https://docs.mongodb.com/manual/reference/operator/aggregation/
1. Equivalent à find avec aggregate
Vous aurez remarqué que chaque opérateur est imbriqué dans un document, et la valeur est elle-même un autre
document. C’est la structure imposée pour la définition de l’opérateur. Pour illustrer un aggregate, nous allons
reprendre notre dernière requête find :
> db.resto.find({ "grades.0.grade":"C" }, {"name":1, "borough":1, "_id":0} )
Cette requête peut être formulé avec la commande aggregate en utilisant les deux operateurs $match et $project
de la manière suivante :
> db.resto.aggregate({$match : {"grades.0.grade" : "C"}},{$project : {"name" :
1, "borough" : 1, "_id" : 0}})
5
TP3 : Les bases de données NoSQL MongoDB
Nous utiliserons ces variables par la suite pour alléger le code en ré-utilisant des variables.
3. Le trie
A la suite de cette dernière requête Trions maintenant le résultat par nom de restaurant par ordre croissant
alphabétiquement (croissant : 1, décroissant : -1) :
Nous allons créer une variable qui représente l’opérateur de tri
> varTrie = { $sort : {"name" : 1}};
Et en suite nous l’injectons dans la liste des variables dans aggregate :
> db.resto.aggregate( [ varMatch, varProject, varTrie] );
6
TP3 : Les bases de données NoSQL MongoDB
4. Groupement simple
Comptons maintenant le nombre de ces restaurants (premier rang ayant pour valeur C). Pour cela, il faut définir
un opérateur $group. Celui-ci doit contenir obligatoirement une clé de groupement (_id), puis une clé (total) à
laquelle on associe la fonction d'agrégation ($sum) :
>varGroupe = { $group : {"_id" : null, "total" : {$sum : 1} } };
>db.restaurants.aggregate( [ varMatch, varGroupe ] );
Ici, pas de valeur de groupement demandé (on compte simplement). Nous avons choisi de mettre la valeur null, on
aurait pu mettre "toto", cela fonctionne également. La clé "total" est créée dans le résultat et va effectuer la
"somme des 1" pour chaque document source. Faire une somme de 1 est équivalent à compter le nombre
d’éléments.
En effet cette requête est équivalente à :
>db.restaurants.count({"grades.0.grade":"C"})
ou
>db.restaurants.find({"grades.0.grade":"C"}).count()
5. Groupement par valeur
Comptons maintenant par quartier le nombre de ces restaurants (qui toujours vérifient le filtre varMatch). Il faut
dans ce cas changer la clé de groupement en y mettant la valeur de la clé "borough". Mais si l’on essaye ceci :
>varGroupe2 = { $group : {"_id" : "borough", "total" : {$sum : 1} } };
>db.resto.aggregate( [ varMatch, varGroupe2 ] );
Nous constatons que “borough” est interprété comme étant un texte statique et pas comme un champ de
document. Pour prendre la valeur du champ, il faut préfixer la clé par un dollar "$borough". Lorsque la clé
d’agrégation "_id" est associé a "$borough" c’est ce champ qui va être utilisé pour le regroupement, voir résultats
> varGroupe3 = { $group : {"_id" : "$borough", "total" : {$sum : 1} } };
> db.resto.aggregate( [ varMatch, varGroupe3 ] );
7
TP3 : Les bases de données NoSQL MongoDB
Nous pourrons utilisées le même principe pour le regroupement par rapport à la spécialité de cuisine.
6. Unwind
Maintenant, trouvons le score moyen des restaurants par quartiers, et trions par score décroissant. Pour cela, nous
groupons les valeurs par quartier ($borough), nous utilisons la fonction d’agrégat "$avg" pour la moyenne
(average), puis nous trions sur la nouvelle clé produite "$moyenne".
Un problème se pose comment calculer la moyenne sur une "grades.score" qui est une liste ? Il faudrait au
préalable retirer chaque élément de la liste, puis faire la moyenne sur l’ensemble des valeurs. C’est l’opérateur
$unwind qui s’en charge, il suffit de lui donner la clé contenant la liste à désimbriquer.
8
TP3 : Les bases de données NoSQL MongoDB
db.restaurants.aggregate( [ varUnwind, varGroup4, varSort2 ] );
Ce qu'il est important de noter dans cette fonction aggregate, c’est que les opérateurs sont composés en séquences
: chaque opérateur prend la collection produite par l’opérateur précédent (et non la collection de départ). Ceci
permet de créer de longues chaînes d’opérateurs pour faire des calculs lourds.
Attention ! L’ordre des opérations est très important (projection après les filtrages, tri
sur le résultat d’un groupe, filtrage avant ou après unwind/group…).