【问题标题】:Get names of all keys in the collection获取集合中所有键的名称
【发布时间】:2011-01-18 22:10:45
【问题描述】:

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

例如,从这个:

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

我想获取唯一键:

type, egg, hello

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案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)
    

    【讨论】:

      【解决方案2】:

      您可以将聚合与 3.4.4 版本中的新 $objectToArray 聚合运算符一起使用,将所有顶部键值对转换为文档数组,然后使用 $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"}}
      ])
      

      【讨论】:

      • 这确实是最好的答案。在不涉及其他编程语言或包的情况下解决问题,并与支持聚合框架的所有驱动程序一起使用(甚至 Meteor!)
      • 如果你想返回一个数组而不是一个包含带有“allkeys”键的单个映射条目的游标,你可以将.next()["allkeys"]附加到命令中(假设集合至少有一个元素) .
      • 我会注意到,来自@kristina 答案的聚合在我的集合上需要 11 秒,而 Map Recude 需要 2 秒)。我没想到。
      • 这在包含数百万个地图缩减超时的文档的集合上对我有用。
      • 我也投了这个票。毕竟它是原生的...
      【解决方案3】:

      我知道这个问题已有 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());
      

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          • 效果很好。终于解决了我的问题..这是我在堆栈溢出中看到的最简单的解决方案..
          • 并按类型过滤,只需添加例如if (typeof(this[key]) == 'number')emit(key, null) 之前。
          • 注意:使用 MongoDB 免费套餐,我得到错误 pymongo.errors.OperationFailure: CMD_NOT_ALLOWED: mapReduce, full error: {'ok': 0, 'errmsg': 'CMD_NOT_ALLOWED: mapReduce', 'code': 8000, 'codeName': 'AtlasError'} 显然是因为免费套餐不支持 mapReduce MongoDB unsupported-commands
          【解决方案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

          【讨论】:

            【解决方案7】:

            我们可以通过使用 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

            【讨论】:

              【解决方案8】:

              如果您使用的是 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" }
                }}
              ])
              

              这是工作的example

              【讨论】:

              • 这是最好的答案。您还可以在聚合管道的开头使用$match 来仅获取与条件匹配的文档的键。
              【解决方案9】:

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

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

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

              【讨论】:

                【解决方案10】:

                根据mongoldb documentationdistinct的组合

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

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

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

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

                【讨论】:

                  【解决方案11】:

                  我认为here 提到的最佳方法是在 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"
                                          }
                                      }
                                  ]
                              }
                          }
                      }
                  ]
                  

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

                  【讨论】:

                  • 是的 $unwind 会爆炸集合(字段数 * 文档数),我们可以通过在所有版本上使用 $mergeObjects 来避免这种情况 > 3.6.. 也一样,应该'以前看过这个答案,那样我的生活会更轻松(-_-)
                  【解决方案12】:

                  要获取减去_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"];
                  

                  【讨论】:

                    【解决方案13】:

                    我试图用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; 
                    });
                    

                    【讨论】:

                      【解决方案14】:

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

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

                      【讨论】:

                      • 如果我想查看特定键,我如何提供 regExp?
                      • @TB.M 你可以试试这个:db.configs.find().forEach(function(doc){Object.keys(doc).forEach(function(key){ if (/YOURREGEXP /.test(key)) {allKeys[key]=1}})});
                      • 这里的测试是什么意思?你能解释一下吗?
                      【解决方案15】:

                      这对我来说很好用:

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

                      【讨论】:

                        【解决方案16】:

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

                        【讨论】:

                        • 这是一个很棒的工具,恭喜。它完全符合问题的要求,并且可以配置限制、深度等。任何关注的人都推荐。
                        【解决方案17】:

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

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

                        【讨论】:

                        • 我发现这是可行的,但与原始 mongod 查询相比效率如何?
                        • 我很确定这与直接在 Mongodb 中执行此操作相比效率极低
                        【解决方案18】:

                        您可以使用 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", ...]
                        

                        【讨论】:

                        • 您好!我刚刚发布了这个问题的后续内容,询问如何使这个 sn-p 工作,即使键位于数据结构的更深层次 (stackoverflow.com/questions/2997004/…)。
                        • @kristina :在 things 集合上使用它时,我怎么可能将整个 things 与键一起列出。它看起来与历史机制有关,因为我得到了我过去修改过的 things..
                        • 我知道这是一个旧线程,但我似乎有类似的需求。我正在使用 nodejs mongodb 本机驱动程序。由此产生的临时集合似乎总是空的。为此,我在集合类中使用了 mapreduce 函数。这不可能吗?
                        • 这可能很明显,但是如果你想得到一个子文档中所有唯一键的列表,只需修改这一行:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
                        • 我没有保存到一个集合然后在其上运行不同,而是使用 map():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
                        【解决方案19】:

                        这是在 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']
                        

                        【讨论】:

                          【解决方案20】:

                          我稍微扩展了 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> 
                          

                          【讨论】:

                          • 他的答案是错误的,而您是在此基础上构建的。重点是输出 all all 文档的字段,而不是第一个文档,它可能具有与下一个文档不同的字段。
                          【解决方案21】:

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

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

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

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

                          【讨论】:

                          • 对于具有许多条目的数据库,其中对所有键的查询很频繁而插入很少,缓存“获取所有键”查询的结果是有意义的。这是一种方法。
                          【解决方案22】:

                          试试这个:

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

                          【讨论】:

                          • 错误答案,因为这仅输出集合中单个文档的字段 - 其他文档可能都有完全不同的键。
                          • 这对我来说仍然是最有用的答案,是一个简单合理的最小值。
                          • 没用?如果它给你错误的答案有什么用?
                          • 上下文显示什么是有用的:如果数据被规范化(例如来自 CSV 文件的来源),它是有用的...对于从 SQL 导入的数据是有用的。
                          • 这不是一个好的答案,它是关于如何获取集合中 one 元素的键而不是集合中的 all 键的答案!
                          猜你喜欢
                          • 1970-01-01
                          • 2021-01-10
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多