web-dev-qa-db-fra.com

Recherche sur plusieurs collections dans MongoDB

Je connais la théorie de MongoDB et le fait qu'il ne prend pas en charge les jointures, et que je devrais utiliser des documents intégrés ou dénormaliser autant que possible, mais voici:

J'ai plusieurs documents, tels que:

  • Utilisateurs, qui intègrent Banlieues, mais ont aussi: prénom, nom
  • Banlieues, qui intègrent les États
  • L'enfant, qui intègre l'école, appartient à un utilisateur, mais a également: prénom, nom

Exemple:

Users:
{ _id: 1, first_name: 'Bill', last_name: 'Gates', suburb: 1 }
{ _id: 2, first_name: 'Steve', last_name: 'Jobs', suburb: 3 }

Suburb:
{ _id: 1, name: 'Suburb A', state: 1 }
{ _id: 2, name: 'Suburb B', state: 1 }
{ _id: 3, name: 'Suburb C', state: 3 }

State:
{ _id: 1, name: 'LA' }
{ _id: 3, name: 'NY' }

Child:
{ _id: 1, _user_id: 1, first_name: 'Little Billy', last_name: 'Gates' }
{ _id: 2, _user_id: 2, first_name: 'Little Stevie', last_name: 'Jobs' }

La recherche que je dois implémenter est sur:

  • prénom, nom des utilisateurs et de l'enfant
  • État des utilisateurs

Je sais que je dois faire plusieurs requêtes pour le faire, mais comment y parvenir? Avec mapReduce ou global?

Pouvez-vous indiquer une solution s'il vous plaît?

J'ai essayé d'utiliser mapReduce mais cela ne m'a pas permis d'avoir des documents des utilisateurs qui contenaient un state_id, c'est pourquoi je l'ai apporté ici.

37
Adrian Istrate

Cette réponse est dépassée. Depuis la version 3.2, MongoDB a un support limité pour les jointures externes gauches avec l'opérateur d'agrégation $ lookup

MongoDB ne fait pas de requêtes qui s'étendent sur plusieurs collections - période. Lorsque vous devez joindre des données de plusieurs collections, vous devez le faire au niveau de l'application en effectuant plusieurs requêtes.

  1. Collection de requêtes A
  2. Obtenez les clés secondaires du résultat et placez-les dans un tableau
  3. Collection de requêtes B passant ce tableau comme valeur de $ in-operator
  4. Joignez les résultats des deux requêtes par programme sur la couche application

Devoir le faire devrait être plutôt l'exception que la norme. Lorsque vous devez fréquemment émuler des JOIN comme cela, cela signifie que vous pensez toujours trop relationnel lorsque vous concevez votre schéma de base de données ou que vos données ne sont tout simplement pas adaptées au concept de stockage basé sur des documents de MongoDB.

38
Philipp

Vous trouverez MongoDB plus facile à comprendre si vous adoptez une approche dénormalisée de la conception de schémas. Autrement dit, vous souhaitez structurer vos documents de la manière dont l'application cliente les comprend. Essentiellement, vous modélisez vos documents comme objets de domaine avec lesquels l'application s'applique. Les jointures deviennent moins importantes lorsque vous modélisez vos données de cette façon. Considérez comment j'ai dénormalisé vos données en une seule collection:

{  
    _id: 1, 
    first_name: 'Bill', 
    last_name: 'Gates', 
    suburb: 'Suburb A',
    state: 'LA',
    child : [ 3 ]
}

{ 
    _id: 2, 
    first_name: 'Steve', 
    last_name: 'Jobs', 
    suburb: 'Suburb C',
    state 'NY',
    child: [ 4 ] 
}
{ 
    _id: 3, 
    first_name: 'Little Billy', 
    last_name: 'Gates',
    suburb: 'Suburb A',
    state: 'LA',
    parent : [ 1 ]
}

{
    _id: 4, 
    first_name: 'Little Stevie', 
    last_name: 'Jobs'
    suburb: 'Suburb C',
    state 'NY',
    parent: [ 2 ]
}

