Obtenir les noms de toutes les clés de la collection

322

J'aimerais obtenir les noms de toutes les clés d'une collection MongoDB.

Par exemple, à partir de ceci:

db.things.insert( { type : ['dog', 'cat'] } );
db.things.insert( { egg : ['cat'] } );
db.things.insert( { type : [] } );
db.things.insert( { hello : []  } );

J'aimerais obtenir les clés uniques:

type, egg, hello
Steve
la source

Réponses:

346

Vous pouvez le faire avec MapReduce:

mr = db.runCommand({
  "mapreduce" : "my_collection",
  "map" : function() {
    for (var key in this) { emit(key, null); }
  },
  "reduce" : function(key, stuff) { return null; }, 
  "out": "my_collection" + "_keys"
})

Exécutez ensuite distinct sur la collection résultante afin de trouver toutes les clés:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
Kristina
la source
2
Salut! Je viens de publier une suite à cette question demandant comment faire fonctionner cet extrait de code même avec des clés situées à des niveaux plus profonds dans la structure de données ( stackoverflow.com/questions/2997004/… ).
Andrea Fiore
1
@kristina: Comment est-il possible que j'obtienne des choses entières avec les clés lorsque j'utilise ceci sur la collection des choses . Il semble lié au mécanisme de l'historique parce que j'ai des choses que j'ai modifiées dans le passé ..
Shawn
3
Je sais que c'est un vieux fil, mais je semble avoir un besoin similaire. J'utilise le pilote natif nodejs mongodb. La collection temporaire résultante semble toujours vide. J'utilise la fonction mapreduce dans la classe de collection pour cela. N'est-ce pas possible?
Deepak
6
Cela peut être évident, mais si vous voulez obtenir une liste de toutes les clés uniques dans un sous-document, modifiez simplement cette ligne:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne
3
Au lieu d'enregistrer dans une collection puis de l'exécuter séparément, j'utilise map ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley
203

Avec la réponse de Kristina comme source d'inspiration, j'ai créé un outil open source appelé Variety qui fait exactement cela: https://github.com/variety/variety

James Cropcho
la source
13
Ceci est un outil fantastique, félicitations. Il fait exactement ce que la question demande et peut être configuré avec des limites, une profondeur, etc. Recommandé par tous ceux qui suivent.
Paul Biggar
74

Vous pouvez utiliser l' agrégation avec une nouvelle $objectToArrraydans la 3.4.4version pour convertir toutes les clés en haut et la paire de valeurs dans des tableaux de documents , suivi par $unwindet $group avec $addToSetpour obtenir les clés distinctes de collection.

$$ROOT pour référencer le document de niveau supérieur.

db.things.aggregate([
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$unwind":"$arrayofkeyvalue"},
  {"$group":{"_id":null,"allkeys":{"$addToSet":"$arrayofkeyvalue.k"}}}
])

Vous pouvez utiliser la requête ci-dessous pour obtenir des clés dans un seul document.

db.things.aggregate([
  {"$match":{_id: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])
Sagar Veeram
la source
20
C'est vraiment la meilleure réponse. Résout le problème sans impliquer un autre langage de programmation ou package, et fonctionne avec tous les pilotes qui prennent en charge le cadre agrégé (même Meteor!)
Micah Henning
2
Si vous souhaitez renvoyer un tableau plutôt qu'un curseur contenant une seule entrée de carte avec une clé "allkeys", vous pouvez ajouter .next()["allkeys"]à la commande (en supposant que la collection a au moins un élément).
M. Justin
19

Essaye ça:

doc=db.thinks.findOne();
for (key in doc) print(key);
Carlos LM
la source
49
réponse incorrecte car cela ne génère que des champs pour un seul document dans une collection - les autres peuvent tous avoir des clés complètement différentes.
Asya Kamsky
15
C'est toujours la réponse la plus utile pour moi, étant un simple minimum raisonnable.
Boris Burkov
11
Ce n'est pas utile? En quoi est-il utile s'il vous donne la mauvaise réponse?
Zlatko
4
Le contexte montre ce qui est utile: si les données sont normalisées (ex. Origines du fichier CSV), c'est utile ... Car les données importées de SQL sont utiles.
Peter Krauss du
5
ce n'est pas une bonne réponse, c'est une réponse sur la façon d'obtenir les clés d' un élément de la collection pas toutes les clés de la collection!
yonatan
16

Si votre collection cible n'est pas trop grande, vous pouvez essayer ceci sous le client shell mongo:

var allKeys = {};

db.YOURCOLLECTION.find().forEach(function(doc){Object.keys(doc).forEach(function(key){allKeys[key]=1})});

allKeys;
Li Chunlin
la source
ici comment je peux donner regExp pour des clés particulières si je veux voir?
TB.M
@ TB.M, vous pouvez essayer ceci: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { allKeys [key] = 1}})});
Li Chunlin
que signifie test ici? pouvez-vous expliquer?
TB.M
14

