8. MongoDB - Travaux Pratiques

Les exercices qui suivent sont à effectuer sur machine, avec MongoDB.

Supports complémentaires:

Manipulation de base

Comme vu en cours, il s’agit de créer une base, une collection, d’y insérer des données et de l’interroger. Les documents fournis correspondent à un extrait d’une base de publications scientifiques, The DBLP Computer Science Bibliography.

Gestion de collection

Voici le résumé des commandes à effectuer.

  • Se connecter à la base DBLP : use DBLP;

  • Créer une collection « publis » : db.createCollection('publis');

  • Créer le document suivant (astuce pour le client mongo: le mettre sur une ligne; c’est plus facile avec un client graphique type RoboMongo):

    {
      "type": "Book",
      "title": "Modern Database Systems: The Object Model, Interoperability, and Beyond.",
      "year": 1995,
      "publisher": "ACM Press and Addison-Wesley",
      "authors": ["Won Kim"],
      "source": "DBLP"
    }
    
  • Insérer le document dans la collection publis avec db.publis.save(...);

  • Créer et insérer deux autres publications à partir de cette page de conférence type « Article » (Vue « BibTeX ») :

    http://www.informatik.uni-trier.de/~ley/db/journals/vldb/vldb23.html

  • Consulter le contenu de la collection : db.publis.find();

  • Importer les données du TP dans MongoDB :

    1. Télécharger le fichier contenant les données : DBLP.json.zip

    2. Décompresser le fichier dblp.json.zip

    3. Dans le même répertoire, lancer l’importation du fichier :

      mongoimport --host localhost:27017 --db DBLP --collection publis < dblp.json
      

Note

Le chemin vers l’exécutable mongoimport est nécessaire, ou la variable d’environnement PATH contenant le chemin vers mongo/bin. L’opération peut prendre quelques secondes (118000 items à insérer)

  • Dans la console mongo vérifier que les données ont été insérées : db.publis.count();

Interrogation simple

Exprimez des requêtes simples (pas de MapReduce) pour les recherches suivantes :

  1. Liste de tous les livres (type « Book ») ;
  2. Liste des publications depuis 2011 ;
  3. Liste des livres depuis 2014 ;
  4. Liste des publications de l’auteur « Toru Ishida » ;
  5. Liste de tous les éditeurs (type « publisher »), distincts ;
  6. Liste de tous les auteurs distincts ;
  7. Trier les publications de « Toru Ishida » par titre de livre et par page de début ;
  8. Projeter le résultat sur le titre de la publication, et les pages ;
  9. Compter le nombre de ses publications ;
  10. Compter le nombre de publications depuis 2011 et par type ;
  11. Compter le nombre de publications par auteur et trier le résultat par ordre croissant ;

Correction

  1. db.publis.find({"type" : "Book"});
  2. db.publis.find({year : {$gte : 2011}});
  3. db.publis.find({"type" : "Book", year : {$gte : 2014}});
  4. db.publis.find({authors : "Toru Ishida"});
  5. db.publis.distinct("publisher");
  6. db.publis.distinct("authors");
  7. db.publis.aggregate([{$match:{authors : "Toru Ishida"}}, { $sort : { booktitle : 1, "pages.start" : 1 } }]);
  8. db.publis.aggregate([{$match:{authors : "Toru Ishida"}}, {$sort : { booktitle : 1, "pages.start" : 1 }}, {$project : {title : 1, pages : 1}}]);
  9. db.publis.aggregate([{$match:{authors : "Toru Ishida"}}, {$group:{_id:null, total : { $sum : 1}}}]);
  10. db.publis.aggregate([{$match:{year : {$gte : 2011}}}, {$group:{_id:"$type", total : { $sum : 1}}}]);
  11. db.publis.aggregate([{ $unwind : "$authors" }, { $group : { _id : "$authors", number : { $sum : 1 } }}, {$sort : {number : -1}}] );

Indexation (optionnel)

Cette partie n’est (peut-être) pas vue en cours. Elle correspond à la découverte de l’indexation et de l’exécution de requêtes avec MongoDB: à vous d’explorer la documentation si vous voulez vous lancer.

  • Pour chaque requête de type find(), regarder le plan d’exécution généré avec .explain() à la fin de la requête ;
  • Créer un index sur l’attribut année db.publis.createIndex( { year :1 } ); ;
  • Refaire les requêtes find() sur l’année en regardant le plan d’exécution généré ;

Note

