Получить имена всех ключей в коллекции

322

Я хотел бы получить имена всех ключей в коллекции MongoDB.

Например, из этого:

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

Я хотел бы получить уникальные ключи:

type, egg, hello
Стив
источник

Ответы:

346

Вы можете сделать это с 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", ...]
кристина
источник
2
Всем привет! Я только что опубликовал продолжение этого вопроса с вопросом, как заставить этот фрагмент работать даже с ключами, расположенными на более глубоких уровнях в структуре данных ( stackoverflow.com/questions/2997004/… ).
Андреа Фиоре
1
@kristina: Как это возможно, что я получаю все вещи, перечисленные с ключами при использовании этого в коллекции вещей . Похоже, это связано с механизмом истории, потому что я получаю вещи, которые я изменил в прошлом ..
Шон,
3
Я знаю, что это старая тема, но у меня, похоже, есть аналогичная необходимость. Я использую собственный драйвер nodejs mongodb. Получающаяся временная коллекция, кажется, всегда пуста. Для этого я использую функцию mapreduce в классе коллекции. Это не возможно?
Дипак
6
Это может быть очевидно, но если вы хотите получить список всех уникальных ключей в поддокументе, просто измените эту строку:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne
3
Вместо того, чтобы сохранять в коллекцию, а затем запускать ее отдельно, я использую map ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley
203

С ответом Кристины в качестве вдохновения я создал инструмент с открытым исходным кодом под названием Variety, который делает именно это: https://github.com/variety/variety.

Джеймс Кропчо
источник
13
Это фантастический инструмент, поздравляю. Он делает именно то, что задает вопрос, и может быть настроен с ограничениями, глубиной и т. Д. Рекомендуется любым, кто следует.
Пол Биггар
74

Вы можете использовать агрегацию с новым $objectToArrrayв 3.4.4версии , чтобы преобразовать все верхний ключ & пару значений в массивы документов с последующими $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: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])
Сагар Вирам
источник
20
Это действительно лучший ответ. Решает проблему, не задействуя какой-либо другой язык программирования или пакет, и работает со всеми драйверами, поддерживающими совокупную структуру (даже Meteor!)
Мика Хеннинг,
2
Если вы хотите вернуть массив, а не курсор, содержащий одну запись карты с ключом «allkeys», вы можете добавить .next()["allkeys"]команду (при условии, что в коллекции есть хотя бы один элемент).
М. Джастин
19

Попробуй это:

doc=db.thinks.findOne();
for (key in doc) print(key);
Карлос Л.М.
источник
49
неверный ответ, так как при этом выводятся только поля для одного документа в коллекции - все остальные могут иметь совершенно разные ключи.
Ася Камская
15
Это все еще самый полезный ответ для меня, будучи простым разумным минимумом.
Борис Бурков
11
Это не полезно? Насколько это полезно, если дает неправильный ответ?
Златко
4
Контекст показывает, что полезно: если данные нормализованы (например, происхождение из файла CSV), это полезно ... Для данных, импортированных из SQL, полезно.
Питер Краусс
5
это не хороший ответ, это ответ о том, как получить ключи одного элемента в коллекции, а не всех ключей в коллекции!
Йонатан
16

Если ваша целевая коллекция не слишком велика, вы можете попробовать это в клиенте оболочки mongo:

var allKeys = {};

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

allKeys;
Ли Чуньлинь
источник
вот как я могу дать regExp для определенных ключей, если я хочу видеть?
TB.M
@ TB.M вы можете попробовать это: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { AllKeys [ключ] = 1}})});
Ли
что значит тест здесь? можешь объяснить, пожалуйста?
TB.M
14

Очищенное и многоразовое решение с использованием пимонго:

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', ... ]
Инго Фишер
источник
1
Прекрасно работает. Наконец-то я решил свою проблему ... это самое простое решение, которое я видел в переполнении стека ...
Smack Alpha
И чтобы фильтровать по типу, просто добавьте, например, if (typeof(this[key]) == 'number')прежде emit(key, null).
Скиппи ле Гран Гуру
10