Le premier avantage est que ce schéma est beaucoup plus facile à interroger. De plus, les mises à jour des champs d'adresse sont désormais cohérentes avec l'entité Personne individuelle, car les champs sont incorporés dans un seul document. Remarquez également la relation bidirectionnelle entre le parent et les enfants? Cela fait de cette collection plus qu'une simple collection de personnes individuelles. Les relations parent-enfant signifient que cette collection est aussi un graphique social . Voici quelques ressources qui peuvent vous être utiles lorsque vous pensez à conception de schéma dans MongoDB .

16
blimpyacht

Voici une fonction JavaScript qui renverra un tableau de tous les enregistrements correspondant aux critères spécifiés, en recherchant dans toutes les collections de la base de données actuelle:

function searchAll(query,fields,sort) {
    var all = db.getCollectionNames();
    var results = [];
    for (var i in all) {
        var coll = all[i];
        if (coll == "system.indexes") continue;
        db[coll].find(query,fields).sort(sort).forEach(
            function (rec) {results.Push(rec);} );
    }
    return results;
}

Depuis Mongo Shell, vous pouvez copier/coller la fonction, puis l'appeler comme ceci:

> var recs = searchAll ({filename: {$ regex: '. pdf $'}}, {moddate: 1, filename: 1, _id: 0}, {filename: 1})> recs

10
Brian Moquin

Alors maintenant, rejoindre est possible dans mongodb et vous pouvez le faire en utilisant $lookup et $facet agrégation ici et qui est probablement le meilleur moyen de trouver dans plusieurs collections

db.collection.aggregate([
  { "$limit": 1 },
  { "$facet": {
    "c1": [
      { "$lookup": {
        "from": Users.collection.name,
        "pipeline": [
          { "$match": { "first_name": "your_search_data" } }
        ],
        "as": "collection1"
      }}
    ],
    "c2": [
      { "$lookup": {
        "from": State.collection.name,
        "pipeline": [
          { "$match": { "name": "your_search_data" } }
        ],
        "as": "collection2"
      }}
    ],
    "c3": [
      { "$lookup": {
        "from": State.collection.name,
        "pipeline": [
          { "$match": { "name": "your_search_data" } }
        ],
        "as": "collection3"
      }}
    ]
  }},
  { "$project": {
    "data": {
      "$concatArrays": [ "$c1", "$c2", "$c3" ]
    }
  }},
  { "$unwind": "$data" },
  { "$replaceRoot": { "newRoot": "$data" } }
])
7
Ashh

Sur la base de @ brian-moquin et d'autres, j'ai créé un ensemble de fonctions pour rechercher des collections entières avec des clés (champs) entières par mot-clé simple.

C'est dans mon Gist; https://Gist.github.com/fkiller/005dc8a07eaa3321110b3e5753dda71b

Pour plus de détails, j'ai d'abord créé une fonction pour rassembler toutes les clés.

function keys(collectionName) {
    mr = db.runCommand({
        'mapreduce': collectionName,
        'map': function () {
            for (var key in this) { emit(key, null); }
        },
        'reduce': function (key, stuff) { return null; },
        'out': 'my_collection' + '_keys'
    });
    return db[mr.result].distinct('_id');
}

Puis une de plus pour générer la requête $or À partir du tableau de clés.

function createOR(fieldNames, keyword) {
    var query = [];
    fieldNames.forEach(function (item) {
        var temp = {};
        temp[item] = { $regex: '.*' + keyword + '.*' };
        query.Push(temp);
    });
    if (query.length == 0) return false;
    return { $or: query };
}

Vous trouverez ci-dessous une fonction pour rechercher une seule collection.

function findany(collection, keyword) {
    var query = createOR(keys(collection.getName()));
    if (query) {
        return collection.findOne(query, keyword);
    } else {
        return false;
    }
}

Et enfin une fonction de recherche pour toutes les collections.

function searchAll(keyword) {
    var all = db.getCollectionNames();
    var results = [];
    all.forEach(function (collectionName) {
        print(collectionName);
        if (db[collectionName]) results.Push(findany(db[collectionName], keyword));
    });
    return results;
}

Vous pouvez simplement charger toutes les fonctions dans la console Mongo et exécuter searchAll('any keyword')

1
Minime