Il est également possible d’avoir le plan d’exécution sur les requêtes de type Map/Reduce (option $explain dans queryParam, voir plus bas).

Pratique de Map/Reduce

Quelques rappels

Pour exprimer une requête MapReduce, il faut définir une fonction de map, une fonction de reduce, un filtre (optionnel), lancer le tout avec l’opérateur mapReduce et consulter le résultat (optionnel!). Soit:

map var mapFunction = function () {emit(this.year, 1);};
reduce var reduceFunction = function (key, values) {return Array.sum(values);};
filtre var queryParam = {query : {}, out : "result_set"}
requête db.publis.mapReduce(mapFunction, reduceFunction, queryParam);
résultat db.result_set.find();

Mappez et réducez

Pour les recherches suivantes, donnez la requête MapReduce sur la base en changeant la requête Map et/ou Reduce

  1. Pour chaque document de type livre, émettre le document avec pour clé « title »
  2. Pour chacun de ses livres, donner le nombre de ses auteurs
  3. Pour chaque document ayant « booktitle » (chapitre) publié par Springer, donner le nombre de ses chapitres.

Attention

la fonction de reduce n’est évaluée que lorsqu’il y a au moins 2 documents pour une même clé. Il est donc nécessaire d’appliquer un filtre après génération du résultat.

  1. Pour chaque éditeur « Springer », donner le nombre de publication par année
  2. Pour chaque clé « publisher & année » (pour ceux qui ont un publisher), donner le nombre de publications

Important

la clé du emit() doit être un document.

  1. Pour l’auteur « Toru Ishida », donner le nombre de publication par année
  2. Pour l’auteur « Toru Ishida », donner le nombre moyen de pages pour ses articles (type Article)
  3. Pour chaque auteur, donner le titre de ses publications

Attention

la sortie du map et du reduce doit être un document (pas un tableau)

  1. Pour chaque auteur, donner le nombre de publications associé à chaque année
  2. Pour l’éditeur « Springer », donner le nombre d’auteurs par année
  3. Compter les publications de plus de 3 auteurs
  4. Donner pour chaque publieur, donner le nombre moyen de pages par publication
  5. Pour chaque auteur, donner le minimum et le maximum des années, ainsi que le nombre de publication totale

Correction

  1. var mapFunction = function () {
      if (this.type == "Book")
         emit(this.title, this);
     };
    
     var reduceFunction = function (key, values) {
        return {articles : values};
      };
    
     var queryParam = {query:{}, out:"result_set"};
    

    ou

    var mapFunction = function () {emit(this.title, this);};
    
    var reduceFunction = function (key, values) {
      return {articles : values};
    };
    
    var queryParam = {query:{type : "Book"}, out:"result_set"};
    

    Note

    Peut être plus efficace si un index est présent sur le type

  2. var mapFunction = function () {
      if(this.type == "Book")
        emit(this.title, this.authors.length);
      };
    
     var reduceFunction = function (key, values) {
       return {articles : values};
     };
    
     var queryParam = {query:{}, out:"result_set"};
    
  3. var mapFunction = function () {
       if(this.publisher=="Springer" && this.booktitle)
             emit(this.booktitle, 1);
           };
    
    var reduceFunction = function (key, values) {
       return Array.sum(values);
     };
    
    var queryParam = {query:{}, out:"result_set"};
    
    db.publis.mapReduce(mapFunction,
       reduceFunction, queryParam);
    
    db.result_set.find({value:{$gte:2}});
    
  4.  var mapFunction = function () {
       if(this.publisher == "Springer")emit(this.year, 1);
     };
    
    var reduceFunction = function (key, values) {
      return Array.sum(values);
    };
    
  5. var mapFunction = function () {
      if(this.publisher)
          emit({publisher:this.publisher, year:this.year}, 1);
       };
    
    var reduceFunction = function (key, values) {
        return Array.sum(values);
     };
    
  6.    var mapFunction = function () {
         if(Array.contains(this.authors, "Toru Ishida"))
           emit(this.year, 1);
        };
    
       var reduceFunction = function (key, values) {
          return Array.sum(values);
       };
    
       var queryParam = {query:{}, out:"result_set"};
    
    ou
    
    .. code-block:: javascript
    
          var mapFunction = function () {emit(this.year, 1);};
    
          var reduceFunction = function (key, values) {
            return Array.sum(values);
          };
    
          var queryParam = {
             query:{authors:"Toru Ishida"}, out:"result_set"};
    
  7. var mapFunction = function () {
       emit(null, this.pages.end - this.pages.start);
    };
    
    var reduceFunction = function (key, values) {
      return Array.avg(values);
    };
    
    var queryParam = {query:{authors:"Toru Ishida"}, out:"result_set"};
    
  8. var mapFunction = function () {
     for(var i=0;i<this.authors.length;i++)
       emit(this.authors[i], this.title);};
    
    var reduceFunction = function (key, values) {
      return {titles : values};
    };
    
  9. var mapFunction = function () {
      for(var i=0;i<this.authors.length;i++)
         emit({author : this.authors[i], year : this.year}, 1);};
    
    var reduceFunction = function (key, values) {
      return Array.sum(values);
    };
    
  10. var mapFunction = function () {
      for(var i=0;i<this.authors.length;i++)
        emit(this.year, this.authors[i]);};
    
    var reduceFunction = function (key, values) {
      var distinct = 0;var authors = new Array();
    
      for(var i=0;i<values.length;i++)
        if(!Array.contains(authors, values[i]))
        {
             distinct++;
             authors[authors.length] = values[i];
         }
    
      return distinct;};
    
  11.  var mapFunction = function () {
       if(this.authors.length >3)emit(null, 1);
     };
    
    var reduceFunction = function (key, values) {
      return Array.sum(values);
    };
    
  12.  var mapFunction = function () {
      if(this.pages && this.pages.end)
        emit(this.publisher, this.pages.end - this.pages.start);
      };
    
     var reduceFunction = function (key, values) {
       return Array.avg(values);
     };
    
    var queryParam = {query:{}, out:"result_set"};
    
  13.  var mapFunction = function () {
       for(var i=0;i<this.authors.length;i++)
         emit(this.authors[i], {min : this.year,
                                max : this.year, number : 1});};
    
     var reduceFunction = function (key, values) {
    
       var v_min = 1000000;var v_max = 0;var v_number = 0;
    
       for(var i=0;i<values.length;i++){
        if(values[i].min < v_min)v_min = values[i].min;
        if(values[i].max > v_max)v_max = values[i].max;
        v_number++;
       }
      return {min:v_min, max:v_max, number:v_number};
    };
    