Une solution nettoyée et réutilisable avec pymongo:

from pymongo import MongoClient
from bson import Code

def get_keys(db, collection):
    client = MongoClient()
    db = client[db]
    map = Code("function() { for (var key in this) { emit(key, null); } }")
    reduce = Code("function(key, stuff) { return null; }")
    result = db[collection].map_reduce(map, reduce, "myresults")
    return result.distinct('_id')

Usage:

get_keys('dbname', 'collection')
>> ['key1', 'key2', ... ]
Ingo Fischer
la source
1
Fonctionne très bien. J'ai enfin résolu mon problème .... c'est la solution la plus simple que j'ai vue dans le débordement de pile ..
Smack Alpha
Et pour filtrer par type, il suffit d'ajouter par exemple if (typeof(this[key]) == 'number')avant emit(key, null).
Skippy le Grand Gourou
10

Utilisation de python. Renvoie l'ensemble de toutes les clés de niveau supérieur de la collection:

#Using pymongo and connection named 'db'

reduce(
    lambda all_keys, rec_keys: all_keys | set(rec_keys), 
    map(lambda d: d.keys(), db.things.find()), 
    set()
)
Laizer
la source
1
J'ai trouvé que cela fonctionne, mais quelle est son efficacité par rapport à une requête Mongod brute?
Jesus Gomez
1
Je suis tout à fait sûr que c'est extrêmement inefficace par rapport à faire cela directement dans Mongodb
Ingo Fischer
9

Voici l'exemple travaillé en Python: Cet exemple renvoie les résultats en ligne.

from pymongo import MongoClient
from bson.code import Code

mapper = Code("""
    function() {
                  for (var key in this) { emit(key, null); }
               }
""")
reducer = Code("""
    function(key, stuff) { return null; }
""")

distinctThingFields = db.things.map_reduce(mapper, reducer
    , out = {'inline' : 1}
    , full_response = True)
## do something with distinctThingFields['results']
BobHy
la source
9

Si vous utilisez mongodb 3.4.4 et supérieur, vous pouvez utiliser l'agrégation ci-dessous en utilisant $objectToArrayet l' $groupagrégation

db.collection.aggregate([
  { "$project": {
    "data": { "$objectToArray": "$$ROOT" }
  }},
  { "$project": { "data": "$data.k" }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": null,
    "keys": { "$addToSet": "$data" }
  }}
])

Voici l' exemple de travail

Ashh
la source
C'est la meilleure réponse. Vous pouvez également utiliser $matchau début du pipeline d'agrégation pour obtenir uniquement les clés des documents qui correspondent à une ou plusieurs conditions.
RonquilloAeon
5

Je suis surpris, personne ici n'a ans en utilisant simple javascriptet Setlogique pour filtrer automatiquement les valeurs en double, exemple simple sur shell mongo comme ci-dessous:

var allKeys = new Set()
db.collectionName.find().forEach( function (o) {for (key in o ) allKeys.add(key)})
for(let key of allKeys) print(key)

Cela imprimera toutes les clés uniques possibles dans le nom de la collection: collectionName .

krishna Prasad
la source
3

Cela fonctionne bien pour moi:

var arrayOfFieldNames = [];

var items = db.NAMECOLLECTION.find();

while(items.hasNext()) {
  var item = items.next();
  for(var index in item) {
    arrayOfFieldNames[index] = index;
   }
}

for (var index in arrayOfFieldNames) {
  print(index);
}
ackuser
la source
3

Je pense que la meilleure façon de le faire comme mentionné ici est dans mongod 3.4.4+ mais sans utiliser l' $unwindopérateur et en utilisant seulement deux étapes dans le pipeline. Au lieu de cela, nous pouvons utiliser les opérateurs $mergeObjectset $objectToArray.

