Ottieni nomi di tutte le chiavi nella raccolta


322

Vorrei ottenere i nomi di tutte le chiavi in ​​una raccolta MongoDB.

Ad esempio, da questo:

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

Vorrei ottenere le chiavi uniche:

type, egg, hello

Risposte:


346

Puoi farlo con 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"
})

Quindi esegui distinti sulla raccolta risultante in modo da trovare tutte le chiavi:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]

2
Ciao! Ho appena pubblicato un seguito a questa domanda chiedendo come far funzionare questo snippet anche con chiavi situate a livelli più profondi nella struttura dei dati ( stackoverflow.com/questions/2997004/… ).
Andrea Fiore,

1
@kristina: Come è possibile che vengano elencate intere cose con le chiavi quando si usa questo nella raccolta cose . Sembra legato al meccanismo della storia perché ho cose che ho modificato in passato ..
Shawn,

3
So che questo è un vecchio thread, ma sembra che abbia un bisogno simile. Sto usando il driver nativo mongodb nodejs. La raccolta temporanea risultante sembra svuotare sempre. Per questo sto usando la funzione mapreduce nella classe collection. Non è possibile?
Deepak,

6
Questo può essere ovvio, ma se vuoi ottenere un elenco di tutte le chiavi univoche in un documento secondario, modifica questa riga:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne

3
Invece di salvare in una raccolta per poi eseguirne una distinta, uso map ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley


74

È possibile utilizzare l'aggregazione con new $objectToArrrayin 3.4.4version per convertire tutte le principali chiavi e coppie di valori in array di documenti seguiti da $unwinde $group con $addToSetper ottenere chiavi distinte nell'intera raccolta.

$$ROOT per fare riferimento al documento di livello superiore.

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

È possibile utilizzare la query seguente per ottenere le chiavi in ​​un singolo documento.

db.things.aggregate([
  {"$match":{_id: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])

20
Questa è davvero la risposta migliore. Risolve il problema senza coinvolgere altri linguaggi o pacchetti di programmazione e funziona con tutti i driver che supportano il framework aggregato (anche Meteor!)
Micah Henning

2
Se si desidera restituire un array anziché un cursore contenente una singola voce della mappa con una chiave "allkeys", è possibile aggiungere .next()["allkeys"]al comando (supponendo che la raccolta abbia almeno un elemento).
M. Justin

19

Prova questo:

doc=db.thinks.findOne();
for (key in doc) print(key);

49
risposta errata poiché questo genera solo campi per un singolo documento in una raccolta - gli altri potrebbero avere chiavi completamente diverse.
Asya Kamsky,

15
È ancora la risposta più utile per me, essendo un semplice minimo ragionevole.
Boris Burkov,

11
Non è utile? In che modo è utile se ti dà la risposta sbagliata?
Zlatko,

4
Il contesto mostra ciò che è utile: se i dati sono normalizzati (es. Origine da file CSV), è utile ... Per i dati importati da SQL è utile.
Peter Krauss,

5
non è una buona risposta è una risposta su come ottenere le chiavi di un elemento nella raccolta non tutte le chiavi della raccolta!
yonatan

16

Se la tua raccolta target non è troppo grande, puoi provare questo sotto il client shell mongo:

var allKeys = {};

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

allKeys;

qui come posso dare regExp per chiavi particolari se voglio vedere?
TB.M,

@ TB.M puoi provare questo: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { allKeys [tasto] = 1}})});
Li Chunlin,

cosa significa test qui? puoi spiegare per favore?
TB.M,


14

Una soluzione pulita e riutilizzabile con 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')

Uso:

get_keys('dbname', 'collection')
>> ['key1', 'key2', ... ]

1
Funziona alla grande. Finalmente ho risolto il mio problema .... questa è la soluzione più semplice che ho visto in overflow dello stack ...
Smack Alpha

E per filtrare per tipo, basta aggiungere ad esempio if (typeof(this[key]) == 'number')primaemit(key, null) .
Skippy le Grand Gourou,

10

Usando Python. Restituisce il set di tutte le chiavi di livello superiore nella raccolta:

#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()
)

1
Ho trovato che funziona, ma quanto è efficace rispetto a una query mongod non elaborata?
Jesus Gomez,

1
Sono abbastanza sicuro che sia estremamente inefficiente rispetto a farlo direttamente in Mongodb
Ingo Fischer,

9

Ecco l'esempio lavorato in Python: Questo esempio restituisce i risultati in linea.

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']

9

Se si utilizza mongodb 3.4.4 e versioni successive, è possibile utilizzare l'aggregazione sottostante utilizzando $objectToArraye $groupaggregazione

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

Ecco l' esempio funzionante


Questa è la risposta migliore È inoltre possibile utilizzare $matchall'inizio della pipeline di aggregazione per ottenere solo le chiavi dei documenti che soddisfano una o più condizioni.
RonquilloAeon

5

Sono sorpreso, nessuno qui ha risposto usando semplici javascripte Setlogici per filtrare automaticamente i valori dei duplicati, semplice esempio sulla shell mongo come di seguito:

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

Questo stamperà tutte le possibili chiavi univoche nel nome della raccolta: collectionName .


3

Questo funziona bene per me:

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);
}

3

Penso che il modo migliore per farlo, come menzionato qui, sia nel mongod 3.4.4+, ma senza usare l' $unwindoperatore e usare solo due fasi nella pipeline. Invece possiamo usare gli operatori $mergeObjectse $objectToArray.

