web-dev-qa-db-fra.com

Comment puis-je exécuter l'équivalent SQL Join dans MongoDB?

Comment puis-je exécuter l'équivalent SQL Join dans MongoDB?

Par exemple, disons que vous avez deux collections (utilisateurs et commentaires) et que je veux extraire tous les commentaires avec pid = 444 ainsi que les informations sur l'utilisateur pour chacun.

comments
  { uid:12345, pid:444, comment="blah" }
  { uid:12345, pid:888, comment="asdf" }
  { uid:99999, pid:444, comment="qwer" }

users
  { uid:12345, name:"john" }
  { uid:99999, name:"mia"  }

Existe-t-il un moyen d'extraire tous les commentaires avec un certain champ (par exemple. ... find ({pid: 444})) et les informations utilisateur associées à chaque commentaire en une fois?

À l'heure actuelle, je reçois d'abord les commentaires qui correspondent à mes critères, puis je calcule tous les ID utilisateur de cet ensemble de résultats, récupère les objets utilisateur et les fusionne avec les résultats du commentaire. On dirait que je me trompe.

455
The Unknown

Depuis Mongo 3.2, les réponses à cette question ne sont généralement plus correctes. Le nouvel opérateur $ lookup ajouté au pipeline d'agrégation est essentiellement identique à une jointure externe gauche:

https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup

De la docs:

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

Bien sûr, Mongo n'est pas une base de données relationnelle, et les développeurs veillent à recommander des cas d'utilisation spécifiques pour $ lookup, mais au moins à partir de la version 3.2 est maintenant possible avec MongoDB.

279
Clayton Gulick

Cette page sur le site officiel mongodb adresse exactement cette question:

http://docs.mongodb.org/ecosystem/tutorial/model-data-for-Ruby-on-Rails/

Lorsque nous affichons notre liste d’histoires, nous devons indiquer le nom de l’utilisateur qui a posté l’histoire. Si nous utilisions une base de données relationnelle, nous pourrions effectuer une jointure sur les utilisateurs et les magasins, et obtenir tous nos objets dans une seule requête. Mais MongoDB ne supporte pas les jointures et nécessite donc parfois un peu de dénormalisation. Ici, cela signifie mettre en cache l'attribut 'nom d'utilisateur'.

Les puristes relationnels peuvent déjà se sentir mal à l'aise, comme si nous enfreignions une loi universelle. Mais gardons à l’esprit que les collections MongoDB ne sont pas équivalentes aux tables relationnelles; chacun sert un objectif de conception unique. Une table normalisée fournit un bloc de données atomique et isolé. Un document, cependant, représente plus étroitement un objet dans son ensemble. Dans le cas d'un site de nouvelles sociales, on peut affirmer qu'un nom d'utilisateur est intrinsèque à l'histoire en cours de publication.

138
William Stein

Nous pouvons fusionner/joindre toutes les données dans une seule collection avec une fonction simple en quelques lignes à l’aide de la console cliente mongodb. Nous pouvons maintenant exécuter la requête souhaitée. Ci-dessous un exemple complet,

.- Auteurs:

db.authors.insert([
    {
        _id: 'a1',
        name: { first: 'orlando', last: 'becerra' },
        age: 27
    },
    {
        _id: 'a2',
        name: { first: 'mayra', last: 'sanchez' },
        age: 21
    }
]);

.- Catégories:

db.categories.insert([
    {
        _id: 'c1',
        name: 'sci-fi'
    },
    {
        _id: 'c2',
        name: 'romance'
    }
]);

.- Livres

db.books.insert([
    {
        _id: 'b1',
        name: 'Groovy Book',
        category: 'c1',
        authors: ['a1']
    },
    {
        _id: 'b2',
        name: 'Java Book',
        category: 'c2',
        authors: ['a1','a2']
    },
]);

.- Prêt de livres

db.lendings.insert([
    {
        _id: 'l1',
        book: 'b1',
        date: new Date('01/01/11'),
        lendingBy: 'jose'
    },
    {
        _id: 'l2',
        book: 'b1',
        date: new Date('02/02/12'),
        lendingBy: 'maria'
    }
]);

.- La magie:

db.books.find().forEach(
    function (newBook) {
        newBook.category = db.categories.findOne( { "_id": newBook.category } );
        newBook.lendings = db.lendings.find( { "book": newBook._id  } ).toArray();
        newBook.authors = db.authors.find( { "_id": { $in: newBook.authors }  } ).toArray();
        db.booksReloaded.insert(newBook);
    }
);

.- Obtenir les nouvelles données de collection:

db.booksReloaded.find().pretty()

.- Réponse :)

{
    "_id" : "b1",
    "name" : "Groovy Book",
    "category" : {
        "_id" : "c1",
        "name" : "sci-fi"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        }
    ],
    "lendings" : [
        {
            "_id" : "l1",
            "book" : "b1",
            "date" : ISODate("2011-01-01T00:00:00Z"),
            "lendingBy" : "jose"
        },
        {
            "_id" : "l2",
            "book" : "b1",
            "date" : ISODate("2012-02-02T00:00:00Z"),
            "lendingBy" : "maria"
        }
    ]
}
{
    "_id" : "b2",
    "name" : "Java Book",
    "category" : {
        "_id" : "c2",
        "name" : "romance"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        },
        {
            "_id" : "a2",
            "name" : {
                "first" : "mayra",
                "last" : "sanchez"
            },
            "age" : 21
        }
    ],
    "lendings" : [ ]
}