À l' $groupétape, nous utilisons l' $mergeObjectsopérateur pour renvoyer un seul document dont la clé / valeur provient de tous les documents de la collection.

Vient ensuite le $projectlieu d'utilisation $mapet $objectToArrayle retour des clés.

let allTopLevelKeys =  [
    {
        "$group": {
            "_id": null,
            "array": {
                "$mergeObjects": "$$ROOT"
            }
        }
    },
    {
        "$project": {
            "keys": {
                "$map": {
                    "input": { "$objectToArray": "$array" },
                    "in": "$$this.k"
                }
            }
        }
    }
];

Maintenant, si nous avons des documents imbriqués et que nous voulons également obtenir les clés, c'est faisable. Pour plus de simplicité, considérons un document avec un document incorporé simple qui ressemble à ceci:

{field1: {field2: "abc"}, field3: "def"}
{field1: {field3: "abc"}, field4: "def"}

Le pipeline suivant fournit toutes les clés (champ1, champ2, champ3, champ4).

let allFistSecondLevelKeys = [
    {
        "$group": {
            "_id": null,
            "array": {
                "$mergeObjects": "$$ROOT"
            }
        }
    },
    {
        "$project": {
            "keys": {
                "$setUnion": [
                    {
                        "$map": {
                            "input": {
                                "$reduce": {
                                    "input": {
                                        "$map": {
                                            "input": {
                                                "$objectToArray": "$array"
                                            },
                                            "in": {
                                                "$cond": [
                                                    {
                                                        "$eq": [
                                                            {
                                                                "$type": "$$this.v"
                                                            },
                                                            "object"
                                                        ]
                                                    },
                                                    {
                                                        "$objectToArray": "$$this.v"
                                                    },
                                                    [
                                                        "$$this"
                                                    ]
                                                ]
                                            }
                                        }
                                    },
                                    "initialValue": [

                                    ],
                                    "in": {
                                        "$concatArrays": [
                                            "$$this",
                                            "$$value"
                                        ]
                                    }
                                }
                            },
                            "in": "$$this.k"
                        }
                    }
                ]
            }
        }
    }
]

Avec un petit effort, nous pouvons obtenir la clé de tous les sous-documents dans un champ tableau où les éléments sont également des objets.

styvane
la source
Oui $unwindva exploser la collection (no.of fields * no.of docs), nous pouvons éviter cela en utilisant $mergeObjectssur toutes les versions> 3.6.. A fait la même chose, J'aurais dû voir cette réponse avant, ma vie aurait été plus facile de cette façon ( -_-)
whoami
3

Peut-être légèrement hors sujet, mais vous pouvez imprimer de manière récursive toutes les clés / champs d'un objet:

function _printFields(item, level) {
    if ((typeof item) != "object") {
        return
    }
    for (var index in item) {
        print(" ".repeat(level * 4) + index)
        if ((typeof item[index]) == "object") {
            _printFields(item[index], level + 1)
        }
    }
}

function printFields(item) {
    _printFields(item, 0)
}

Utile lorsque tous les objets d'une collection ont la même structure.

qed
la source
1

Pour obtenir une liste de toutes les clés moins _id, envisagez d'exécuter le pipeline d'agrégation suivant:

var keys = db.collection.aggregate([
    { "$project": {
       "hashmaps": { "$objectToArray": "$$ROOT" } 
    } }, 
    { "$project": {
       "fields": "$hashmaps.k"
    } },
    { "$group": {
        "_id": null,
        "fields": { "$addToSet": "$fields" }
    } },
    { "$project": {
            "keys": {
                "$setDifference": [
                    {
                        "$reduce": {
                            "input": "$fields",
                            "initialValue": [],
                            "in": { "$setUnion" : ["$$value", "$$this"] }
                        }
                    },
                    ["_id"]
                ]
            }
        }
    }
]).toArray()[0]["keys"];
chridam
la source
0

J'essayais d'écrire dans nodejs et j'ai finalement trouvé ceci:

db.collection('collectionName').mapReduce(
function() {
    for (var key in this) {
        emit(key, null);
    }
},
function(key, stuff) {
    return null;
}, {
    "out": "allFieldNames"
},
function(err, results) {
    var fields = db.collection('allFieldNames').distinct('_id');
    fields
        .then(function(data) {
            var finalData = {
                "status": "success",
                "fields": data
            };
            res.send(finalData);
            delteCollection(db, 'allFieldNames');
        })
        .catch(function(err) {
            res.send(err);
            delteCollection(db, 'allFieldNames');
        });
 });

