Ce tutoriel vous présente la base de données MongoDB. Vous découvrirez comment installer le logiciel, manipuler les données et appliquer les techniques de conception de données à vos propres applications.

Tous les exemples ont été développés à l’aide de MongoDB 5, mais la plupart fonctionneront dans des versions antérieures ou ultérieures. Le code peut être saisi directement dans une application cliente ou dans le shell MongoDB (mongo ou mongosh) pour interroger et mettre à jour la base de données.

Qu’est-ce que MongoDB ?

MongoDB est une base de données NoSQL open source. NoSQL signifie que la base de données n’utilise pas de tables relationnelles comme une base de données SQL traditionnelle.

Il existe une gamme de types de bases de données NoSQL, mais MongoDB stocke les données dans des objets de type JavaScript appelés documents, dont le contenu ressemble à ceci :

{
  _id: "123",
  name: "Craig"
}


Bien que MongoDB soit devenu synonyme du framework JavaScript Node.js, des pilotes de base de données MongoDB officiels sont disponibles pour la plupart des frameworks, langages et moteurs d’exécution, notamment Node.js, PHP et Python. Vous pouvez également opter pour des bibliothèques telles que Mongoose qui offrent un niveau d’abstraction plus élevé ou des fonctionnalités de mappage objet-relationnel (Object Relational Mapping ou ORM).

Contrairement aux tables SQL, il n’y a pas de limites structurelles à ce que vous pouvez stocker dans MongoDB. Les schémas de données ne sont pas imposés : Vous pouvez stocker ce que vous voulez où vous voulez. Cela rend MongoDB idéal pour les structures de données plus organiques – ou désordonnées.

Prenons l’exemple d’un carnet d’adresses de contacts. Les personnes peuvent souvent avoir plusieurs numéros de téléphone. Vous pourriez définir trois champs de téléphone dans une table SQL, mais ce serait trop pour certains contacts et trop peu pour d’autres. En fin de compte, vous aurez besoin d’une table de téléphone séparée, ce qui ajoute à la complexité.

Dans MongoDB, ces numéros de téléphone pourraient être définis comme un tableau illimité d’objets dans le même document :

{
  _id: "123",
  name: "Craig",
  telephone: [
    { home: "0123456789" },
    { work: "9876543210" },
    { cell: "3141592654" }
  ]
}

Notez que MongoDB utilise une notation d’objets JavaScript similaire pour les mises à jour et les requêtes de données, ce qui peut poser quelques difficultés si vous êtes habitué à SQL.

Éléments de MongoDB

Avant d’aller plus loin, jetons un coup d’œil à ce qui fait le charme de MongoDB. Nous utiliserons ce vocabulaire tout au long de cet article.

  • Document : Un objet individuel unique dans un stock de données, analogue à un enregistrement ou une ligne dans une table de base de données SQL.
  • Champ : Un élément de données unique dans un document, tel qu’un nom ou un numéro de téléphone, analogue à un champ ou une colonne de table SQL.
  • Collection : Un ensemble de documents similaires, analogue à une table SQL. Bien que vous puissiez mettre tous vos documents dans une seule collection, il est généralement plus pratique de les regrouper en types spécifiques. Dans un carnet d’adresses de contacts, vous pourriez avoir une collection pour les personnes et une collection pour les entreprises.
  • Base de données : Une collection de données liées, identique dans sa signification à une base de données SQL.
  • Schéma : Un schéma définit les structures de données. Dans les bases de données SQL, vous devez définir des définitions de table avec des champs et des types associés avant de pouvoir stocker des données. Ceci n’est pas nécessaire dans MongoDB bien qu’il soit possible de créer un schéma qui valide les documents avant qu’ils puissent être ajoutés à une collection.
  • Index : Une structure de données utilisée pour améliorer les performances d’interrogation, identique dans sa signification aux index SQL.
  • Clé primaire : Un identifiant unique pour chaque document. MongoDB ajoute automatiquement un champ _id unique et indexé à chaque document d’une collection.
  • Dénormalisation : Dans les bases de données SQL, la « normalisation » est une technique utilisée pour organiser les données et éliminer les doublons. Dans MongoDB, la « dénormalisation » est encouragée. Vous répétez activement les données et un seul document pourrait contenir toutes les informations nécessaires.
  • Joints : SQL fournit un opérateur JOIN afin que les données puissent être extraites de plusieurs tables normalisées en une seule requête. La jointure n’était pas possible dans MongoDB jusqu’à la version 3.6 et des limitations subsistent. C’est une autre raison pour laquelle les données devraient être dénormalisées en documents autonomes.
  • Transactions : Lorsqu’une mise à jour modifie deux valeurs ou plus sur un même document, MongoDB s’assure qu’elles réussissent toutes ou qu’elles échouent toutes. Les mises à jour sur deux documents ou plus doivent être enveloppées dans une transaction. MongoDB prend en charge les transactions depuis la version 4.0, mais un ensemble de répliques multi-serveurs ou un cluster sharded est nécessaire. Les exemples d’installation ci-dessous utilisent un seul serveur, les transactions ne sont donc pas possibles.

