MongoDB

huge + monstruous → humongous → mongo

Administration

  1. Installer MongoDB
  2. Créer l'utilisateur root (en mode anonyme)
  3. Désactiver le mode anonyme: option --auth
  4. Se loguer en root
  5. Créer une base puis un utilisateur associé
  6. [ Supprimer une base, revenir en 3. ]

Étape 0 : google it...

Étape 1 : voir aussi la doc

use admin
db.createUser({
	user: "admin",
	pwd: "password",
	roles: [ "root" ], //il y a plus subtil...
})

Étape 2 : /usr/bin/mongod --auth --config /etc/mongodb.conf

Étape 3 : mongo -u admin -p password admin ou

use admin
db.auth("admin","password")

après avoir lancé mongo.

Étape 4 :

use ma_base
db.createUser({
	user: "my_user",
	pwd: "password",
	roles: [{role: "readWrite", db: "ma_base"}]
})

[ Étape 5 : db.dropDatabase() ]

Gérer les collections ? Équivalent des tables SQL

// Ajoute un document *et* crée la collection
db.collection.insert({ ... })

// Pour plus de flexibilité :
db.createCollection(<name>, { capped: <boolean>,
	autoIndexId: <boolean>,
	size: <number>,
	max: <number>,
	storageEngine: <document>,
	validator: <document>,
	validationLevel: <string>,
	validationAction: <string>,
	indexOptionDefaults: <document>,
	viewOn: <string>,
	pipeline: <pipeline>,
	collation: <document> } )

Voir aussi la documentation

Suppression : db.collection.drop()

Différences avec SQLite

  • Orienté document (et donc a priori
    peu adapté aux données relationnelles)
  • Les collections ne sont pas typées : tout document peut être inséré.
  • Mongo n'est pas transactionnel : seules les opérations sur un document sont atomiques.
  • Gestion de plusieurs utilisateurs aux droits différents.
  • S'exécute dans un processus séparé de l'application.
  • Prévu pour traiter de très grosses bases de données (exemple réel)

Modification des collections

Renommer des champs :
db.students.updateMany(
	{ },
	{ $rename: { 'nickname': 'alias', 'cell': 'mobile' } }
)
Ajouter / supprimer un champ :
db.students.updateMany(
	{ },
	{
		$set: { 'pastSchool': '' },
		$unset: { 'cell': 0 }
	}
)
Renommer une collection :
db.students.renameCollection("étudiants")
// Ou, si changement de base :
db.adminCommand( {
	renameCollection: "test.students",
	to: "enseignement.étudiants" } )
Restructurer une collection :
{ "_id" : 57,
  "value" : {
    "user" : [ {
      "firstname": "Jane",
      "lastname": "Doe",
      "State": "Unknown",
      "location" : {
        "lat" : 34.123456,
        "lon" : -95.123456
      },
    } ],
} }
db.collection.find().forEach(
  function(doc) {
    var user = doc.value.user;
    delete doc.value;
    db.collection.update(
      { "_id": doc._id },
      { "$set": { "user": user } }
    );
  }
)

DML : altérer le contenu des collections

Insert : exemples

// Seuls les champs non nuls doivent être spécifiés
db.livres.insert({ author: 'Fred Vargas'});
db.livres.insert(
	{ author:'Alain Damasio', title:'La Horde du Contrevent'});
// Plusieurs documents en une commande
db.livres.insertMany([
	{author:'Haruki Murakami', title:'La Course au mouton sauvage'},
	{author:'Haruki Murakami', title:'La Fin des temps'},
	{author:'Haruki Murakami', title:"La Ballade de l'impossible"}])

Importer depuis un fichier CSV

The mongoimport tool imports content from an Extended JSON, CSV, or TSV export created by mongoexport.

mongoexport is a utility that produces a JSON or CSV export of data stored in a MongoDB instance.

Exemples :

mongoexport --db users --collection contacts --type=csv
            --fields name,address --out contacts.csv

mongoimport --db users --collection contacts --file contacts.json

Modifier des documents

// Assigne l'ISBN au premier document ayant isbn = null
db.livres.update(
  { isbn: null },
  { $set: { isbn: 32 } } )

// SET isbn = 42 WHERE author = ...
db.livres.update(
  { author: 'Alain Damasio' },
  { $set: { isbn: 42 } } )
