Obtenga los nombres de todas las claves de la colección.

322

Me gustaría obtener los nombres de todas las claves en una colección MongoDB.

Por ejemplo, de esto:

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

Me gustaría obtener las claves únicas:

type, egg, hello
Steve
fuente

Respuestas:

346

Puedes hacer esto 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"
})

Luego, ejecute distinto en la colección resultante para encontrar todas las claves:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
kristina
fuente
2
¡Hola! Acabo de publicar un seguimiento de esta pregunta preguntando cómo hacer que este fragmento funcione incluso con claves ubicadas en niveles más profundos en la estructura de datos ( stackoverflow.com/questions/2997004/… ).
Andrea Fiore
1
@kristina: ¿Cómo es posible que consiga cosas completas listadas con las teclas cuando uso esto en la colección de cosas ? Parece relacionado con el mecanismo de la historia porque obtengo cosas que he modificado en el pasado ...
Shawn
3
Sé que este es un hilo viejo, pero parece que tengo una necesidad similar. Estoy usando el controlador nativo de nodejs mongodb. La colección temporal resultante parece vaciarse siempre. Estoy usando la función mapreduce en la clase de colección para esto. ¿Eso no es posible?
Deepak
66
Esto puede ser obvio, pero si desea obtener una lista de todas las claves únicas en un subdocumento, simplemente modifique esta línea:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne
3
En lugar de guardar en una colección y luego ejecutarla de forma distinta, utilizo map ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley
203

Con la respuesta de Kristina como inspiración, creé una herramienta de código abierto llamada Variety que hace exactamente esto: https://github.com/variety/variety

James Cropcho
fuente
13
Esta es una herramienta fantástica, felicidades. Hace exactamente lo que hace la pregunta y se puede configurar con límites, profundidad, etc. Recomendado por cualquiera que lo siga.
Paul Biggar
74

Puede utilizar la agregación con el nuevo $objectToArrrayen 3.4.4la versión para convertir todo tecla superior y par de valores en matrices de documentos seguidos por $unwindy $group con los $addToSetque obtener las claves distintas a través de toda la colección.

$$ROOT para hacer referencia al documento de nivel superior.

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

Puede usar la consulta a continuación para obtener claves en un solo documento.

db.things.aggregate([
  {"$match":{_id: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])
Sagar Veeram
fuente
20
Esta es realmente la mejor respuesta. Resuelve el problema sin involucrar algún otro lenguaje o paquete de programación, y funciona con todos los controladores que admiten el marco agregado (¡incluso Meteor!)
Micah Henning
2
Si desea devolver una matriz en lugar de un cursor que contenga una sola entrada de mapa con una tecla "allkeys", puede agregar .next()["allkeys"]el comando (suponiendo que la colección tenga al menos un elemento).
M. Justin
19

Prueba esto:

doc=db.thinks.findOne();
for (key in doc) print(key);
Carlos LM
fuente
49
respuesta incorrecta ya que esto solo genera campos para un solo documento en una colección; los demás pueden tener claves completamente diferentes.
Asya Kamsky
15
Sigue siendo la respuesta más útil para mí, ya que es un mínimo razonable simple.
Boris Burkov
11
No es util? ¿Cómo es útil si te da la respuesta incorrecta?
Zlatko
44
El contexto muestra lo que es útil: si los datos están normalizados (por ejemplo, origen del archivo CSV), es útil ... Para los datos importados desde SQL es útil.
Peter Krauss
55
¡no es una buena respuesta, es una respuesta sobre cómo obtener las claves de un elemento de la colección, no todas las claves de la colección!
yonatan
16

Si su colección de destino no es demasiado grande, puede probar esto en el cliente de shell mongo:

var allKeys = {};

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

allKeys;
Li Chunlin
fuente
aquí, ¿cómo puedo dar regExp para claves particulares si quiero ver?
TB.M
@ TB.M puede probar esto: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { allKeys [clave] = 1}})});
Li Chunlin
¿Qué significa prueba aquí? ¿Puedes explicarme?
TB.M
14

Una solución limpia y reutilizable usando 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', ... ]
Ingo Fischer
fuente
1
Funciona genial. Finalmente resolví mi problema ... esta es la solución más simple que vi en el desbordamiento de pila ...
Smack Alpha
Y para filtrar por tipo, simplemente agregue, por ejemplo, if (typeof(this[key]) == 'number')antes emit(key, null).
Skippy le Grand Gourou
10

Usando python. Devuelve el conjunto de todas las claves de nivel superior de la colección:

#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
fuente
1
He encontrado que esto funciona, pero ¿qué tan eficiente es en comparación con una consulta mongod sin procesar?
Jesús Gómez
1
Estoy bastante seguro de que esto es extremadamente ineficiente en comparación con hacerlo directamente en Mongodb
Ingo Fischer
9

Aquí está la muestra trabajada en Python: esta muestra devuelve los resultados en línea.

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
fuente
9

Si está usando mongodb 3.4.4 y superior, puede usar la agregación siguiente usando $objectToArrayy $groupagregación

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

Aquí está el ejemplo de trabajo.

Ashh
fuente
Esta es la mejor respuesta. También puede usar $matchal comienzo de la canalización de agregación para obtener solo las claves de los documentos que coinciden con una o varias condiciones.
RonquilloAeon
5

Me sorprende, nadie aquí tiene ans mediante el uso de simple javascripty Setlógica para filtrar automáticamente los valores duplicados, ejemplo simple en mongo shell como a continuación:

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

