Obter nomes de todas as chaves na coleção

322

Eu gostaria de obter os nomes de todas as chaves em uma coleção do MongoDB.

Por exemplo, disso:

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

Gostaria de obter as chaves exclusivas:

type, egg, hello
Steve
fonte

Respostas:

346

Você poderia fazer isso com o 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"
})

Em seguida, execute distintamente na coleção resultante para encontrar todas as chaves:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
Cristina
fonte
2
Olá! Acabei de postar um acompanhamento para esta pergunta perguntando como fazer esse trecho funcionar mesmo com chaves localizadas em níveis mais profundos na estrutura de dados ( stackoverflow.com/questions/2997004/… ).
Andrea Fiore
1
@ kristina: Como é possível que eu consiga listar coisas inteiras com as teclas ao usá- las na coleção de coisas . Parece relacionado ao mecanismo da história, porque recebo coisas que modifiquei no passado.
Shawn
3
Sei que esse é um tópico antigo, mas pareço ter uma necessidade semelhante. Estou usando o driver nativo nodejs mongodb. A coleção temporária resultante parece esvaziar sempre. Estou usando a função mapreduce na classe de coleção para isso. Isso não é possível?
Deepak
6
Isso pode ser óbvio, mas se você deseja obter uma lista de todas as chaves únicas em um subdocumento, basta modificar esta linha:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne
3
Em vez de salvar a uma coleção em seguida, executando distinta sobre isso, eu uso mapa ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley
203

Com a resposta de Kristina como inspiração, criei uma ferramenta de código aberto chamada Variety, que faz exatamente isso: https://github.com/variety/variety

James Cropcho
fonte
13
Esta é uma ferramenta fantástica, parabéns. Ele faz exatamente o que a pergunta pede e pode ser configurado com limites, profundidade etc. Recomendado por qualquer um que a seguir.
precisa
74

Você pode usar a agregação com o novo $objectToArrrayna 3.4.4versão para converter todos os pares principais de chave e valor em matrizes de documentos seguidos por $unwind& $group com $addToSetpara obter chaves distintas em toda a coleção.

$$ROOT para referenciar o documento de nível superior.

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

Você pode usar a consulta abaixo para obter chaves em um único documento.

db.things.aggregate([
  {"$match":{_id: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])
Sagar Veeram
fonte
20
Esta é realmente a melhor resposta. Resolve o problema sem envolver alguma outra linguagem de programação ou pacote, e trabalha com todos os drivers que suportam a estrutura agregada (mesmo Meteor!)
Micah Henning
2
Se você quiser retornar uma matriz em vez de um cursor que contenha uma única entrada de mapa com uma tecla "allkeys", poderá anexar .next()["allkeys"]ao comando (assumindo que a coleção tenha pelo menos um elemento).
M. Justin
19

Tente o seguinte:

doc=db.thinks.findOne();
for (key in doc) print(key);
Carlos LM
fonte
49
resposta incorreta, pois isso gera apenas campos para um único documento em uma coleção - os outros podem ter chaves completamente diferentes.
Asya Kamsky 31/03
15
Ainda é a resposta mais útil para mim, sendo um mínimo razoável simples.
Boris Burkov
11
Não é útil? Como é útil se você fornecer a resposta errada?
precisa
4
O contexto mostra o que é útil: se os dados são normalizados (por exemplo, origens do arquivo CSV), é útil ... Para os dados importados do SQL, é útil.
Peter Krauss
5
não é uma boa resposta, é uma resposta sobre como obter chaves de um elemento da coleção nem todas as chaves da coleção!
yonatan
16

Se sua coleção de destino não for muito grande, você pode tentar isso no mongo shell client:

var allKeys = {};

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

allKeys;
Li Chunlin
fonte
aqui como eu posso dar regExp para chaves específicas, se eu quiser ver?
TB.M
@ TB.M, você pode tentar o seguinte: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { allKeys [chave] = 1}})});
precisa
o que test significa aqui? você pode explicar?
TB.M
14

Uma solução limpa e reutilizável 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
fonte
1
Funciona bem. Finalmente tenho o meu problema resolvido .... este é o mais simples serra solução i no estouro de pilha ..
Bater Alpha
E para filtrar por tipo, basta adicionar, por exemplo, if (typeof(this[key]) == 'number')antes emit(key, null).
Skippy le Grand Gourou
10

Usando python. Retorna o conjunto de todas as chaves de nível superior na coleção:

#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
fonte
1
Eu encontrei isso para trabalhar, mas quão eficiente é em comparação com uma consulta mongod bruto?
Jesus Gomez
1
Tenho certeza de que isso é extremamente ineficiente comparado a fazer isso diretamente no MongoDB
Ingo Fischer
9

Aqui está o exemplo trabalhado em Python: Este exemplo retorna os resultados em linha.

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

Se você estiver usando o mongodb 3.4.4 e superior, poderá usar a agregação abaixo usando $objectToArraye a $groupagregação

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

Aqui está o exemplo de trabalho

Ashh
fonte
Esta é a melhor resposta. Você também pode usar $matchno início do pipeline de agregação para obter apenas as chaves dos documentos que correspondem às condições.
RonquilloAeon
5

Estou surpreso, ninguém aqui tem ans usando simple javascripte Setlogic para filtrar automaticamente os valores de duplicatas, exemplo simples no mongo shell como abaixo:

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