Использование 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()
)
Laizer
источник
1
Я нашел, что это работает, но насколько это эффективно по сравнению с необработанным запросом Mongod?
Иисус Гомес
1
Я совершенно уверен, что это крайне неэффективно по сравнению с выполнением этого непосредственно в Mongodb
Инго Фишер
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']
BobHy
источник
9

Если вы используете 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" }
  }}
])

Вот рабочий пример

Ashh
источник
Это лучший ответ. Вы также можете использовать $matchв начале конвейера агрегации только для получения ключей документов, которые соответствуют условию (ам).
RonquilloAeon
5

Я удивлен, ни у кого здесь нет ответов с использованием simple javascriptи Setлогики для автоматической фильтрации значений дубликатов, простой пример для оболочки mongo, как показано ниже:

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 .

Кришна Прасад
источник
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);
}
ackuser
источник
3

Я думаю, что лучший способ сделать это, как упомянуто здесь , в 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"
                        }
                    }
                ]
            }
        }
    }
]

Приложив немного усилий, мы можем получить ключ для всего поддокумента в поле массива, где элементы также являются объектами.

styvane
источник
Да $unwindвзорвет коллекцию (no.of fields * no.of docs), мы можем избежать этого, используя $mergeObjectsна всех версиях> 3.6.. Сделал то же самое, Если бы видел этот ответ раньше, моя жизнь была бы проще в этом смысле ( -_-)
Whoami
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)
}

Полезно, когда все объекты в коллекции имеют одинаковую структуру.

QED
источник
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"];
chridam
источник
0

Я пытался написать в 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; 
});
Гаутама
источник
0

Согласно документации 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. Как предлагали некоторые другие ответы, существуют и другие подходы, такие как совокупная структура. Лично я нахожу этот подход более гибким, так как вы можете легко создавать и настраивать способы возврата результатов. Очевидно, что это касается только атрибутов верхнего уровня, а не вложенных. Кроме того, чтобы гарантировать, что все документы представлены, если есть вторичные индексы (кроме основного _id), эти индексы должны быть установлены как required.

jlmurph
источник
0

Мы можем добиться этого, используя mongo js file. Добавьте следующий код в ваш файл getCollectionName.js и запустите файл js в консоли Linux, как показано ниже:

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

Иршад Хан
источник
0

Следуя ветке ответа @James Cropcho, я обнаружил следующее, что оказалось очень простым в использовании. Это бинарный инструмент, который именно то, что я искал: mongoeye .

Используя этот инструмент, потребовалось около 2 минут, чтобы экспортировать мою схему из командной строки.

paneer_tikka
источник
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());
Эндрю Самоле
источник
-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> 
va5ja
источник
3
его ответ неверен, и вы основаны на нем. весь смысл в том, чтобы вывести все поля всех документов, а не первый документ, который может иметь поля, отличные от каждого следующего.
Ася Камская
-3

У меня есть 1 более простая работа вокруг ...

Что вы можете сделать, вставляя данные / документ в основную коллекцию «вещей», вы должны вставить атрибуты в 1 отдельную коллекцию, скажем, «things_attributes».

поэтому каждый раз, когда вы вставляете в "вещи", вы получаете из "вещи_атрибуты" сравниваете значения этого документа с вашими новыми ключами документа, если какой-либо новый присутствующий ключ добавляет его в этот документ и снова повторно вставляет его.

Таким образом, things_attributes будет иметь только 1 документ с уникальными ключами, которые вы можете легко получить, когда захотите, используя findOne ()

Пареш Бехеде
источник
Для баз данных со многими записями, где запросы для всех ключей являются частыми, а вставки нечастыми, имеет смысл кэшировать результат запроса «получить все ключи». Это один из способов сделать это.
Скотт