Nella $groupfase, utilizziamo l' $mergeObjectsoperatore per restituire un singolo documento in cui chiave / valore provengono da tutti i documenti della raccolta.

Quindi arriva il $projectluogo in cui utilizziamo $mape $objectToArrayrestituiamo le chiavi.

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

Ora se abbiamo documenti nidificati e vogliamo ottenere anche le chiavi, questo è fattibile. Per semplicità, consideriamo un documento con un semplice documento incorporato simile al seguente:

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

La seguente pipeline fornisce tutte le chiavi (field1, field2, field3, field4).

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"
                        }
                    }
                ]
            }
        }
    }
]

Con un piccolo sforzo, possiamo ottenere la chiave per tutti i documenti secondari in un campo di array in cui anche gli elementi sono oggetti.


$unwind, esploderà la raccolta (no. Di campi * no. Di documenti), possiamo evitarlo usando $mergeObjectssu tutte le versioni> 3.6. Ha fatto lo stesso, avrei dovuto vedere prima questa risposta, la mia vita sarebbe stata più semplice -_-)
whoami l'

3

Forse leggermente fuori tema, ma puoi ricorsivamente stampare piuttosto tutte le chiavi / i campi di un oggetto:

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 quando tutti gli oggetti in una raccolta hanno la stessa struttura.


1

Per ottenere un elenco di tutte le chiavi meno _id, prendere in considerazione l'esecuzione della seguente pipeline aggregata:

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"];

0

Stavo cercando di scrivere in nodejs e alla fine ho trovato questo:

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');
        });
 });

Dopo aver letto la raccolta appena creata "allFieldNames", eliminala.

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

0

Come da documentazione mongoldb , una combinazione didistinct

Trova i valori distinti per un campo specificato in una singola raccolta o vista e restituisce i risultati in un array.

e le operazioni di raccolta degli indici sono ciò che restituirebbe tutti i possibili valori per una determinata chiave o indice:

Restituisce un array che contiene un elenco di documenti che identificano e descrivono gli indici esistenti nella raccolta

Quindi in un dato metodo si potrebbe usare un metodo come il seguente, per interrogare una raccolta per tutti i suoi indici registrati, e restituire, dire un oggetto con gli indici per le chiavi (questo esempio usa async / wait per NodeJS, ma ovviamente potresti usare qualsiasi altro approccio asincrono):

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;
        }
    }
}

Pertanto, l'interrogazione di una raccolta con l' _idindice di base restituisce quanto segue (la raccolta di test ha un solo documento al momento del 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..
        })
});

Intendiamoci, utilizza metodi nativi del driver NodeJS. Come alcune altre risposte hanno suggerito, ci sono altri approcci, come il quadro aggregato. Personalmente trovo questo approccio più flessibile, in quanto puoi facilmente creare e mettere a punto come restituire i risultati. Ovviamente, questo riguarda solo gli attributi di livello superiore, non quelli nidificati. Inoltre, per garantire che tutti i documenti siano rappresentati in presenza di indici secondari (diversi da quello _id principale), tali indici devono essere impostati come required.


0

Possiamo raggiungere questo obiettivo utilizzando il file mongo js. Aggiungi il codice seguente nel tuo file getCollectionName.js ed esegui il file js nella console di Linux come indicato di seguito:

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();

Grazie @utente


0

Seguendo il thread della risposta di @James Cropcho, sono arrivato al seguito che ho trovato super facile da usare. È uno strumento binario, che è esattamente quello che stavo cercando: mongoeye .

Usando questo strumento ci sono voluti circa 2 minuti per esportare il mio schema dalla riga di comando.


0

So che questa domanda ha 10 anni ma non esiste una soluzione C # e mi ci sono volute ore per capire. Sto usando il driver .NET e System.Linqper restituire un elenco di chiavi.

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());

-1

Ho ampliato un po 'la soluzione di Carlos LM in modo che sia più dettagliata.

Esempio di uno schema:

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

Digita nella 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;
        }
    }
}

Correre:

schemafy(db.collection.findOne());

Produzione

_id <number>
id <number>
t <string>
p <number>
ls <object>:
    0 <object>:
    l <string>
    p <object>:
        pp <number> 

3
la sua risposta è sbagliata e tu ci hai costruito sopra. il punto è produrre tutti i campi di tutti i documenti, non il primo documento che può avere campi diversi da quelli successivi.
Asya Kamsky,

-3

Ho 1 lavoro più semplice in giro ...

Quello che puoi fare è inserire dati / documenti nella "raccolta" principale della tua raccolta, devi inserire gli attributi in una raccolta separata diciamo "cose_attributi".

così ogni volta che si inserisce "cose", si ottiene da "cose_attributi" confrontare i valori di quel documento con le nuove chiavi del documento se una nuova chiave presente lo aggiunge in quel documento e lo reinserisce nuovamente.

Quindi things_attributes avrà solo 1 documento di chiavi uniche che puoi facilmente ottenere quando ne hai bisogno usando findOne ()


Per i database con molte voci in cui le query per tutte le chiavi sono frequenti e gli inserimenti non sono frequenti, sarebbe utile memorizzare nella cache il risultato della query "ottieni tutte le chiavi". Questo è un modo per farlo.
Scott,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.