Comment installer MongoDB

Vous avez trois options pour utiliser MongoDB sur votre machine locale. Nous allons vous guider à travers chacune d’elles.

1. Utiliser Docker (recommandé)

Docker est un outil de gestion de logiciels qui peut installer, configurer et exécuter MongoDB ou toute autre application en quelques minutes.

Installez Docker et Docker Compose puis créez un dossier de projet avec un seul fichier nommé docker-compose.yml contenant le contenu suivant (notez que les indentations sont essentielles) :

version: '3'

services:

  mongodb:
    image: mongo:5
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=pass
      - MONGO_INITDB_DATABASE=mongodemo
    container_name: mongodb
    volumes:
      - dbdata:/data/db
    ports:
      - "27017:27017"

  adminer:
    image: dehy/adminer
    container_name: adminer
    depends_on:
      - mongodb
    ports:
      - "8080:80"

volumes:
  dbdata:

Accédez au dossier à partir de la ligne de commande et exécutez :

docker-compose up

La dernière version de MongoDB 5 sera téléchargée et lancée. Cela prendra quelques minutes lors du premier lancement, mais les exécutions suivantes sont considérablement plus rapides.

Notez que :

  • Un compte administrateur MongoDB est défini avec l’ID « root » et le mot de passe « pass ».
  • Les données sont sauvegardées entre les redémarrages dans un volume Docker nommé dbdata.
  • Le client de base de données Adminer est également fourni.

Vous pouvez utiliser n’importe quel client de base de données MongoDB pour vous connecter à localhost:27017 en utilisant l’identifiant « root » et le mot de passe « pass ». Sinon, vous pouvez accéder à Adminer à l’adresse http://localhost:8080/ et vous connecter avec les informations d’identification suivantes :

  • Système : MongoDB (alpha)
  • Serveur : host.docker.internal
  • Nom d’utilisateur : root
  • Mot de passe : pass
Connexion à Adminer
Connexion à Adminer

Adminer vous permet d’inspecter les collections et les documents. Sachez toutefois que les collections sont appelées « tables » :

Vue de la collection Adminer
Vue de la collection Adminer

Pour exécuter des commandes, vous pouvez utiliser le shell MongoDB (mongosh) ou l’environnement REPL (Read Eval Print Loop) de l’ancienne ligne de commande mongo.

Accédez au shell bash du conteneur Docker MongoDB :

docker exec -it mongodb bash

Lancez ensuite le shell MongoDB avec l’ID et le mot de passe :

mongosh -u root -p pass

(L’ancienne commande mongo peut être utilisée si vous préférez)

Vous pouvez ensuite lancer des commandes MongoDB comme les suivantes :

  • show dbs; – Afficher toutes les bases de données
  • use mongodemo; – Utiliser une base de données spécifique
  • show collections; – Lister les collections dans une base de données
  • db.person.find(); – Lister tous les documents dans une collection
  • exit; – Quitter/fermer le shell

Arrêtez MongoDB en exécutant la commande suivante depuis le répertoire du projet :

docker-compose down

2. Utiliser un fournisseur cloud (aucune installation)

Vous pouvez utiliser une instance MongoDB hébergée, il n’est donc pas nécessaire d’installer quoi que ce soit localement. Une connexion Internet est indispensable et la vitesse de réponse dépendra de l’hébergeur et de votre bande passante. La plupart des services facturent des frais d’utilisation mensuels et/ou en mégaoctets.

L’hôte fournira normalement les détails vous permettant d’administrer la base de données à distance à l’aide du logiciel client MongoDB.

3. Installer MongoDB localement

MongoDB peut être installé et configuré sur Linux, Windows ou Mac OS. Deux éditions sont disponibles :

  1. Une édition commerciale Enterprise
  2. Une édition communautaire open source (utilisée dans ce tutoriel)