> db.livres.find() //équivalent du SELECT * FROM ...
{ "_id" : ObjectId("5a9f1b3af0ce301a50fddc64"),
  "author" : "Fred Vargas", "isbn" : 32 }
{ "_id" : ObjectId("5a9f1b40f0ce301a50fddc65"),
  "author" : "Alain Damasio",
  "title" : "La Horde du Contrevent", "isbn" : 42 }
{ "_id" : ObjectId("5a9f1b89f0ce301a50fddc66"),
  "author" : "Haruki Murakami",
  "title" : "La Course au mouton sauvage" } (...)

BSON id

Mongo assigne automatiquement un identifiant formé de 12 octets, si le champ "_id" n'est pas spécifié :

The 12-byte ObjectId value consists of:

  • a 4-byte value representing the seconds since the Unix epoch,
  • a 3-byte machine identifier,
  • a 2-byte process id, and
  • a 3-byte counter, starting with a random value.
Notes
  • Un BSON id contient la date de création d'un document.
  • Pour les livres on pourrait utiliser l'ISBN comme identifiant ;
    mais il faut le faire à la création (_id is immutable)

Supprimer des documents

Exemples

// Équivalents du DELETE FROM ... WHERE ...
db.livres.remove({ isbn: 32 })
db.livres.remove({ title: 'La Fin des temps' })

// Équivalent du DELETE FROM ... (supprime tout)
db.livres.remove({ })

Équivalent du "DROP TABLE" :

db.livres.drop()

Requêtes avec MongoDB

...Le principe est le même, mais les opérateurs sont plus complexes car la structure d'un document (hiérarchique) est en général moins simple que celle d'une table SQL ("flat")

DQL

Points importants
  • Un document est sensé être "self-contained", avec le moins possible de références vers d'autres collections.
  • Il n'y a (donc) pas d'équivalent des jointures ("JOIN").

Remarque : ce n'est pas tout à fait vrai.... Voir aussi.

En revanche on pourra utiliser l'opérateur d'agrégation comme substitut à certaines requêtes imbriquées.

Équivalent du SELECT ... FROM ...

Requête la plus simple : affiche des (combinaisons de) champs.

Exemples dans le contexte d'une médiathèque, sur la collection documents(Type,Description,Titre,Auteur,Date,Emprunteur)

// Afficher toute la table
db.documents.find()
// Afficher seulement l'identifiant (code) et l'auteur
db.documents.find({ }, { Auteur:1 })
// Afficher aussi l'identifiant de l'emprunteur
db.documents.aggregate([{ $project:
  { Auteur: 1, IdentifiantAdherent: "$Emprunteur" }}])
Équivalent du SELECT ... FROM ... WHERE ...

Requête la plus courante : filtre sur les documents.

document(Type,Description,Titre,Auteur,Date,Emprunteur)

// Les documents produits entre 1980 et 1990
db.documents.find({ $and: [
	{ Date: { $gte: new Date('1980-01-01') }},
	{ Date: { $lte: new Date('1990-01-01') }}
]})

Titre et date des documents empruntés par Alice, Type != "BD".

db.documents.find(
	{
		Emprunteur: 'Alice',
		Type: { $ne: 'BD' },
	},
	{ _id: 0, Titre: 1, Date: 1 }
)
COUNT

Compte les documents vérifiant un certain critère.

// Nombre de documents dans une collection
db.commands.count()
// Nombre de commandes effectuées avant le 9/01/2009
db.commands.count({ Date: { $lte: new Date('2009-01-09') } })
// Nombre de commandes effectuées apres le 9/01/2009
// dont la quantité est superieure a 5
db.commands.count({
  Date: { $gte: new Date('2009-01-09') },
	Quantité: { $gt: 5 }
})
MAX, MIN, SUM, ...
// Prix maximum d'une commande
db.commands.aggregate([
  { $group: {
    _id: 1,
    prixMax: { $max: { $multiply: [ "$Prix", "$Quantité" ] } }
  } },
  { $project: { _id: 0, prixMax: 1 } }
])
// Quantité minimum (ou totale) commandée par Bob
db.commands.aggregate([
  { $match: { Client: "Bob" } },
  { $group: {
    _id: 1,
    quantitéMin: { $min: "$Quantité" } //ou $sum
  } },
	{ $project: { _id: 0, quantitéMin: 1 } }
])

concerts(Groupe,Lieu,NomSalle,CapacitéSalle,NbEntrées)

