Pergunta

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

Por exemplo, a partir disso:

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

Eu gostaria de obter as chaves exclusivas:

type, egg, hello
Foi útil?

Solução

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, funcione distintos na coleção resultante para encontrar todas as chaves:

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

Outras dicas

Com Resposta de Kristina Como inspiração, criei uma ferramenta de código aberto chamado Variety, que faz exatamente isso: https://github.com/Variety/Variety

Você pode usar agregação com novo $objectToArrray dentro 3.4.4 versão para converter todos os pares de chaves e valores em matrizes de documentos seguidos por $unwind & $group com $addToSet Para obter teclas 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 teclas em um único documento.

db.things.aggregate([
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])

Experimente isso:

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

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;

Usando python. Retorna o conjunto de todas as teclas de nível superior da 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()
)

Aqui está a amostra funcionada em Python: esta amostra 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']

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

Se você estiver usando o MongoDB 3.4.4 e acima, poderá usar abaixo a agregação usando $objectToArray e $group agregação

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

Aqui está o trabalho exemplo

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

Estou surpresa, ninguém aqui tem Ans usando simples javascript e Set lógica para filtrar automaticamente os valores duplicados, exemplo simples em Mongo Shellcomo 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á todo o possível chaves No nome da coleção: Coleção Nome.

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

Eu acho que a melhor maneira de fazer isso como mencionado aqui está em MongoD 3.4.4+, mas sem usar o $unwind operador e usando apenas dois estágios no pipeline. Em vez disso, podemos usar o $mergeObjects e $objectToArray operadores.

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

Então vem o $project onde usamos $map e $objectToArray para devolver 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 queremos obter as chaves também, isso é factível. Para simplificar, considere um documento com um documento incorporado simples que se parece com o seguinte:

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

O pipeline seguinte produz todas as teclas (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"
                        }
                    }
                ]
            }
        }
    }
]

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

Eu estava tentando escrever no Nodejs e finalmente inventei 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; 
});

De acordo com o MonGoldB documentação, uma combinação de distinct

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

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

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

Portanto, em um determinado método, pode -se usar um método como o seguinte, a fim de consultar uma coleção para todos os seus índices registrados e retornar, digamos um objeto com os índices para chaves (este exemplo usa async/aguarda para os nodejs, mas Obviamente você pode 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;
        }
    }
}

Então, consultando uma coleção com o básico _id Índice, retornaria o seguinte (a coleção de testes 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 do driver NodeJS. Como sugeriram outras respostas, existem outras abordagens, como a estrutura agregada. Pessoalmente, acho essa abordagem mais flexível, pois você pode criar e ajustar facilmente 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 (exceto o principal _id), esses índices devem ser definidos como required.

Talvez um pouco fora de tópico, mas você pode recursivamente imprimir todas as teclas/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.

Podemos conseguir isso usando o arquivo Mongo JS. Adicione o código abaixo em seu getCollectionName.js Arquive 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

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

Usando essa ferramenta, levou cerca de 2 minutos para exportar meu esquema da linha de comando.

Eu estendi um pouco a solução de Carlos LM, por isso é mais detalhado.

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> 

Eu tenho 1 de volta mais simples ...

O que você pode fazer é inserir dados/documentos em sua coleção principal "Coisas" Você deve inserir os atributos em 1 coleção separada, digamos "Things_attributes".

Portanto, toda vez que você se insere em "Coisas", você obtém de "Things_attributes", compare os valores desse documento com as suas novas chaves de documentos, se alguma nova chave apresentar-a a anexar nesse documento e novamente novamente.

Então Things_attributes terá apenas 1 documento de teclas exclusivas que você pode obter facilmente sempre que precisar usando FindOne ()

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top