Documents de cours - FX Jollois
This project is maintained by fxjollois
Dans cette introduction, nous allons aborder l’utilisation de MongoDB, via l’interrogation de données dans le shell
. MongoDB utilise le langage JavaScript
.
MongoDB est une base de données NoSQL distribué de type Document Store (site web)
Objectifs :
Principe de base : les données sont des documents
BSON
)collections
JSON
JavaScript Object Notation
{}
{ "nom": "jollois", "prenom": "fx" }
[]
[ 1, 5, 10]
string
et number
) et trois constantes (true
, false
, null
)Validation possible du JSON sur jsonlint.com/
{
"tubd": {
"formation": "DU Analyste Big Data",
"responsable": { "nom": "Poggi", "prenom": "JM" },
"etudiants" : [
{ "id": 1, "nom": "jollois", "prenom": "fx" },
{ "id": 2, "nom": "aristote", "details": "délégué" },
{ "id": 5, "nom": "platon" }
],
"ouverte": true
},
"tudv": {
"formation": "DU Data Visualisation",
"ouverte": false,
"todo": [
"Creation de la maquette",
"Validation par le conseil"
],
"responsable": { "nom": "Métivier" }
}
}
BSON
: extension de JSON
Schéma dynamique
ALTER TABLE
ou de redesign de la basePas de jointures entre les collections
JS
)Pour pouvoir accéder au shell
de Mongo, il faut se connecter en SSH au serveur distant. Voici la commande à réaliser dans un terminal de commande. Le login
est à remplacer par le vôtre, fourni en début de cours.
ssh login@193.51.82.104 -p2342
Pour lancer le shell
de Mongo, il suffit d’exécuter la commande suivante dans le terminal de commande.
mongo
Vous devriez voir apparaître des avertissements diverses. Il ne faut pas en tenir compte.
Une fois connecté au shell
, il est possible de connaître l’ensemble des bases de données présentes sur le serveur. Pour ceci, vous devez utiliser la commande show dbs
comme ci-dessous.
show dbs
Pour choisir la base sur laquelle vous voulez travailler, il faut la sélectionner à l’aide de la commande use db
(db
étant à remplacé par le nom de la base de données choisie - ici test
).
use test
Une base de données est constitué d’une ou plusieurs collections. Chacune de celles-ci contient un ensemble de documents. Pour lister celles-ci, on utilise la commande show collections
.
show collections
Vous devriez avoir une liste à deux éléments : restaurants
et test
.
Dans MongoDB, comme nous le verrons par la suite, nous utilisons un formalisme de type db.collection.fonction()
:
db
représente la base de données choisie grâce à la commande use
(ce mot clé est non modifiable)collection
représente la colletion dans laquelle nous allons effectuer l’opération, et doit donc correspondre à une des collections présentes dans la basefonction()
détermine l’opération à effectuer sur la collection.En premier lieu, on peut dénombrer le nombre de documents de chaque collection, grâce à la fonction count()
.
db.restaurants.count()
Les documents présents dans une collection n’ont pas de schémas prédéfinis. Si nous souhaitons avoir une idée de ce que contient la collection, il est possible d’afficher un document (le premier trouvé), avec findOne()
. Cette opération permet de comprendre la structure global d’un document, même s’il peut y avoir des différences entre documents.
db.restaurants.findOne()
Il est possible d’inclure des critères de sélection dans cette fonction, que nous verrons dans la suite. De même pour la sélection des items à afficher.
Une autre fonction très utile pour mieux appréhender les données est de lister les valeurs prises par les différents items de la collection, grâce à distinct()
. Pour spécifier un sous-item d’un item, il est nécessaire d’utiliser le formalisme item.sousitem
.
db.restaurants.distinct("borough")
db.restaurants.distinct("cuisine")
db.restaurants.distinct("address.zipcode")
db.restaurants.distinct("grades.grade")
Pour faire des recherches, il existe la fonction find()
. Sans paramètre, elle renvoie l’ensemble des documents. Il faut donc l’utiliser avec précautions. Mais celle-ci peut aussi prendre deux paramètres :
Ces deux paramètres doivent être écrits sous la forme d’objets JSON
.
Dans ce premier exemple, on cherche le restaurant s’appelant "La Grenouille"
.
db.restaurants.find({ name: "La Grenouille" })
L’affichage rendu par find()
est compact et peu lisible directement. On peut aérer ce rendu en ajoutant la fonction pretty()
pour avoir une présentation propre.
db.restaurants.find({ name: "La Grenouille" }).pretty()
Si l’on désire n’afficher que certains éléments, il est possible d’ajouter un deuxième argument spécifiant les items que l’on veut (avec 1
) ou qu’on ne veut pas (avec 0
).
db.restaurants.find({ cuisine: "French" }, { name: 1 })
Par défaut, l’identifiant du document, toujours nommé _id
, est renvoyé. Pour ne pas l’avoir, il faut ainsi le préciser avec "_id": 0
.
db.restaurants.find({ cuisine: "French" }, { _id: 0, name: 1 })
Les documents peuvent être complexes (c’est même le but), et les critères portent donc souvent sur des sous-items. Il faut utiliser le même formalisme déjà vu ("item.sousitem"
). Il faut noter deux choses :
.
;db.restaurants.find({ "grades.grade" : "A" }, { _id: 0, name: 1 })
Ce formalisme est le même pour indiquer un sous-item à afficher. Nous ajoutons à la requête précédent l’affichage des scores. Nous voyons que le résultat inclu tous les scores.
db.restaurants.find({ "grades.grade" : "A" }, { _id: 0, name: 1, "grades.grade" : 1 })
Le test d’égalité est aussi réalisable avec une variable numérique.
db.restaurants.find({ "grades.score": 90 }, { _id: 0, name: 1 })
Pour les comparaisons, nous disposons des opérateurs $eq
(equal), $gt
(greater than), $gte
(greater than or equal), $lt
(less than), $lte
(less than or equal) et $ne
(not equal).
db.restaurants.find({ "grades.score": { $gte: 90 } }, { _id: 0, name: 1 })
En plus de ces comparaisons simples, nous disposons d’opérateurs de comparaisons à une liste : $in
(présent dans la liste) et $nin
(non présent dans la liste).
db.restaurants.find({ "grades.score": { $in: [ 90, 98 ] } }, { _id: 0, name: 1 })
Par défaut, si on ajoute des critères de restriction dans le premier paramètre, la recherche se fait avec un ET entre les critères.
db.restaurants.find({ "grades.grade" : "A", cuisine : "French" }, { _id: 0, name: 1 })
Mais si on veut faire des combinaisons autres, il existe des opérateurs logiques : $and
, $or
et $nor
. Ces trois opérations prennent un tableau de critères comme valeur.
db.restaurants.find({ "$or": [ { "grades.grade" : "Z" }, { cuisine : "French" } ] },
{ _id: 0, name: 1, cuisine: 1, "grades.grade": 1 })
Comme précédemment indiqué, il est courant qu’un document ne contienne pas tous les items possibles. Si l’on cherche à tester la présence ou non d’un item, on utilise l’opérateur $exists
(avec true
si on teste la présence, et false
l’absence).
db.restaurants.find({ french : { "$exists" : true } }, { _id: 0, name: 1 })
Il est souvent nécessaire de faire des dénombrements suite à des sélections, pour faire des vérifications de code ou des estimations de charge (ou autre). La fonction count()
peut ainsi s’ajouter à la suite d’une fonction find()
pour connaître la taille du résultat.
db.restaurants.find({ cuisine : "French" }).count()
On peut aussi limiter le nombre de documents renvoyés par la fonction find()
en lui ajoutant la fonction limit()
, comme ici où nous nous restreignons aux 5 premiers résultats.
db.restaurants.find({ cuisine : "French" }, { _id: 0, name: 1 }).limit(5)
Une autre opération classique est le tri des résultats, réalisable avec la fonction sort()
. On doit indiquer les items de tri et leur attribuer une valeur de 1
pour un tri ascendant et une valeur de -1
pour un tri descendant.
db.restaurants.find({ cuisine : "French" }, { _id: 0, name: 1 }).sort({ name: 1 })
Idem que précédemment, mais dans l’ordre décroissant.
db.restaurants.find({ cuisine : "French" }, { _id: 0, name: 1 }).sort({ name: -1 })
Bien évidemment, il est possible de mettre plusieurs critères de tri, en indiquant croissant ou décroissant pour chaque item de tri.
db.restaurants.find({ cuisine : "French" }, { _id: 0, name: 1, borough: 1 }).sort({ borough: -1, name: 1 })
Enfin, il est possible d’écrire les commandes sur plusieurs lignes, avec des indentations, afin de rendre les commandes plus lisibles, tel que ci-dessous.
db.restaurants.find(
{ cuisine : "French" },
{ _id: 0, name: 1, borough: 1 }
).sort(
{ borough: -1, name: 1 }
)
En plus des recherches classiques d’informations, le calcul d’agrégat est très utilisé, pour l’analyse, la modélisation ou la visualisation de données. Ce calcul s’effectue avec la fonction aggregate()
. Celle-ci prend en paramètre un tableau d’opérations (appelé aussi pipeline
), pouvant contenir les éléments suivants :
$project
: redéfinition des documents (si nécessaire)$match
: restriction sur les documents à utiliser$group
: regroupements et calculs à effectuer$sort
: tri sur les agrégats$unwind
: découpage de tableauxVoici un premier exemple permettant un calcul pour toute la base. Ici, nous réalisons un dénombrement (il fait la somme de la valeur 1 pour chaque document).
db.restaurants.aggregate([
{ $group: { _id: null, nb: { $sum: 1 }}}
])
Les calculs peuvent être plus complexes, comme nous le verrons plus tard. Bien évidemment, ces calculs d’agrégats peuvent se faire aussi en ajoutant des critères de regroupement. Attention au $
avant le nom de l’item.
db.restaurants.aggregate([
{ $group: { _id: "$borough", nb: { $sum: 1 }}}
])
Ce résultat peut être trié en ajoutant l’action $sort
dans le tableau, avec le même mécanismes que précédemment (1 : ascendant, -1 : descendant).
db.restaurants.aggregate([
{ $group: { _id: "$borough", nb: { $sum: 1 }}},
{ $sort: { "nb": -1 }}
])
Il est parfois intéressant (voire nécessaire) de redéfinir les documents, pour ne garder que les items qui nous intéressent. Dans cette projection, il existe différentes fonctions de traitement comme la concaténation (cf ci-dessous).
L’action $limit
permet de limiter le nombre de sorties renvoyées par le moteur.
db.restaurants.aggregate([
{ $project: { _id: 0, name: 1, info: { $concat: [ "$cuisine", " - ", "$borough" ]} }},
{ $limit: 5 }
]).pretty()
On peut aussi faire une restriction avant le calcul, avec l’opération $match
.
db.restaurants.aggregate([
{ $match: { cuisine: "French" }},
{ $group: { _id: "$borough", nb: { $sum: 1 }}}
])
Imaginons maintenant que nous souhaitons calculer le nombre de restaurant par grade. La première idée serait de réaliser l’opération suivante.
db.restaurants.aggregate([
{ $group: { _id: "$grades.grade", nb: { $sum: 1 }}}
])
Malheureusement, nous voyons que le regroupement se fait par ensemble de grades existant dans la base. Il faut donc faire un découpage des tableaux grades
dans chaque document. Pour cela, il existe l’opération $unwind
.
Pour montrer comment fonctionne cette opération, voici les 5 premières lignes renvoyées lorsqu’on l’applique directement sur les données. On s’apercoit que chaque document ne contient plus qu’une seule évaluation.
db.restaurants.aggregate([
{ $unwind: "$grades" },
{ $limit: 5 }
]).pretty()
Par ce biais, nous pouvons donc maintenant faire l’opération de regroupement par grade.
db.restaurants.aggregate([
{ $unwind: "$grades" },
{ $group: { _id: "$grades.grade", nb: { $sum: 1 }}}
])
Comme indiqué précédemment, on peut faire tous les calculs d’agrégats classiques, comme ici avec la somme ($sum
), la moyenne ($avg
), le minimum ($min
) et le maximum ($max
).
db.restaurants.aggregate([
{ $unwind: "$grades" },
{ $group: {
_id: null,
nb: { $sum: 1 },
scoreTot: { $sum: "$grades.score" },
scoreMoy: { $avg: "$grades.score" },
scoreMin: { $min: "$grades.score" },
scoreMax: { $max: "$grades.score" }
}}
]).pretty()
Une fois que les documents sont scindés en plusieurs suite à l’action $unwind
, il peut être intéressant de regrouper les valeurs dans un tableau. Ici, nous cherchons les différents grades obtenus par chaque restaurant.
db.restaurants.aggregate([
{ $limit: 5 },
{ $unwind: "$grades" },
{ $group: { _id: "$name", nb: { $sum: 1 }, eval: { $addToSet: "$grades.grade" }}}
])
Il existe de nombreuses autres opérateurs disponibles dans Mongo. L’aide en ligne est assez bien fournie en exemple et la communauté est grandissante, permettant d’avoir aussi un grand nombre de réponses sur des forums comme Stack Overflow.
Répondre aux questions suivantes