Bonus / Pour aller plus loin

Les exercices qui suivent impliquent des commandes non vues en cours, et nécessitent donc un peu de recherche de votre part. Si vous êtes motivés par MongoDB, allez-y !

Mises à jour

  • Modification : db.media.update ({"Title" : "Database"}, {$set:{Genre : "Science"}});

Note

Premier JSon : Mapping, Second JSon : Update ($set, $unset)

  • Suppression : db.media.remove({"Title" : "Database"})
  • Fonction itérative :
db.publis.find().forEach(
        function(pub){
        pub.pp = pub.pages;
        db.publis.save(pub);
});

Pour les mises à jour suivantes, vérifier le contenu des données avant et après la mise à jour.

  1. Mettre à jour tous les livres contenant « database » en ajoutant l’attribut « Genre » : « Database »
  2. Supprimer le champ « number » de tous articles
  3. Supprimer tous les articles n’ayant aucun auteur
  4. Modifier toutes les publications ayant des pages pour ajouter le champ « pp » avec pour valeur le motif suivant : pages.start--pages.end

Indexation 2Dsphere

Nous pouvons indexer les données avec 2DSphere qui permet de faire des recherches en 2 dimensions.

Documentation : http://docs.mongodb.org/manual/applications/geospatial-indexes/

Pour ce faire, télécharger le fichier cities15000.csv.zip. Décompressez le. Créer une collection cities dans la base de données, et importer les données à l’aide d’un programme de lecture CSV (à vous de le créer). Le schéma de sortie doit contenir pour les coordonnées les informations suivantes :

"coordinate" : [XXXXX, YYYYY]
L’attribut coordinate sera alors indexé :
db.publis.ensureIndex( { coordinate : "2d" } );

Pour interroger l’index, il faut utiliser un opérateur 2D et l’utiliser sur coordinate

Documentation : http://docs.mongodb.org/manual/tutorial/query-a-2d-index/

  1. Récupérer les coordonnées de la ville de Paris, Lyon et Bordeaux
  2. Trouver les villes autour de Paris dans un rayon de 100km (Opérateur $near).
  3. Calculer la somme des populations de cette zone.
  4. Trouver les villes comprises dans le triangle Paris-Lyon-Bordeaux (Opérateur $geoWithin)
  5. Vérifier s’il y a des villes qui ne sont pas en France dans ce résultat