MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Liaison et création de jointures MongoDB à l'aide de SQL :partie 2

JOIN est l'une des principales fonctionnalités distinctes entre les bases de données SQL et NoSQL. Dans les bases de données SQL, nous pouvons effectuer une jointure entre deux tables au sein de la même base de données ou de bases de données différentes. Cependant, ce n'est pas le cas pour MongoDB car il permet les opérations JOIN entre deux collections dans la même base de données.

La façon dont les données sont présentées dans MongoDB rend presque impossible de les relier d'une collection à une autre, sauf lors de l'utilisation de fonctions de requête de script de base. MongoDB dénormalise les données en stockant les éléments associés dans un document séparé ou relie les données dans un autre document séparé.

On pourrait relier ces données en utilisant des références manuelles telles que le champ _id d'un document qui est enregistré dans un autre document en tant que référence. Néanmoins, il faut faire plusieurs requêtes pour récupérer certaines données requises, ce qui rend le processus un peu fastidieux.

On se résout donc à utiliser le concept JOIN qui facilite la mise en relation des données. L'opération JOIN dans MongoDB est obtenue grâce à l'utilisation de l'opérateur $lookup, qui a été introduit dans la version 3.2.

Opérateur $lookup

L'idée principale derrière le concept JOIN est d'obtenir une corrélation entre les données d'une collection à une autre. La syntaxe de base de l'opérateur $lookup est :

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

En ce qui concerne les connaissances SQL, nous savons toujours que le résultat d'une opération JOIN est une ligne séparée reliant tous les champs de la table locale et étrangère. Pour MongoDB, il s'agit d'un cas différent dans la mesure où les documents de résultat sont ajoutés sous la forme d'un tableau de documents de collection locale. Par exemple, prenons deux collections ; ‘étudiants’ et ‘unités’

étudiants

{"_id" : 1,"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5}

Unités

{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}
{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}
{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}

Nous pouvons récupérer les unités des étudiants avec leurs notes respectives en utilisant l'opérateur $lookup avec l'approche JOIN .i.e

db.getCollection('students').aggregate([{
$lookup:
    {
        from: "units",
        localField: "_id",
        foreignField : "_id",
        as: "studentUnits"
    }
}])

Ce qui nous donnera les résultats ci-dessous :

{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
    "studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14,"grade" : "B","score" : 7.5,
    "studentUnits" : [{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}]}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16,"grade" : "A","score" : 11.5,
    "studentUnits" : [{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}]}

Comme mentionné précédemment, si nous faisons un JOIN en utilisant le concept SQL, nous serons renvoyés avec des documents séparés dans la plate-forme Studio3T .i.e

SELECT *
  FROM students
    INNER JOIN units
      ON students._id = units._id

Est un équivalent de

db.getCollection("students").aggregate(
    [
        { 
            "$project" : {
                "_id" : NumberInt(0), 
                "students" : "$$ROOT"
            }
        }, 
        { 
            "$lookup" : {
                "localField" : "students._id", 
                "from" : "units", 
                "foreignField" : "_id", 
                "as" : "units"
            }
        }, 
        { 
            "$unwind" : {
                "path" : "$units", 
                "preserveNullAndEmptyArrays" : false
            }
        }
    ]
);

La requête SQL ci-dessus renverra les résultats ci-dessous :