J'espère que ces lignes peuvent vous aider.

132
Orlando Becerra

Vous devez le faire comme vous l'avez décrit. MongoDB est une base de données non relationnelle et ne prend pas en charge les jointures.

39
Otto Allmendinger

Voici un exemple de "rejoindre" * Acteurs et Collections de films :

https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

Il utilise la méthode .mapReduce()

* join - alternative à la jointure dans des bases de données orientées document

17
antitoxic

Comme d'autres l'ont souligné, vous essayez de créer une base de données relationnelle à partir d'une base de données non relationnelle, ce que vous ne voulez vraiment pas faire, mais de toute façon, si vous devez le faire, voici une solution que vous pouvez utiliser. Nous faisons d’abord une recherche foreach sur la collection A (ou les utilisateurs de votre cas), puis nous obtenons chaque élément sous forme d’objet. peut le trouver alors nous avons une correspondance et nous pouvons imprimer ou faire quelque chose avec elle. J'espère que cela vous aide et bonne chance :)

db.users.find().forEach(
function (object) {
    var commonInBoth=db.comments.findOne({ "uid": object.uid} );
    if (commonInBoth != null) {
        printjson(commonInBoth) ;
        printjson(object) ;
    }else {
        // did not match so we don't care in this case
    }
});
16
grepit

Avec la bonne combinaison de $ lookup , $ project et $ match , vous pouvez joindre plusieurs tables sur plusieurs paramètres. C'est parce qu'ils peuvent être enchaînés plusieurs fois.

Supposons que nous voulions faire ce qui suit ( référence )

SELECT S.* FROM LeftTable S
LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND 
S.MOB IS NOT NULL

Étape 1: Liez toutes les tables

vous pouvez $ rechercher autant de tables que vous le souhaitez.

$ lookup - un pour chaque table de la requête

$ déroulement - car les données sont correctement dénormalisées, sinon elles sont encapsulées dans des tableaux.

Code python ..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"}

                        ])

Étape 2: Définir toutes les conditions

$ project : définissez toutes les instructions conditionnelles ici, ainsi que toutes les variables que vous souhaitez sélectionner.

Code Python ..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }}
                        ])

Étape 3: Joignez toutes les conditions

$ match - associez toutes les conditions à l'aide de OR ou AND etc. Il peut y en avoir des multiples.

$ project : annuler toutes les conditions

Code Python ..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "$R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }},

                        # join all conditionals

                        {"$match": {
                          "$and": [
                            {"R.TIM": {"$gt": 0}}, 
                            {"MOB": {"$exists": True}},
                            {"midEq": {"$eq": True}}
                        ]}},

                        # undefine conditionals

                        {"$project": {
                          "midEq": 0
                        }}

                        ])

De la même manière, toute combinaison de tables, de conditions et de jointures peut être réalisée.

13
Shaurabh Bharti

Cela dépend de ce que vous essayez de faire.

Vous l'avez actuellement configurée en tant que base de données normalisée, ce qui est bien, et la façon dont vous le faites est appropriée.

Cependant, il y a d'autres façons de le faire.

Vous pouvez avoir une collection de publications contenant des commentaires intégrés pour chaque publication, avec des références aux utilisateurs que vous pouvez interroger de manière itérative. Vous pouvez stocker le nom de l'utilisateur avec les commentaires, vous pouvez les stocker tous dans un seul document.

Le problème avec NoSQL est qu’il est conçu pour des schémas flexibles et une lecture et une écriture très rapides. Dans une ferme Big Data typique, la base de données est le plus gros goulet d'étranglement. Vous avez moins de moteurs de base de données que d'applications et de serveurs frontaux ... ils sont plus chers mais plus puissants. De plus, l'espace disque est relativement bon marché. La normalisation découle du concept d’économie d’espace, mais elle entraîne un coût pour que vos bases de données effectuent des jointures complexes et vérifie l’intégrité des relations en effectuant des opérations en cascade. Tout cela évite aux développeurs des soucis s’ils conçoivent correctement la base de données.

Avec NoSQL, si vous acceptez le fait que la redondance et l’espace de stockage ne sont pas un problème en raison de leur coût (temps de processeur requis pour effectuer les mises à jour et coûts du disque dur pour stocker des données supplémentaires), la dénormalisation ne pose pas de problème (pour les baies intégrées des centaines de milliers d’articles, cela peut être un problème de performances, mais la plupart du temps, ce n’est pas un problème). De plus, vous aurez plusieurs serveurs d'applications et serveurs frontaux pour chaque cluster de base de données. Demandez-leur de faire le gros des jointures et laissez les serveurs de base de données s'en tenir à la lecture et à l'écriture.