Capacité moyenne des salles de Berlin ayant totalisé plus de 300 entrées au moins une fois. NB : les salles ont des capacités ≠

db.concerts.aggregate([
	{ $match: {
		NbEntrées: { $gte: 300 },
		Lieu: 'Berlin'
	} },
	{ $group: {
		_id: "$NomSalle",
		Capacity: { $first: "$CapacitéSalle" }
	} },
	{ $group: {
		_id: 1,
		avgCapacity: { $avg: "$Capacity" }
	} },
	{ $project: { _id: 0, avgCapacity: 1 } }
])
Équivalent du SELECT ... FROM ... WHERE ... GROUP BY ...

commandes(Client,Produit,Prix,Quantité,Date)

// Sommes des prix totaux par client puis par date
db.commandes.aggregate([
	{ $group: {
		_id: { Client: "$Client", Date: "$Date" },
		prixTotal: {$sum: {$multiply: ["$Prix","$Quantité"]}}
	} }
])
// Clients qui ont commandé quelque chose le 9/01/2009
db.commandes.aggregate([
	{ $match: { Date: new Date('2009-01-09') } },
	{ $group: {
		_id: "$Client",
		totQuantity: { $sum: "$Quantité" }
	} },
	{ $match: { totQuantity: { $gte: 1 } } }
])
ORDER BY ...
// Prix total par ordre décroissant
db.commandes.aggregate([
  { $match: { Date: new Date('2010-10-12') } },
  { $project: { _id: 0, Client: 1, Produit: 1,
    prixTotal: { $multiply: ["$Quantité", "$Prix"] } } },
  { $sort: { prixTotal: -1 } } ])

documents(Type,Description,Titre,Auteur,Date,Emprunteur)

// Nombres de documents datant de 2004 empruntés par
// chaque utilisateur, rangés par ordre croissant.
db.documents.aggregate([
  { $match: { $and: [ { Date: { $gte: new Date('2004-01-01') }},
    { Date: { $lte: new Date('2004-12-31T23:59:59') } } ] } },
  { $group: { _id: "$Emprunteur", nbDocs: { $sum: 1 } } },
  { $sort: { nbDocs: 1 } },
  { $project: { _id: 0, Emprunteur: "$_id", nbDocs: 1 } }
])
Expressions conditionnelles
// Collection 'grades' :
{_id : 1, name : "Susan Wilkes", scores : [ 87, 86, 78 ] }
{_id : 2, name : "Bob Hanna", scores : [ 71, 64, 81 ] }
{_id : 3, name : "James Torrelio", scores : [ 91, 84, 97 ] }
db.grades.aggregate( [
  { $project: { name : 1, summary : { $switch: {
    branches: [
      { case: { $gte : [ { $avg : "$scores" }, 90 ] },
        then: "Doing great!" },
      { case: { $and : [ { $gte : [ { $avg : "$scores" }, 80 ]},
          { $lt : [ { $avg : "$scores" }, 90 ] } ] },
        then: "Doing pretty well." },
      { case: { $lt : [ { $avg : "$scores" }, 80 ] },
        then: "Needs improvement." } ],
    default: "No scores found." } } } } ])

Note : pour l'opérateur $cond voir l'exo 1 du TP

Requêtes avancées (sub-documents)

Nested document