{ "students" : {"_id" : NumberInt(1),"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}, 
    "units" : {"_id" : NumberInt(1),"Maths" : "A","English" : "A","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(2), "name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5 }, 
    "units" : {"_id" : NumberInt(2),"Maths" : "B","English" : "B","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(3),"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5},
"units" : {"_id" : NumberInt(3),"Maths" : "A","English" : "A","Science" : "A","History" : "A"}}

La durée d'exécution dépendra évidemment de la structure de votre requête. Par exemple, si vous avez de nombreux documents dans une collection par rapport à l'autre, vous devez effectuer l'agrégation à partir de la collection avec moins de documents, puis rechercher dans celle avec plus de documents. De cette façon, une recherche pour le champ choisi dans la collection de documents moindre est tout à fait optimale et prend moins de temps que d'effectuer plusieurs recherches pour un champ choisi dans la collection avec plus de documents. Il est donc conseillé de mettre la plus petite collection en premier.

Pour une base de données relationnelle, l'ordre des bases de données n'a pas d'importance puisque la plupart des interpréteurs SQL ont des optimiseurs, qui ont accès à des informations supplémentaires pour décider laquelle doit être la première.

Dans le cas de MongoDB, nous devrons utiliser un index pour faciliter l'opération JOIN. Nous savons tous que tous les documents MongoDB ont une clé _id qui, pour un DBM relationnel, peut être considérée comme la clé primaire. Un index offre une meilleure chance de réduire la quantité de données auxquelles il faut accéder en plus de prendre en charge l'opération lorsqu'il est utilisé dans la clé étrangère $lookup.

Dans le pipeline d'agrégation, pour utiliser un index, nous devons nous assurer que la $match est effectuée en premier lieu afin de filtrer les documents qui ne correspondent pas aux critères. Par exemple si nous voulons récupérer le résultat pour l'étudiant avec une valeur de champ _id égale à 1 :

select * 
from students 
  INNER JOIN units 
    ON students._id = units._id 
      WHERE students._id = 1;

Le code MongoDB équivalent que vous obtiendrez dans ce cas est :

db.getCollection("students").aggregate(
[{"$project" : { "_id" : NumberInt(0), "students" : "$$ROOT" }}, 
     {  "$lookup" : {"localField" : "students._id", "from" : "units",  "foreignField" : "_id",  "as" : "units"} }, 
     { "$unwind" : { "path" : "$units","preserveNullAndEmptyArrays" : false } }, 
      { "$match" : {"students._id" : NumberLong(1) }}
    ]);

Le résultat renvoyé pour la requête ci-dessus sera :

{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
    "studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}

Lorsque nous n'utilisons pas l'étape $match ou plutôt pas à la première étape, si nous vérifions avec la fonction d'explication, nous aurons également inclus l'étape COLLSCAN. Faire un COLLSCAN pour un grand nombre de documents prendra généralement beaucoup de temps. Nous nous résolvons ainsi à utiliser un champ d'index qui, dans la fonction d'explication, implique uniquement l'étape IXSCAN. Ce dernier a un avantage puisque nous vérifions sur un index dans les documents et ne parcourons pas tous les documents ; il ne faudra pas longtemps pour retourner les résultats. Vous pouvez avoir une structure de données différente comme :

{    "_id" : NumberInt(1), 
    "grades" : {"Maths" : "A", "English" : "A",  "Science" : "A", "History" : "B"
    }
}

Nous souhaiterons peut-être renvoyer les notes sous la forme d'entités différentes dans un tableau plutôt que d'un champ de notes intégré complet.

Après avoir écrit la requête SQL ci-dessus, nous devons modifier le code MongoDB résultant. Pour ce faire, cliquez sur l'icône de copie à droite comme ci-dessous pour copier le code d'agrégation :

Ensuite, allez dans l'onglet d'agrégation et sur le volet présenté, il y a une icône de collage, cliquez dessus pour coller le code.

Cliquez sur la ligne $match, puis sur la flèche verte vers le haut pour déplacer l'étape vers le haut en tant que première étape. Cependant, vous devrez d'abord créer un index dans votre collection comme :

db.students.createIndex(
   { _id: 1 },
   { name: studentId }
)

Vous obtiendrez l'exemple de code ci-dessous :

db.getCollection("students").aggregate(
    [{ "$match" : {"_id" : 1.0}},
  { "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}}, 
      { "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}}, 
      { "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}}
    ]
Plusieursnines Devenez un administrateur de base de données MongoDB – Amener MongoDB en productionDécouvrez ce que vous devez savoir pour déployer, surveiller, gérer et faire évoluer MongoDBDélécharger gratuitement

Avec ce code, nous obtiendrons le résultat ci-dessous :

{ "students" : {"_id" : NumberInt(1), "name" : "James Washington","age" : 15.0,"grade" : "A", "score" : 10.5}, 
    "units" : {"_id" : NumberInt(1), "grades" : {"Maths" : "A", "English" : "A", "Science" : "A",  "History" : "B"}}}

Mais tout ce dont nous avons besoin est d'avoir les notes comme une entité de document distincte dans le document renvoyé et non comme dans l'exemple ci-dessus. Nous allons donc ajouter l'étape $addfields d'où le code ci-dessous.

db.getCollection("students").aggregate(
    [{ "$match" : {"_id" : 1.0}},
  { "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}}, 
      { "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}}, 
      { "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}},
      { "$addFields" : {"units" : "$units.grades"} }]

Les documents résultants seront alors :

{
"students" : {"_id" : NumberInt(1), "name" : "James Washington", "grade" : "A","score" : 10.5}, 
     "units" : {"Maths" : "A", "English" : "A",  "Science" : "A", "History" : "B"}
}

Les données renvoyées sont assez soignées, car nous avons éliminé les documents intégrés de la collection des unités en tant que champ séparé.

Dans notre prochain tutoriel, nous allons nous intéresser aux requêtes à plusieurs jointures.