La page d’installation de MongoDB fournit des instructions pour les différents systèmes d’exploitation. En général :

Veillez à suivre attentivement les instructions pour que votre installation soit réussie !

Comment accéder à votre base de données MongoDB

Maintenant que votre base de données MongoDB est installée, il est temps d’apprendre à la gérer. Voyons ce que vous devez faire pour accéder à votre base de données et travailler avec elle.

1. Installer un client MongoDB

Une application client MongoDB est nécessaire pour administrer les bases de données. Si vous utilisez une installation cloud ou locale, nous vous recommandons d’installer le shell MongoDB en ligne de commande mongosh.

Adminer est un client de base de données basé sur le web qui prend en charge MongoDB, bien qu’il soit actuellement limité à l’inspection des collections. Adminer est téléchargeable sous forme de script PHP unique, mais il est déjà configuré si vous utilisez l’installation Docker ou si DevKinsta est installé.

Une application client GUI offre une meilleure interface pour la mise à jour et l’inspection des données. Plusieurs options sont disponibles, notamment le logiciel gratuit et multi-plateformes MongoDB Compass:

MongoDB Compass
MongoDB Compass

Studio 3T, un autre prétendant à l’interface graphique, fournit une application commerciale qui offre gratuitement des fonctionnalités limitées :

Studio 3T
Studio 3T

Vous pouvez accéder à votre base de données MongoDB avec l’un de ces outils en utilisant l’un des éléments suivants :

  1. Le nom de réseau de la machine, l’URL ou l’adresse IP (localhost pour une installation locale).
  2. Le port MongoDB (27017 par défaut).
  3. Un ID utilisateur et un mot de passe. Un utilisateur root est normalement défini lors de l’installation.

2. Définir et enregistrer les informations d’identification d’accès à la base de données

L’administrateur root a un accès illimité à toutes les bases de données. En général, vous devriez utiliser un utilisateur personnalisé avec des privilèges spécifiques pour limiter l’accès et augmenter la sécurité.

Par exemple, la commande suivante crée un utilisateur nommé myuser avec le mot de passe mypass qui a un accès en lecture et en écriture à la base de données mydb :

use mydb;

db.createUser({
  user: "myuser",
  pwd: "mypass",
  roles: [
    { role: "readWrite", db: "mydb" }
  ]
});

Comment insérer de nouveaux documents dans MongoDB

Il n’est pas nécessaire de définir une base de données ou une collection avant d’insérer votre premier document. En utilisant n’importe quel client MongoDB, passez simplement à une base de données nommée mongodemo :

use mongodemo;

Puis insérez un seul document dans une nouvelle collection de personnes :

db.person.insertOne(
  {
    name: 'Abdul',
    company: 'Alpha Inc',
    telephone: [
      { home: '0123456789' },
      { work: '9876543210' }
    ]
  }
);

Visualisez le document en exécutant une requête pour retourner tous les résultats de la collection de personnes :

db.person.find({});

Le résultat sera quelque chose comme ceci :

{
  "_id" : ObjectId("62442429854636a03f6b8534"),
  name: 'Abdul',
  company: 'Alpha Inc',
  telephone: [
    { home: '0123456789' },
    { work: '9876543210' }
  ]
}

Comment insérer plusieurs documents

Vous pouvez insérer plusieurs documents dans une collection en passant un tableau à insertMany(). Le code suivant crée des documents de personnes supplémentaires et une nouvelle collection de sociétés :

db.person.insertMany([
  {
    name: 'Brian',
    company: 'Beta Inc'
  },
  {
    name: 'Claire',
    company: 'Gamma Inc',
    telephone: [
      { cell: '3141592654' }
    ]
  },
  {
    name: 'Dawn',
    company: 'Alpha Inc'
  },
  {
    name: 'Esther',
    company: 'Beta Inc',
    telephone: [
      { home: '001122334455' }
    ]
  },
  {
    name: 'George',
    company: 'Gamma Inc'
  },
  {
    name: 'Henry',
    company: 'Alpha Inc',
    telephone: [
      { work: '012301230123' },
      { cell: '161803398875' }
    ]
  },
]);

db.company.insertMany([
  {
    name: 'Alpha Inc',
    base: 'US'
  },
  {
    name: 'Beta Inc',
    base: 'US'
  },
  {
    name: 'Gamma Inc',
    base: 'GB'
  },
]);

D’où vient le _id ?

