355

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

例如,从此:

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

我想获得唯一键:

type, egg, hello
4

23 回答 23

376

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

然后在结果集合上运行 distinct 以找到所有键:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
于 2010-02-22T00:03:21.037 回答
223

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

于 2012-04-28T17:53:52.677 回答
123

您可以将聚合与3.4.4$objectToArray版中的新聚合运算符一起使用,将所有顶部键值对转换为文档数组,然后使用with来获取整个集合中的不同键。(用于引用顶级文档。)$unwind$group$addToSet$$ROOT

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"}}
])
于 2017-04-23T11:45:11.260 回答
21

使用 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', ... ]
于 2018-01-05T16:46:45.217 回答
18

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

var allKeys = {};

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

allKeys;
于 2016-12-26T06:38:57.437 回答
15

如果您使用的是 mongodb 3.4.4 及更高版本,那么您可以使用以下聚合 using$objectToArray$groupaggregation

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

这是工作示例

于 2018-10-02T04:05:05.673 回答
13

试试这个:

doc=db.thinks.findOne();
for (key in doc) print(key);
于 2014-03-06T12:34:58.510 回答
11

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

#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()
)
于 2014-08-11T09:12:18.383 回答
9

这是在 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']
于 2014-04-25T00:42:24.723 回答
6

我认为这里提到的最好的方法是在 mongod 3.4.4+ 中,但不使用$unwind操作符并且只使用管道中的两个阶段。相反,我们可以使用$mergeObjectsand$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"
                        }
                    }
                ]
            }
        }
    }
]

稍加努力,我们就可以在元素也是对象的数组字段中获取所有子文档的键。

于 2018-04-10T21:06:54.127 回答
6

我很惊讶,这里没有人通过使用简单javascriptSet逻辑来自动过滤重复值,下面是mongo shell上的简单示例:

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

于 2019-04-16T05:46:54.933 回答
3

这对我来说很好:

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);
}
于 2016-05-04T14:54:06.473 回答
3

可能有点题外话,但您可以递归地漂亮地打印对象的所有键/字段:

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

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

于 2018-10-12T09:59:44.450 回答
1

要获取所有键减号的列表_id,请考虑运行以下聚合管道:

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"];
于 2018-02-25T21:59:25.160 回答
1

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

"""
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)
于 2021-07-21T09:31:41.243 回答
0

根据 mongoldb文档,结合了distinct

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

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

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

因此,在给定的方法中,可以使用类似以下的方法,以查询集合中所有已注册的索引,并返回,例如带有键索引的对象(此示例对 NodeJS 使用 async/await,但是显然你可以使用任何其他异步方法):

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 驱动程序的本地方法。正如其他一些答案所暗示的那样,还有其他方法,例如聚合框架。我个人觉得这种方法更灵活,因为您可以轻松创建和微调如何返回结果。显然,这仅涉及顶级属性,而不是嵌套属性。此外,为了保证所有文档都被表示应该有二级索引(除了主 _id 一个),这些索引应该设置为required.

于 2018-05-14T23:44:00.673 回答
0

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

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

谢谢@ackuser

于 2018-12-25T13:21:12.927 回答
0

根据@James Cropcho 的回答,我找到了以下内容,我发现它非常易于使用。它是一个二进制工具,正是我想要的: mongoeye

使用此工具大约需要 2 分钟才能从命令行导出我的架构。

于 2019-08-14T13:16:49.783 回答
0

我知道这个问题已有 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());
于 2020-04-03T21:20:25.710 回答
0

我知道我迟到了,但如果你想在 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 特定的。

于 2022-01-31T14:14:27.073 回答
-1

我扩展了 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> 
于 2014-03-28T13:37:47.770 回答
-1

我试图用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; 
});
于 2017-10-10T09:43:39.027 回答
-3

我有1个更简单的解决方法......

您可以做的是在将数据/文档插入主集合“事物”时,您必须将属性插入到 1 个单独的集合中,比如说“things_attributes”。

因此,每次插入“things”时,都会从“things_attributes”将该文档的值与新文档键进行比较,如果存在任何新键将其附加到该文档中并再次重新插入它。

所以 things_attributes 将只有 1 个唯一键文档,您可以在需要时使用 findOne() 轻松获取

于 2014-03-21T11:41:39.757 回答