// Dans une librairie-papeterie...
db.inventory.insertMany([
	{ item: "journal", qty: 25,
    size: { h: 14, w: 21, uom: "cm" }, status: "A" },
	{ item: "notebook", qty: 50,
    size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
	{ item: "paper", qty: 100,
    size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
	{ item: "planner", qty: 75,
    size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
	{ item: "postcard", qty: 45,
    size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);

Documents contenant un sous-document spécifique :

db.inventory.find( { size: { h: 14, w: 21, uom: "cm" } } )

Attention : sensible à l'ordre des champs.

Recherche sur le champ d'un sous-document :

db.inventory.find( { "size.uom": "in" } )

Les guillemets sont obligatoires dans ce cas.

Opérateurs : comme précédemment,

db.inventory.find( { "size.h": { $lt: 15 } } )
// AND :
db.inventory.find( { "size.h": { $lt: 15 },
	"size.uom": "in", status: "D" } )

Documents contenant des tableaux

db.inventory.insertMany([
	{ item: "journal", qty: 25, tags: ["blank", "red"],
    dim_cm: [ 14, 21 ] },
	{ item: "notebook", qty: 50, tags: ["red", "blank"],
    dim_cm: [ 14, 21 ] },
	{ item: "paper", qty: 100, tags: ["red", "blank", "plain"],
    dim_cm: [ 14, 21 ] },
	{ item: "planner", qty: 75, tags: ["blank", "red"],
    dim_cm: [ 22.85, 30 ] },
	{ item: "postcard", qty: 45, tags: ["blue"],
    dim_cm: [ 10, 15.25 ] }
]);

Exact match :

db.inventory.find( { tags: ["red", "blank"] } )

Contient toutes les valeurs demandées :

db.inventory.find( { tags: { $all: ["red", "blank"] } } )

Document dont un tableau contient un élément précis :

db.inventory.find( { tags: "red" } )

Condition sur les éléments du tableau :

db.inventory.find( { dim_cm: { $gt: 25 } } )

Conditions multiples (AND) :

// Conditions toutes vérifiées
// (pas forcément par le même élément)
db.inventory.find( { dim_cm: { $gt: 15, $lt: 20 } } )

// Conditions toutes vérifiées par un même élément :
db.inventory.find(
  { dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } } } )

// Conditions toutes vérifiées par l'élément de rang 1 (le 2eme)
db.inventory.find( { "dim_cm.1": { $gt: 25 } } )

Filtre par longueur de tableau :

db.inventory.find( { "tags": { $size: 3 } } )

D'autres opérateurs...

Documents contenant des tableaux de documents

db.inventory.insertMany([
  {item: "journal", instock:
    [{warehouse: "A", qty: 5}, {warehouse: "C", qty: 15}]},
  {item: "notebook", instock: [{warehouse: "C", qty: 5}]},
  {item: "paper", instock:
    [{warehouse: "A", qty: 60}, {warehouse: "B", qty: 15}]},
  {item: "planner", instock:
    [{warehouse: "A", qty: 40}, {warehouse: "B", qty: 5}]},
  {item: "postcard", instock:
    [{warehouse: "B", qty: 15}, {warehouse: "C", qty: 35}]}
])

Documents dont un tableau contient un sous-document fixé :

db.inventory.find( { "instock": { warehouse: "A", qty: 5 } } )

Attention : l'ordre des champs est important.

// Au moins un élément du tableau instock a "qty" <= 20
db.inventory.find( { 'instock.qty': { $lte: 20 } } )

// Le premier élément du tableau a "qty" <= 20
db.inventory.find( { 'instock.0.qty': { $lte: 20 } } )

// Les deux conditions sont vérifiées,
// pas forcément par le même élement
db.inventory.find( { "instock.qty": { $gt: 10,  $lte: 20 } } )
db.inventory.find(
  { "instock.qty": 5, "instock.warehouse": "A" } )

Voir aussi la documentation ($elemMatch)

Les index

Même principe qu'en SQLite

MongoDB defines indexes at the collection level and supports indexes on any field or sub-field of the documents in a MongoDB collection. MongoDB indexes use a B-tree data structure.

_id : automatiquement indexé

Sinon :

// Index décroissant sur le champ 'name' :
db.collection.createIndex( { name: -1 } )
// Index composé :
db.products.createIndex( { "item": 1, "stock": 1 } )

Comme d'hab, voir aussi la doc

"Server functions"

There is a special system collection named system.js that can store JavaScript functions for reuse.

To store a function, you can use the db.collection.save(), as in the following examples:

db.system.js.save({
  _id: "echoFunction",
  value : function(x) { return x; } } )
db.system.js.save({
  _id : "myAddFunction" ,
  value : function (x, y) { return x + y; } } );

Once you save a function in the system.js collection, you can use the function from any JavaScript context; e.g. $where operator, mapReduce command or db.collection.mapReduce().

Exemple d'utilisation

Sur un site de variantes du jeu d'échecs

/* Structure ("games" collection) :
 *   _id: BSON id
 *   players: [white_player, black_player]
 *   result: string (1-0, 0-1, 1/2 or * for unfinished)
 *   ...
 *   moves: array of moves; move structure:
 *      ...
 *   chats: array of objects {message, author} */
function addChat(gid, auth, msg) {
  db.games.update({"_id": gid}, {
    $push: {"chats": {
      // msg: at most 150 characters
      $each: [{msg:msg.substring(0,150), auth:auth}],
      $slice: -7 //keep only 7 last chat messages
    }}
  });
}