MongoDB attribue automatiquement un _id à chaque document d’une collection. Il s’agit d’un ObjectID – une valeur BSON (Binary Javascript Object Notation) contenant :

  • L’époque Unix en secondes au moment de la création (4 octets)
  • Un identifiant de machine/processus de 5 octets
  • Un compteur de 3 octets commençant par une valeur aléatoire

C’est la clé primaire du document. Cette valeur hexadécimale de 24 caractères est garantie unique pour tous les documents de la base de données et ne peut être modifiée une fois insérée.

MongoDB fournit également une fonction getTimeStamp() afin que vous puissiez obtenir la date/heure de création du document sans avoir à définir explicitement une valeur. Alternativement, vous pouvez définir votre propre valeur _id unique lors de la création d’un document.

Dénormalisation des données

Les enregistrements insérés ci-dessus définissent la société de chaque utilisateur comme une chaîne de caractères telle que « Alpha Inc ». Ceci n’est pas recommandé dans les bases de données SQL normalisées :

  • Il est facile de faire une erreur : un utilisateur est affecté à « Alpha Inc » alors qu’un autre est « Alpha Inc » (caractère point supplémentaire). Ils sont traités comme des sociétés différentes.
  • La mise à jour d’un nom de société peut signifier la mise à jour de nombreux enregistrements.

La solution SQL consiste à créer une table company et à associer une société à une personne en utilisant sa clé primaire (probablement un nombre entier). La clé resterait la même quels que soient les changements de nom de société et la base de données peut appliquer des règles pour garantir l’intégrité des données.

La dénormalisation est encouragée dans MongoDB. Vous devriez activement répéter les données et un seul document pourrait contenir toutes les informations nécessaires. Cela présente plusieurs avantages :

  • Les documents sont autonomes et plus faciles à lire – il n’est pas nécessaire de faire référence à d’autres collections.
  • Les performances d’écriture peuvent être plus rapides que celles d’une base de données SQL car moins de règles d’intégrité des données sont appliquées.
  • Le sharding – ou la distribution des données sur plusieurs machines – devient plus facile car il n’est pas nécessaire de référencer les données dans d’autres collections.

Requêtes MongoDB simples

Vous pouvez lister tous les documents d’une collection, telle que la personne, en utilisant un find() vide :

db.person.find({})

La méthode count() renvoie le nombre de documents (dans notre cas, ce nombre sera de 7) :

db.person.find({}).count();

La méthode sort() renvoie les documents dans l’ordre que vous préférez, par exemple par nom dans l’ordre alphabétique inverse :

db.person.find({}).sort({ name: -1 });

Vous pouvez également limiter le nombre de documents retournés, par exemple trouver les trois premiers noms :

db.person.find({}).sort({ name: 1 }).limit(2);

Vous pouvez rechercher des enregistrements spécifiques en définissant une requête avec un ou plusieurs champs, par exemple, trouver tous les documents de personnes où le nom est défini comme « Claire » :

db.person.find({ name: 'Claire' });

Les opérateurs logiques tels que $and, $or, $not, $gt (supérieur à), $lt (inférieur à), et $ne (non égal), sont également pris en charge, par exemple, localiser tous les documents de personnes où la société est soit « Alpha Inc » soit « Beta Inc » :

db.person.find({
  $or: [
    { company: 'Alpha Inc' },
    { company: 'Beta Inc' }
  ]
});

Dans la base de données d’exemple, le même résultat pourrait être obtenu avec $nin (pas dans) pour extraire tous les documents où la société n’est pas « Gamma Inc » :

db.person.find({
  company: { $nin: ['Gamma Inc'] }
});

Un deuxième objet valeur dans la méthode find() définit une projection qui définit les champs retournés. Dans cet exemple, seul le nom est renvoyé (notez que le document _id est toujours renvoyé, sauf s’il est explicitement désactivé) :

db.person.find(
  { name:'Claire' },
  { _id:0, name:1 }
);

Le résultat :

{
  "name" : "Claire"
}

La requête $elemMatch vous permet de trouver des éléments dans un tableau, comme tous les documents où le tableau de téléphone a un numéro professionnel. Le même $elemMatch peut être utilisé dans la projection pour montrer uniquement le numéro de travail :

db.person.find(
  {
    telephone: { $elemMatch: { work: { $exists: true }} }
  },
  {
    _id: 0,
    name:1,
    telephone: { $elemMatch: { work: { $exists: true }}}
  }
);

Le résultat :

{
  "name" : "Abdul",
  "telephone" : [
    { "work" : "9876543210" }
  ]
},
{
  "name" : "Henry",
  "telephone" : [
    { "work" : "012301230123" }
  ]
}