Esto imprimirá todas las claves únicas posibles en el nombre de la colección: collectionName .

Krishna Prasad
fuente
3

Esto funciona bien para mi:

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
fuente
3

Creo que la mejor manera de hacer esto como se menciona aquí es en mongod 3.4.4+ pero sin usar el $unwindoperador y usar solo dos etapas en la tubería. En cambio, podemos usar los operadores $mergeObjectsy $objectToArray.

En la $groupetapa, utilizamos el $mergeObjectsoperador para devolver un solo documento donde la clave / valor son de todos los documentos de la colección.

Luego viene el lugar $projectdonde usamos $mapy $objectToArraypara devolver las llaves.

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

Ahora, si tenemos documentos anidados y también queremos obtener las claves, esto es factible. Para simplificar, consideremos un documento con un documento incrustado simple que se vea así:

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

La siguiente canalización produce todas las claves (campo1, campo2, campo3, campo4).

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 poco de esfuerzo, podemos obtener la clave para todos los subdocumentos en un campo de matriz donde los elementos también son objetos.

Styvane
fuente
$unwindexplotará la colección (no. De campos * no. De documentos), podemos evitar eso al usar $mergeObjectsen todas las versiones> 3.6.. Hice lo mismo, debería haber visto esta respuesta antes, mi vida habría sido más fácil de esa manera ( -_-)
whoami
3

Tal vez un poco fuera de tema, pero puede imprimir recursivamente todas las claves / campos de un objeto:

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

Útil cuando todos los objetos de una colección tienen la misma estructura.

qed
fuente
1

Para obtener una lista de todas las teclas menos _id, considere ejecutar la siguiente tubería agregada:

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
fuente
0

Estaba tratando de escribir en nodejs y finalmente se me ocurrió esto:

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

Después de leer la colección recién creada "allFieldNames", elimínela.

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

Según la documentación de mongoldb , una combinación dedistinct

Encuentra los valores distintos para un campo específico en una única colección o vista y devuelve los resultados en una matriz.

y las operaciones de recopilación de índices son las que devolverían todos los valores posibles para una clave o índice dado:

Devuelve una matriz que contiene una lista de documentos que identifican y describen los índices existentes en la colección.

Entonces, en un método dado, uno podría usar un método como el siguiente, para consultar una colección para todos sus índices registrados y regresar, digamos un objeto con los índices para las claves (este ejemplo usa async / await para NodeJS, pero obviamente, podría usar cualquier otro enfoque asincrónico):

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

Entonces, consultar una colección con el _idíndice básico devolvería lo siguiente (la colección de prueba solo tiene un documento en el momento de la prueba):

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

Eso sí, esto utiliza métodos nativos del controlador NodeJS. Como han sugerido algunas otras respuestas, existen otros enfoques, como el marco agregado. Personalmente, considero que este enfoque es más flexible, ya que puede crear y ajustar fácilmente cómo devolver los resultados. Obviamente, esto solo aborda los atributos de nivel superior, no los anidados. Además, para garantizar que todos los documentos estén representados en caso de que haya índices secundarios (que no sean el _id principal), esos índices deben establecerse como required.

jlmurph
fuente
0

Podemos lograr esto utilizando el archivo mongo js. Agregue el código siguiente en su archivo getCollectionName.js y ejecute el archivo js en la consola de Linux como se indica a continuación:

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

Gracias @ackuser

Irshad Khan
fuente
0

Siguiendo el hilo de la respuesta de @James Cropcho, llegué a lo siguiente, que me pareció súper fácil de usar. Es una herramienta binaria, que es exactamente lo que estaba buscando: mongoeye .

Con esta herramienta, tardé unos 2 minutos en exportar mi esquema desde la línea de comandos.

paneer_tikka
fuente
0

Sé que esta pregunta tiene 10 años, pero no hay una solución de C # y esto me llevó horas resolverlo. Estoy usando el controlador .NET y System.Linqpara devolver una lista de las claves.

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
fuente
-1

Extendí un poco la solución de Carlos LM para que sea más detallada.

Ejemplo de un esquema:

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

Escribe en la consola:

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

Correr:

schemafy(db.collection.findOne());

Salida

_id <number>
id <number>
t <string>
p <number>
ls <object>:
    0 <object>:
    l <string>
    p <object>:
        pp <number> 
va5ja
fuente
3
su respuesta es incorrecta y usted construyó sobre ella. el objetivo es generar todos los campos de todos los documentos, no el primer documento que puede tener campos diferentes a los siguientes.
Asya Kamsky
-3

Tengo 1 trabajo más simple alrededor ...

Lo que puede hacer es insertar datos / documentos en su colección principal "cosas", debe insertar los atributos en 1 colección separada, digamos "cosas_atributos".

así que cada vez que inserte en "cosas", obtiene de "cosas_atributos" comparar valores de ese documento con sus nuevas claves de documento si alguna clave nueva presente lo agrega en ese documento y lo vuelve a insertar.

Así que things_attributes tendrá solo 1 documento de claves únicas que puede obtener fácilmente cuando lo necesite utilizando findOne ()

Paresh Behede
fuente
Para bases de datos con muchas entradas donde las consultas para todas las claves son frecuentes y las inserciones son poco frecuentes, el almacenamiento en caché del resultado de la consulta "obtener todas las claves" tendría sentido. Esta es una forma de hacerlo.
Scott