获取集合中所有键的名称

我想获得MongoDB集合中所有键的名称。

例如,从这个:

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

我想获得唯一的键:

type, egg, hello
257325 次浏览

你可以用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"
})

然后在结果集合上单独运行,以便找到所有的键:

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

克里斯蒂娜的回答为灵感,我创建了一个名为Variety的开源工具,它正是这样做的:https://github.com/variety/variety

试试这个:

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

我有一个更简单的工作…

你能做的是插入数据/文档到你的主集合“东西”,你必须在一个单独的集合中插入属性,让我们说“things_attributes”。

所以每次你插入"things"时,你确实会从"things_attributes"中得到该文档的值与你的新文档键的值进行比较,如果有任何新键存在,则将其追加到该文档中并再次重新插入它。

所以things_attributes只有一个唯一键的文档你可以通过findOne()很容易地得到

我扩展了Carlos LM的解决方案,使其更加详细。

一个模式的例子:

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

在控制台输入:

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

运行:

schemafy(db.collection.findOne());

输出

_id <number>
id <number>
t <string>
p <number>
ls <object>:
0 <object>:
l <string>
p <object>:
pp <number>
下面是用Python编写的示例: 此示例内联返回结果。

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

使用python。返回集合中所有顶级键的集合:

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

这对我来说很有效:

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

如果你的目标集合不是很大,你可以在mongo shell客户端下尝试:

var allKeys = {};


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


allKeys;

你可以在3.4.4版本中使用带有新的$objectToArray聚合运算符的聚合来将所有顶部键-值对转换为文档数组,然后使用$unwind$group以及$addToSet来获得整个集合中的不同键。(使用$unwind0来引用顶层文档。)

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

您可以使用以下查询来获取单个文档中的键。

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

我试着用nodejs写,最后想到了这个:

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

读取新创建的集合“allFieldNames”后,删除它。

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

使用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')

用法:

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

要获得减去_id的所有键的列表,可以考虑运行以下聚合管道:

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

我认为像前面提到的在这里这样做的最好方法是在mongod 3.4.4+中,但不使用$unwind操作符,并且只在管道中使用两个阶段。相反,我们可以使用$mergeObjects$objectToArray操作符。

$group阶段,我们使用$mergeObjects操作符返回一个文档,其中键/值来自集合中的所有文档。

然后是$project,其中我们使用$map$objectToArray来返回密钥。

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

现在,如果我们有一个嵌套的文档,想要获得键,这是可行的。为了简单起见,让我们考虑一个包含简单嵌入式文档的文档,它看起来像这样:

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

下面的管道会生成所有的键(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"
}
}
]
}
}
}
]

只需稍加努力,我们就可以获得数组字段中所有子文档的键,其中元素也是object。

根据mongoldb 文档distinct的组合

在单个集合或视图中查找指定字段的不同值,并以数组形式返回结果。

索引集合操作将返回给定键或索引的所有可能值:

返回一个数组,其中包含标识和描述集合上现有索引的文档列表

所以在一个给定的方法中,你可以使用下面的方法,为了查询一个集合中所有注册的索引,并返回一个对象,比如一个键的索引(这个例子使用async/await用于NodeJS,但显然你可以使用任何其他异步方法):

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

因此,使用基本的_id索引查询一个集合,将返回以下内容(测试集合在测试时只有一个文档):

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..
})
});
请注意,这使用了NodeJS Driver原生的方法。正如其他一些答案所建议的那样,还有其他方法,例如聚合框架。我个人认为这种方法更灵活,因为您可以轻松地创建和微调如何返回结果。显然,这只处理顶级属性,而不是嵌套属性。 此外,为了保证所有文档都被表示,如果有次要索引(除了主_id索引),这些索引应该设置为required.

如果你正在使用mongodb 3.4.4及以上版本,那么你可以使用< >强$objectToArray < / >强< >强$group < / >强聚合来使用下面聚合

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

下面是工作的< /强> < >强例子