Isso imprimirá todas as chaves exclusivas possíveis no nome da coleção: collectionName .

Krishna Prasad
fonte
3

Este trabalho é bom para mim:

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

Eu acho que a melhor maneira de fazer isso, conforme mencionado aqui, é no mongod 3.4.4+, mas sem usar o $unwindoperador e usando apenas dois estágios no pipeline. Em vez disso, podemos usar o $mergeObjectse $objectToArrayoperadores.

No $groupestágio, usamos o $mergeObjectsoperador para retornar um único documento em que chave / valor são de todos os documentos da coleção.

Depois vem o local $projectonde usamos $mape $objectToArraypara retornar as chaves.

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

Agora, se tivermos documentos aninhados e quisermos obter as chaves também, isso é possível. Para simplificar, vamos considerar um documento com documento incorporado simples que se parece com isso:

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

O pipeline a seguir produz todas as chaves (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"
                        }
                    }
                ]
            }
        }
    }
]

Com um pouco de esforço, podemos obter a chave para todos os subdocumentos em um campo de matriz em que os elementos também são objetos.

styvane
fonte
Sim $unwindexplodirá a coleção (no.of fields * no.of docs), podemos evitar isso usando $mergeObjectsem todas as versões> 3.6.. Fiz o mesmo, deveria ter visto essa resposta antes, minha vida teria sido mais fácil dessa maneira ( -_-)
whoami
3

Talvez um pouco fora do tópico, mas você pode imprimir recursivamente todas as chaves / campos de um 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 quando todos os objetos em uma coleção têm a mesma estrutura.

qed
fonte
1

Para obter uma lista de todas as chaves menos _id, considere executar o seguinte pipeline agregado:

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

Eu estava tentando escrever em nodejs e finalmente surgiu com isso:

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

Depois de ler a coleção recém-criada "allFieldNames", exclua-a.

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

De acordo com a documentação do mongoldb , uma combinação dedistinct

Localiza os valores distintos para um campo especificado em uma única coleção ou exibição e retorna os resultados em uma matriz.

e operações de coleta de índices são o que retornaria todos os valores possíveis para uma determinada chave ou índice:

Retorna uma matriz que contém uma lista de documentos que identificam e descrevem os índices existentes na coleção

Portanto, em um determinado método, poderia-se usar um método como o seguinte, para consultar uma coleção por todos os seus índices registrados e retornar, digamos, um objeto com os índices das chaves (este exemplo usa async / waitit para NodeJS, mas obviamente, você poderia usar qualquer outra abordagem assíncrona):

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

Portanto, consultar uma coleção com o _idíndice básico retornaria o seguinte (a coleção de teste possui apenas um documento no momento do teste):

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

Lembre-se, isso usa métodos nativos para o driver NodeJS. Como algumas outras respostas sugeriram, existem outras abordagens, como a estrutura agregada. Pessoalmente, acho essa abordagem mais flexível, pois você pode criar e ajustar com facilidade como retornar os resultados. Obviamente, isso aborda apenas atributos de nível superior, não aninhados. Além disso, para garantir que todos os documentos sejam representados, caso haja índices secundários (que não sejam o principal _id), esses índices devem ser definidos como required.

jlmurph
fonte
0

Podemos conseguir isso usando o arquivo mongo js. Adicione o código abaixo no seu arquivo getCollectionName.js e execute o arquivo js no console do Linux, conforme indicado abaixo:

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

Obrigado @ackuser

Irshad Khan
fonte
0

Seguindo o tópico da resposta de @James Cropcho, cheguei ao seguinte, que achei super fácil de usar. É uma ferramenta binária, que é exatamente o que eu estava procurando: mongoeye .

Usando essa ferramenta, demorou cerca de 2 minutos para obter meu esquema exportado da linha de comando.

paneer Tikka
fonte
0

Eu sei que esta pergunta tem 10 anos, mas não há solução C # e isso me levou horas para descobrir. Estou usando o driver .NET e System.Linqpara retornar uma lista das chaves.

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

Estendi um pouco a solução de Carlos LM para ficar mais detalhada.

Exemplo de um 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
           }
        }
    ]
};

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

Corre:

schemafy(db.collection.findOne());

Resultado

_id <number>
id <number>
t <string>
p <number>
ls <object>:
    0 <object>:
    l <string>
    p <object>:
        pp <number> 
va5ja
fonte
3
a resposta dele está errada e você construiu sobre ela. o ponto principal é produzir todos os campos de todos os documentos, não o primeiro documento que pode ter campos diferentes de cada um.
Asya Kamsky 31/03
-3

Eu tenho 1 solução mais simples ...

O que você pode fazer é ao inserir dados / documento em sua coleção principal "coisas", você deve inserir os atributos em uma coleção separada, digamos "coisas_atributos".

portanto, toda vez que você insere "coisas", você obtém de "things_attributes" compara valores desse documento com suas novas chaves de documento, se alguma nova chave presente o anexa nesse documento e a reinsere.

Portanto, things_attributes terá apenas 1 documento de chaves exclusivas, que você poderá obter facilmente sempre que precisar, usando findOne ()

Paresh Behede
fonte
Para bancos de dados com muitas entradas em que as consultas para todas as chaves são frequentes e as inserções não são frequentes, o armazenamento em cache do resultado da consulta "obter todas as chaves" faria sentido. Esta é uma maneira de fazer isso.
Scott Scott