Utilisation des curseurs dans MongoDB

La plupart des pilotes de base de données permettent de renvoyer les résultats d’une requête sous forme de tableau ou de structure de données similaire. Toutefois, si ce tableau contient des milliers de documents, cela peut entraîner des problèmes de mémoire.

Comme la plupart des bases de données SQL, MongoDB prend en charge le concept de curseurs. Les curseurs permettent à une application de lire les résultats d’une requête un par un avant de passer à l’élément suivant ou d’abandonner la recherche.

Les curseurs peuvent également être utilisés à partir d’un shell MongoDB :

let myCursor = db.person.find( {} );

while ( myCursor.hasNext() ) {
  print(tojson( myCursor.next() ));
}

Comment créer des index dans MongoDB

La collection de personnes contient actuellement sept documents. Toute requête ne sera donc pas coûteuse en termes de calcul. Cependant, imaginez que vous avez un million de contacts avec un nom et une adresse e-mail. Les contacts peuvent être classés par nom mais les adresses e-mail seront dans un ordre apparemment aléatoire.

Si vous devez rechercher un contact par son adresse e-mail, la base de données devra chercher jusqu’à un million d’éléments avant de trouver une correspondance. L’ajout d’un index sur le champ email crée une « table » de recherche où les e-mails sont stockés par ordre alphabétique. La base de données peut maintenant utiliser des algorithmes de recherche plus efficaces pour localiser la bonne personne.

Les index deviennent essentiels lorsque le nombre de documents augmente. En général, vous devriez appliquer un index à tout champ qui pourrait être référencé dans une requête. Vous pourriez appliquer des index à chaque champ, mais sachez que cela ralentirait les mises à jour des données et augmenterait l’espace disque requis, car la réindexation devient nécessaire.

MongoDB offre une gamme de types d’index.

Index à champ unique

La plupart des index seront appliqués à des champs uniques, par exemple, indexer le champ name par ordre alphabétique croissant :

db.person.createIndex({ name: 1 });

L’utilisation de -1 inverse l’ordre. Cela serait peu utile dans notre exemple ici, mais cela pourrait être pratique si vous avez un champ date où les événements les plus récents ont la priorité.

Trois autres index sont utiles dans l’exemple de la base de données mongodemo :

db.person.createIndex( { company: 1 } );
db.company.createIndex( { name: 1 } );
db.company.createIndex( { base: 1 } );

Les index composés sur plusieurs champs

Deux champs ou plus peuvent être spécifiés dans un index, par exemple

db.person.createIndex( { name: 1, company: 1 } );

Cela peut être utile lorsqu’un champ est régulièrement utilisé en conjonction avec un autre dans les requêtes de recherche.

Index à clés multiples sur des éléments de tableau ou d’objet

Les documents peuvent être complexes et il est souvent nécessaire d’indexer des champs plus profonds dans la structure, comme le numéro de téléphone du travail :

db.products.createIndex( { 'telephone.work': 1 } );

Indices wildcard

Un wildcard peut indexer tous les champs d’un document. Ceci est généralement pratique sur des documents plus petits et plus simples qui peuvent être interrogés de différentes manières :

db.company.createIndex( { '$**': 1 } );

Index en texte intégral

Un index texte vous permet de créer des requêtes de type moteur de recherche qui peuvent examiner le texte dans tous les champs de la chaîne et l’ordonner par pertinence. Vous pouvez limiter l’index de texte à des champs spécifiques :

db.person.createIndex( { name: "text", company: "text" } );

…ou créer un index de texte sur tous les champs de chaîne :

db.person.createIndex( { "$**": "text" } );

L’opérateur $text vous permet d’effectuer une recherche dans cet index, par exemple pour trouver tous les documents où « Gamma » est référencé :

db.person.find({ $text: { $search: 'Gamma' } });

Notez que les recherches en texte intégral nécessitent généralement cinq caractères ou plus pour renvoyer des résultats utiles.

Autres types d’index

MongoDB fournit plusieurs autres types d’index spécialisés :

Comment gérer les index de MongoDB

Les index définis sur une collection peuvent être examinés avec :

db.person.getIndexes();

Ceci renvoie un tableau de résultats tels que :

[
  {
    "v" : 2.0,
    "key" : { "_id" : 1.0 },
    "name" : "_id_"
  },
  {
    "v" : 2.0,
    "key" : { "company" : 1.0 },
    "name" : "company_1"
  },
  {
    "v" : 2.0,
    "key" : { "name" : 1.0 },
    "name" : "name_1"
  }
]