可能有点偏离主题,但你可以递归地漂亮打印对象的所有键/字段:

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

当集合中的所有对象都具有相同的结构时非常有用。

我们可以通过使用mongo js文件来实现这一点。在你的getCollectionName.js文件中添加以下代码,并在Linux控制台中运行js文件,如下所示:

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

由于@ackuser

我很惊讶,这里没有人使用简单的javascriptSet逻辑来自动过滤重复的值,__abc2的简单示例如下:

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

这将打印集合名称:collectionName中所有可能惟一的

在@James Cropcho的回答之后,我找到了下面这个我发现超级容易使用的方法。这是一个二进制工具,这正是我正在寻找的: mongoeye . < / p >

使用这个工具,大约花了2分钟从命令行导出我的模式。

我知道这个问题已经10年了,但是c#没有解决方案,我花了好几个小时才弄清楚。我正在使用. net驱动程序和System.Linq来返回键的列表。

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

基于@Wolkenarchitekt的答案:https://stackoverflow.com/a/48117846/8808983,我写了一个脚本,可以在db中的所有键中找到模式,我认为它可以帮助其他人阅读这个线程:

"""
Python 3
This script get list of patterns and print the collections that contains fields with this patterns.
"""


import argparse


import pymongo
from bson import Code




# initialize mongo connection:
def get_db():
client = pymongo.MongoClient("172.17.0.2")
db = client["Data"]
return db




def get_commandline_options():
description = "To run use: python db_fields_pattern_finder.py -p <list_of_patterns>"
parser = argparse.ArgumentParser(description=description)
parser.add_argument('-p', '--patterns', nargs="+", help='List of patterns to look for in the db.', required=True)
return parser.parse_args()




def report_matching_fields(relevant_fields_by_collection):
print("Matches:")


for collection_name in relevant_fields_by_collection:
if relevant_fields_by_collection[collection_name]:
print(f"{collection_name}: {relevant_fields_by_collection[collection_name]}")


# pprint(relevant_fields_by_collection)




def get_collections_names(db):
"""
:param pymongo.database.Database db:
:return list: collections names
"""
return db.list_collection_names()




def get_keys(db, collection):
"""
See: https://stackoverflow.com/a/48117846/8808983
:param db:
:param collection:
:return:
"""
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')




def get_fields(db, collection_names):
fields_by_collections = {}
for collection_name in collection_names:
fields_by_collections[collection_name] = get_keys(db, collection_name)
return fields_by_collections




def get_matches_fields(fields_by_collections, patterns):
relevant_fields_by_collection = {}
for collection_name in fields_by_collections:
relevant_fields = [field for field in fields_by_collections[collection_name] if
[pattern for pattern in patterns if
pattern in field]]
relevant_fields_by_collection[collection_name] = relevant_fields


return relevant_fields_by_collection




def main(patterns):
"""
:param list patterns: List of strings to look for in the db.
"""
db = get_db()


collection_names = get_collections_names(db)
fields_by_collections = get_fields(db, collection_names)
relevant_fields_by_collection = get_matches_fields(fields_by_collections, patterns)


report_matching_fields(relevant_fields_by_collection)




if __name__ == '__main__':
args = get_commandline_options()
main(args.patterns)

我知道我来晚了,但如果你想在python中快速找到所有键(甚至嵌套的键),你可以用递归函数来做:

def get_keys(dl, keys=None):
keys = keys or []
if isinstance(dl, dict):
keys += dl.keys()
list(map(lambda x: get_keys(x, keys), dl.values()))
elif isinstance(dl, list):
list(map(lambda x: get_keys(x, keys), dl))
return list(set(keys))

像这样使用它:

dl = db.things.find_one({})
get_keys(dl)

如果你的文件没有相同的密钥,你可以这样做:

dl = db.things.find({})
list(set(list(map(get_keys, dl))[0]))

但是这个解决方案肯定是可以优化的。

一般来说,这个解决方案基本上是解决在嵌套字典中查找键,所以这不是mongodb特定的。