TL; DR: Ce que tu fais est bien et il y a d'autres façons de le faire. Consultez les modèles de modèle de données de la documentation mongodb pour quelques exemples intéressants. http://docs.mongodb.org/manual/data-modeling/

10
Snowburnt

Vous pouvez rejoindre deux collections dans Mongo à l’aide de la fonction de recherche proposée dans la version 3.2. Dans votre cas, la requête serait

db.comments.aggregate({
    $lookup:{
        from:"users",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

ou vous pouvez également rejoindre en ce qui concerne les utilisateurs alors il y aura un petit changement comme indiqué ci-dessous.

db.users.aggregate({
    $lookup:{
        from:"comments",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

Cela fonctionnera de la même manière que les jointures gauche et droite en SQL.

10
jarry jafery

Il existe une spécification prise en charge par de nombreux pilotes, appelée DBRef.

DBRef est une spécification plus formelle pour la création de références entre documents. DBRefs (généralement) inclut un nom de collection ainsi qu'un identifiant d'objet. La plupart des développeurs n'utilisent DBRef que si la collection peut changer d'un document à l'autre. Si votre collection référencée sera toujours identique, les références de manuel décrites ci-dessus sont plus efficaces.

Extrait de la documentation MongoDB: Modèles de données> Référence de modèle de données> Références de base de données

9
Pickels

Avant .2.6, Mongodb ne supporte pas les requêtes de jointure comme mysql. ci-dessous solution qui fonctionne pour vous.

 db.getCollection('comments').aggregate([
        {$match : {pid : 444}},
        {$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}},
   ])
6
Anish Agarwal

$ lookup (agrégation)

Effectue une jointure externe gauche sur une collection non durcie dans la même base de données pour filtrer les documents de la collection "jointe" à des fins de traitement. L'étape $ lookup ajoute à chaque document d'entrée un nouveau champ de tableau dont les éléments sont les documents correspondants de la collection "jointe". L'étape $ lookup fait passer ces documents remodelés à l'étape suivante. L'étape $ lookup a les syntaxes suivantes:

Match d'égalité

Pour effectuer une correspondance d'égalité entre un champ des documents d'entrée et un champ des documents de la collection "jointe", l'étape $ lookup a la syntaxe suivante:

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

L'opération correspondrait à l'instruction pseudo-SQL suivante:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
                               FROM <collection to join>
                               WHERE <pipeline> );

RL Mongo

6
GoutamS

Vous pouvez exécuter des requêtes SQL, y compris rejoindre sur MongoDB avec mongo_fdw de Postgres.

3
metdos

MongoDB n'autorise pas les jointures, mais vous pouvez utiliser des plugins pour gérer cela. Vérifiez le plugin mongo-join. C'est le meilleur et je l'ai déjà utilisé. Vous pouvez l’installer à l’aide de npm directement comme ceci npm install mongo-join. Vous pouvez consulter le documentation complète avec des exemples .

(++) outil vraiment utile pour joindre des collections (N)

(-) nous pouvons appliquer des conditions uniquement au niveau supérieur de la requête

Exemple

var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server;
db.open(function (err, Database) {
    Database.collection('Appoint', function (err, Appoints) {

        /* we can put conditions just on the top level */
        Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date },
            full_date :{ $lte: end_date }}, function (err, cursor) {
            var join = new Join(Database).on({
                field: '_id_Doctor', // <- field in Appoints document
                to: '_id',         // <- field in User doc. treated as ObjectID automatically.
                from: 'User'  // <- collection name for User doc
            }).on({
                field: '_id_Patient', // <- field in Appoints doc
                to: '_id',         // <- field in User doc. treated as ObjectID automatically.
                from: 'User'  // <- collection name for User doc
            })
            join.toArray(cursor, function (err, joinedDocs) {

                /* do what ever you want here */
                /* you can fetch the table and apply your own conditions */
                .....
                .....
                .....


                resp.status(200);
                resp.json({
                    "status": 200,
                    "message": "success",
                    "Appoints_Range": joinedDocs,


                });
                return resp;


            });

    });
2
Amine_Dev

Vous pouvez le faire en utilisant le pipeline d'agrégation, mais c'est difficile de l'écrire vous-même.

Vous pouvez utiliser mongo-join-query pour créer automatiquement le pipeline d'agrégation à partir de votre requête.

Voici à quoi ressemblerait votre requête:

_const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");

joinQuery(
    mongoose.models.Comment,
    {
        find: { pid:444 },
        populate: ["uid"]
    },
    (err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);
_

Votre résultat aurait l'objet utilisateur dans le champ uid et vous pouvez lier autant de niveaux que vous le souhaitez. Vous pouvez renseigner la référence sur l'utilisateur, qui fait référence à une équipe, qui fait référence à autre chose, etc.

Disclaimer : J'ai écrit _mongo-join-query_ pour résoudre ce problème précis.

1
Marcelo Lazaroni

playORM peut le faire pour vous en utilisant S-SQL (Scalable SQL) qui ajoute simplement un partitionnement de sorte que vous puissiez faire des jointures au sein de partitions.

0
Dean Hiller