La « clé » définit le champ et l’ordre tandis que le « name » est un identifiant unique pour cet index – tel que « company_1 » pour l’index sur le champ company.

L’efficacité d’un index peut être examinée en ajoutant une méthode .explain() à n’importe quelle requête, par exemple

db.person.find({ name:'Claire' }).explain();

Cette requête renvoie un grand ensemble de données, mais l’objet « winningPlan » indique le « indexName » utilisé dans la requête :

"winningPlan" : {
  "stage" : "FETCH",
  "inputStage" : {
    "stage" : "IXSCAN",
    "keyPattern" : { "name" : 1.0 },
    "indexName" : "name_1",
  }
}

Si nécessaire, vous pouvez supprimer un index en faisant référence à son nom :

db.person.dropIndex( 'name_1' );

ou en utilisant le document de spécification de l’index :

db.person.dropIndex({ name: 1 });

La méthode dropIndexes() vous permet de supprimer plus d’un index en une seule commande.

Utilisation des schémas de définition de données de MongoDB

Contrairement à SQL, les schémas de définition des données ne sont pas nécessaires dans MongoDB. Vous pouvez enregistrer n’importe quelle donnée dans n’importe quel document de n’importe quelle collection à tout moment.

Cela offre une liberté considérable. Toutefois, il peut arriver que vous souhaitiez insister pour que des règles soient respectées. Par exemple, il ne devrait pas être possible d’insérer un document dans la collection personne à moins qu’il ne contienne un nom.

Les règles de validation peuvent être spécifiées à l’aide d’un objet $jsonSchema qui définit un tableau d’éléments requis et les propriétés de chaque champ validé. La collection de personnes a déjà été créée, mais vous pouvez encore définir un schéma qui spécifie qu’une chaîne de nom est requise :

db.runCommand({
  collMod: 'person',
  validator: {
    $jsonSchema: {
      required: [ 'name' ],
      properties: {
        name: {
          bsonType: 'string',
          description: 'name string required'
        }
      }
    }
  }
});

Essayez d’insérer un document de personne sans nom :

db.person.insertOne({ company: 'Alpha Inc' });

…et la commande échouera :

{
  "index" : 0.0,
  "code" : 121.0,
  "errmsg" : "Document failed validation",
  "op" : {
      "_id" : ObjectId("624591771658cd08f8290401"),
      "company" : "Alpha Inc"
  }
}

Les schémas peuvent également être définis si vous créez une collection avant qu’elle ne soit utilisée. La commande suivante met en œuvre les mêmes règles que ci-dessus :

db.createCollection('person', {
  validator: {
    $jsonSchema: {
        required: [ 'name' ],
        properties: {
          name: {
          bsonType: 'string',
          description: 'name string required'
          }
      }
    }
  }
});

Cet exemple plus complexe crée une collection d’utilisateurs qui valide qu’un nom, une adresse e-mail et au moins un numéro de téléphone doivent être fournis :

db.createCollection('users', {
  validator: {
    $jsonSchema: {
      required: [ 'name', 'email', 'telephone' ],
      properties: {
        name: {
          bsonType: 'string',
          description: 'name string required'
          },
          email: {
        bsonType: 'string',
          pattern: '^.+@.+$',
          description: 'valid email required'
          },
        telephone: {
          bsonType: 'array',
          minItems: 1,
          description: 'at least one telephone number required'
          }
      }
    }
  }
});

Comment mettre à jour des documents existants dans MongoDB

MongoDB propose plusieurs méthodes de mise à jour, notamment updateOne(), updateMany(), et replaceOne(). Celles-ci sont transmises :

  • Un objet filtre qui localise les documents à mettre à jour
  • Un objet de mise à jour – ou un tableau d’objets de mise à jour – décrivant les données à modifier
  • Un objet optionnel d’options. La propriété la plus utile est upsert qui permet d’insérer un nouveau document si aucun n’est trouvé.

L’exemple suivant met à jour le document de la personne dont le nom est défini comme « Henry ». Il supprime le numéro de téléphone au travail, ajoute un numéro de téléphone au domicile et définit une nouvelle date de naissance :

db.person.updateOne(
  { name: 'Henry' },
  [
    { $unset: [ 'telephone.work' ] },
    { $set: {
      'birthdate': new ISODate('1980-01-01'),
      'telephone': [ { 'home': '789789789' } ]
    } }
  ]
);