Après avoir lu la collection nouvellement créée "allFieldNames", supprimez-la.

db.collection("allFieldNames").remove({}, function (err,result) {
     db.close();
     return; 
});
Gautam
la source
0

Selon la documentation de mongoldb , une combinaison dedistinct

Recherche les valeurs distinctes d'un champ spécifié dans une seule collection ou vue et renvoie les résultats dans un tableau.

et les opérations de collecte d' index sont ce qui retournerait toutes les valeurs possibles pour une clé ou un index donné:

Renvoie un tableau contenant une liste de documents qui identifient et décrivent les index existants sur la collection

Donc, dans une méthode donnée, on pourrait utiliser une méthode comme la suivante, afin d'interroger une collection pour tous ses index enregistrés, et retourner, par exemple, un objet avec les index pour les clés (cet exemple utilise async / wait pour NodeJS, mais vous pouvez évidemment utiliser toute autre approche asynchrone):

async function GetFor(collection, index) {

    let currentIndexes;
    let indexNames = [];
    let final = {};
    let vals = [];

    try {
        currentIndexes = await collection.indexes();
        await ParseIndexes();
        //Check if a specific index was queried, otherwise, iterate for all existing indexes
        if (index && typeof index === "string") return await ParseFor(index, indexNames);
        await ParseDoc(indexNames);
        await Promise.all(vals);
        return final;
    } catch (e) {
        throw e;
    }

    function ParseIndexes() {
        return new Promise(function (result) {
            let err;
            for (let ind in currentIndexes) {
                let index = currentIndexes[ind];
                if (!index) {
                    err = "No Key For Index "+index; break;
                }
                let Name = Object.keys(index.key);
                if (Name.length === 0) {
                    err = "No Name For Index"; break;
                }
                indexNames.push(Name[0]);
            }
            return result(err ? Promise.reject(err) : Promise.resolve());
        })
    }

    async function ParseFor(index, inDoc) {
        if (inDoc.indexOf(index) === -1) throw "No Such Index In Collection";
        try {
            await DistinctFor(index);
            return final;
        } catch (e) {
            throw e
        }
    }
    function ParseDoc(doc) {
        return new Promise(function (result) {
            let err;
            for (let index in doc) {
                let key = doc[index];
                if (!key) {
                    err = "No Key For Index "+index; break;
                }
                vals.push(new Promise(function (pushed) {
                    DistinctFor(key)
                        .then(pushed)
                        .catch(function (err) {
                            return pushed(Promise.resolve());
                        })
                }))
            }
            return result(err ? Promise.reject(err) : Promise.resolve());
        })
    }

    async function DistinctFor(key) {
        if (!key) throw "Key Is Undefined";
        try {
            final[key] = await collection.distinct(key);
        } catch (e) {
            final[key] = 'failed';
            throw e;
        }
    }
}

Donc, interroger une collection avec l' _idindex de base , retournerait ce qui suit (la collection de test n'a qu'un seul document au moment du test):

Mongo.MongoClient.connect(url, function (err, client) {
    assert.equal(null, err);

    let collection = client.db('my db').collection('the targeted collection');

    GetFor(collection, '_id')
        .then(function () {
            //returns
            // { _id: [ 5ae901e77e322342de1fb701 ] }
        })
        .catch(function (err) {
            //manage your error..
        })
});

Attention, cela utilise des méthodes natives du pilote NodeJS. Comme certaines autres réponses l'ont suggéré, il existe d'autres approches, comme le cadre global. Personnellement, je trouve cette approche plus flexible, car vous pouvez facilement créer et affiner comment renvoyer les résultats. De toute évidence, cela ne concerne que les attributs de niveau supérieur, pas ceux imbriqués. De plus, pour garantir que tous les documents sont représentés s'il existe des index secondaires (autres que le _id principal), ces index doivent être définis comme required.

jlmurph
la source
0

Nous pouvons y parvenir en utilisant le fichier mongo js. Ajoutez le code ci-dessous dans votre fichier getCollectionName.js et exécutez le fichier js dans la console de Linux comme indiqué ci-dessous:

mongo --host 192.168.1.135 getCollectionName.js

db_set = connect("192.168.1.135:27017/database_set_name"); // for Local testing
// db_set.auth("username_of_db", "password_of_db"); // if required

db_set.getMongo().setSlaveOk();

var collectionArray = db_set.getCollectionNames();

collectionArray.forEach(function(collectionName){

    if ( collectionName == 'system.indexes' || collectionName == 'system.profile' || collectionName == 'system.users' ) {
        return;
    }

    print("\nCollection Name = "+collectionName);
    print("All Fields :\n");

    var arrayOfFieldNames = []; 
    var items = db_set[collectionName].find();
    // var items = db_set[collectionName].find().sort({'_id':-1}).limit(100); // if you want fast & scan only last 100 records of each collection
    while(items.hasNext()) {
        var item = items.next(); 
        for(var index in item) {
            arrayOfFieldNames[index] = index;
        }
    }
    for (var index in arrayOfFieldNames) {
        print(index);
    }

});

quit();

Merci @ackuser

Irshad Khan
la source
0

En suivant le fil de la réponse de @James Cropcho, j'ai atterri sur les éléments suivants que j'ai trouvé très faciles à utiliser. C'est un outil binaire, c'est exactement ce que je cherchais: mongoeye .

En utilisant cet outil, il a fallu environ 2 minutes pour exporter mon schéma à partir de la ligne de commande.

paneer_tikka
la source
0

Je sais que cette question a 10 ans mais il n'y a pas de solution C # et cela m'a pris des heures à comprendre. J'utilise le pilote .NET et System.Linqpour retourner une liste des clés.

var map = new BsonJavaScript("function() { for (var key in this) { emit(key, null); } }");
var reduce = new BsonJavaScript("function(key, stuff) { return null; }");
var options = new MapReduceOptions<BsonDocument, BsonDocument>();
var result = await collection.MapReduceAsync(map, reduce, options);
var list = result.ToEnumerable().Select(item => item["_id"].ToString());
Andrew Samole
la source
-1

J'ai un peu étendu la solution de Carlos LM pour qu'elle soit plus détaillée.

Exemple de schéma:

var schema = {
    _id: 123,
    id: 12,
    t: 'title',
    p: 4.5,
    ls: [{
            l: 'lemma',
            p: {
                pp: 8.9
            }
        },
         {
            l: 'lemma2',
            p: {
               pp: 8.3
           }
        }
    ]
};

Tapez dans la console:

var schemafy = function(schema, i, limit) {
    var i = (typeof i !== 'undefined') ? i : 1;
    var limit = (typeof limit !== 'undefined') ? limit : false;
    var type = '';
    var array = false;

    for (key in schema) {
        type = typeof schema[key];
        array = (schema[key] instanceof Array) ? true : false;

        if (type === 'object') {
            print(Array(i).join('    ') + key+' <'+((array) ? 'array' : type)+'>:');
            schemafy(schema[key], i+1, array);
        } else {
            print(Array(i).join('    ') + key+' <'+type+'>');
        }

        if (limit) {
            break;
        }
    }
}

Courir:

schemafy(db.collection.findOne());

Production

_id <number>
id <number>
t <string>
p <number>
ls <object>:
    0 <object>:
    l <string>
    p <object>:
        pp <number> 
va5ja
la source
3
sa réponse est fausse et vous avez construit dessus. le tout est de sortir tous les champs de tous les documents, pas le premier document qui peut avoir des champs différents que chacun suivant.
Asya Kamsky
-3

J'ai 1 travail plus simple à faire ...

Ce que vous pouvez faire, c'est qu'en insérant des données / documents dans votre collection principale «choses», vous devez insérer les attributs dans 1 collection distincte, disons «choses_attributs».

Ainsi, chaque fois que vous insérez dans "Things", vous obtenez de "Things_attributes" comparez les valeurs de ce document avec vos nouvelles clés de document si une nouvelle clé présente l'ajoute dans ce document et le réinsère à nouveau.

Donc Things_attributes n'aura qu'un seul document de clés uniques que vous pouvez facilement obtenir quand vous en avez besoin en utilisant findOne ()

Paresh Behede
la source
Pour les bases de données avec de nombreuses entrées où les requêtes pour toutes les clés sont fréquentes et les insertions sont peu fréquentes, la mise en cache du résultat de la requête "obtenir toutes les clés" aurait du sens. C'est une façon de procéder.
Scott