L’exemple suivant met à jour le document de la personne dont le nom est « Ian ». Ce nom n’existe pas actuellement, mais le fait de définir upsert sur « true » le crée :

db.person.updateOne(
  { name: 'Ian' },
  { $set: { company: 'Beta Inc' } },
  { upsert: true }
);

Vous pouvez exécuter des commandes de requête pour examiner les mises à jour des données à tout moment.

Comment supprimer des documents dans MongoDB

L’exemple de mise à jour ci-dessus a utilisé $unset pour supprimer le numéro de téléphone du travail du document portant le nom « Henry ». Pour supprimer un document entier, vous pouvez utiliser l’une des méthodes de suppression suivantes : deleteOne(), deleteMany(), et remove() (qui peuvent en supprimer un ou plusieurs).

Le document nouvellement créé pour Ian peut être supprimé avec un filtre approprié :

db.person.deleteOne({ name: 'Ian' });

Utilisation des opérations d’agrégation dans MongoDB

L’agrégation est puissante mais peut être difficile à comprendre. Elle définit une série – ou pipeline – d’opérations dans un tableau. Chaque étape de ce pipeline effectue une opération telle que le filtrage, le regroupement, le calcul ou la modification d’un ensemble de documents. Une étape peut également utiliser un comportement similaire à celui d’un JOIN SQL avec une opération $lookup. Les documents résultants sont transmis à l’étape suivante du pipeline pour un traitement supplémentaire si nécessaire.

L’agrégation est mieux illustrée par un exemple. Nous allons construire étape par étape une requête qui renvoie le nom, la société et le numéro de téléphone professionnel (si disponible) de toute personne qui travaille pour une organisation basée aux États-Unis.

La première opération exécute un $match pour filtrer les entreprises basées aux États-Unis :

db.company.aggregate([
  { $match: { base: 'US' } }
]);

Ceci renvoie :

{
  "_id" : ObjectId("62442429854636a03f6b853b"),
  "name" : "Alpha Inc",
  "base" : "US"
}
{
  "_id" : ObjectId("62442429854636a03f6b853c"),
  "name" : "Beta Inc",
  "base" : "US"
}

Nous pouvons ensuite ajouter un nouvel opérateur pipeline $lookup qui fait correspondre le nom de la société (localField) à la société (foreignField) dans la collection person (from). Le résultat sera ajouté sous forme de tableau d’employés au document de chaque entreprise :

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: {
      from: 'person',
      localField: 'name',
      foreignField: 'company',
            as: 'employee'
          }
        }
]);

Et voici le résultat :

{
  "_id" : ObjectId("62442429854636a03f6b853b"),
  "name" : "Alpha Inc",
  "base" : "US",
  "employee" : [
    {
      "_id" : ObjectId("62442429854636a03f6b8534"),
      "name" : "Abdul",
      "company" : "Alpha Inc",
      "telephone" : [
        { "home" : "0123456789" },
        { "work" : "9876543210" }
      ]
    },
    {
      "_id" : ObjectId("62442429854636a03f6b8537"),
      "name" : "Dawn",
      "company" : "Alpha Inc"
    },
    {
      "_id" : ObjectId("62442429854636a03f6b853a"),
      "name" : "Henry",
      "company" : "Alpha Inc",
      "telephone" : [
        { "home" : "789789789" }
      ],
    }
  ]
}
{
  "_id" : ObjectId("62442429854636a03f6b853c"),
  "name" : "Beta Inc",
  "base" : "US",
  "employee" : [
    {
      "_id" : ObjectId("62442429854636a03f6b8535"),
      "name" : "Brian",
      "company" : "Beta Inc"
    },
    {
      "_id" : ObjectId("62442429854636a03f6b8538"),
      "name" : "Esther",
      "company" : "Beta Inc",
      "telephone" : [
       { "home" : "001122334455" }
      ]
    }
  ]
}

Une opération $project (projection) peut maintenant supprimer tous les tableaux sauf celui des employés. Cette opération est suivie d’une opération $unwind pour déstructurer le tableau et obtenir des documents séparés sur les employés :

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
  { $project: { _id: 0, employee: 1 } },
  { $unwind: '$employee' }
]);

Le résultat :

{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8534"),
    "name" : "Abdul",
    "company" : "Alpha Inc",
    "telephone" : [
      { "home" : "0123456789" },
      { "work" : "9876543210" }
    ]
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8537"),
    "name" : "Dawn",
    "company" : "Alpha Inc"
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b853a"),
    "name" : "Henry",
    "company" : "Alpha Inc",
    "telephone" : [
      { "home" : "789789789" }
    ]
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8535"),
    "name" : "Brian",
    "company" : "Beta Inc"
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8538"),
    "name" : "Esther",
    "company" : "Beta Inc",
    "telephone" : [
      { "home" : "001122334455" }
    ]
  }
}

Enfin, une opération $replaceRoot est utilisée pour formater chaque document de sorte que seuls le nom de la personne, sa société et son numéro de téléphone au travail soient renvoyés. Cette opération est suivie d’une opération $sort pour sortir les documents dans l’ordre croissant des noms. La requête agrégée complète :

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
  { $project: { _id: 0, employee: 1 } },
  { $unwind: '$employee' },
  { $replaceRoot: {
    newRoot: {
      $mergeObjects: [ {
        name: "$employee.name",
        company: '$employee.company',
        work: { $first: '$employee.telephone.work' }
      }, "$name" ]
   } } },
  { $sort: { name: 1 } }
]);

Le résultat :

{
  "name" : "Abdul",
  "company" : "Alpha Inc",
  "work" : "9876543210"
}
{
  "name" : "Brian",
  "company" : "Beta Inc",
}
{
  "name" : "Dawn",
  "company" : "Alpha Inc",
}
{
  "name" : "Esther",
  "company" : "Beta Inc"
}
{
  "name" : "Henry",
  "company" : "Alpha Inc"
}

Il existe d’autres façons d’obtenir ce résultat, mais le point essentiel est que MongoDB peut faire le gros du travail. Il est rarement nécessaire de lire les documents et de manipuler les données directement dans le code de votre application.

Comment exécuter des opérations MongoDB groupée

Par défaut, MongoDB peut gérer 1 000 opérations simultanées. Il est peu probable que cela pose un problème lorsque vous utilisez MongoDB, mais les applications peuvent atteindre cette limite si elles effectuent une série de manipulations de données sur des enregistrements individuels. Les applications Node.js sont particulièrement problématiques car elles peuvent rapidement émettre une série de requêtes asynchrones sans avoir à attendre qu’elles soient terminées.

Pour contourner ce problème, MongoDB fournit une API d’opérations groupées qui accepte un nombre quelconque de mises à jour qui peuvent être exécutées dans l’ordre ou dans n’importe quel ordre.

Voici un exemple en pseudocode dans Node.js :

// reference the mycollection collection
const bulk = db.collection('mycollection').initializeUnorderedBulkOp();

// make any number of data changes
bulk.insertOne(...);
bulk.insertMany(...)
bulk.updateOne(...);
bulk.deleteOne(...);
// etc...

bulk.execute();

La dernière instruction émet effectivement une seule requête MongoDB, vous avez donc moins de chance d’atteindre la limite des 1 000 opérations.

Résumé

MongoDB fournit un stockage flexible pour les applications telles que les systèmes de gestion de contenu, les carnets d’adresses et les réseaux sociaux où les structures de données strictes sont trop rigides et difficiles à définir. Les écritures de données sont rapides et le sharding sur plusieurs serveurs devient plus facile.

L’écriture d’applications utilisant une base de données MongoDB peut également être libératrice. Il est possible de stocker n’importe quelle donnée dans n’importe quel document de n’importe quelle collection à tout moment. Ceci est particulièrement pratique lorsque vous développez un prototype ou un produit minimum viable à l’aide de méthodologies agiles où les exigences évoluent au fil du temps.

Cela dit, les requêtes complexes peuvent être un défi et les concepts de dénormalisation sont difficiles à accepter lorsque vous migrez depuis le monde SQL.

MongoDB est moins adapté aux applications qui ont des exigences transactionnelles strictes où l’intégrité des données est essentielle, comme dans le cas des systèmes bancaires, comptables et de contrôle des stocks. Ceux-ci ont des champs de données identifiables qui doivent être conçus avant de commencer le codage.

Il existe de nombreux types d’applications entre ces deux extrêmes, si bien que le choix d’une base de données appropriée devient plus difficile. Heureusement, les bases de données NoSQL, dont MongoDB, ont commencé à adopter des options de type SQL, notamment les JOIN et les transactions.

À l’inverse, les bases de données SQL telles que MySQL et PostgreSQL offrent désormais des champs de données JSON de type NoSQL. Elles aussi peuvent mériter votre attention, mais comme toujours, le choix final vous appartient.