Получить имена всех ключей в коллекции
Я хотел бы получить имена всех ключей в коллекции MongoDB.
например, из этого:
db.things.insert( { type : ['dog', 'cat'] } );
db.things.insert( { egg : ['cat'] } );
db.things.insert( { type : [] } );
db.things.insert( { hello : [] } );
Я хотел бы получить уникальные ключи:
type, egg, hello
17 ответов:
Вы можете сделать это с помощью 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", ...]
с Кристины в качестве вдохновения я создал инструмент с открытым исходным кодом под названием Variety, который делает именно это:https://github.com/variety/variety
вы можете использовать агрегацию с new
$objectToArrray
in3.4.4
версия для преобразования всех пар top key & value в массивы документов, за которыми следует$unwind
&$group
С$addToSet
чтобы получить различные ключи по всей коллекции.
$$ROOT
для ссылки на документ верхнего уровня.db.things.aggregate([ {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}}, {"$unwind":"$arrayofkeyvalue"}, {"$group":{"_id":null,"allkeys":{"$addToSet":"$arrayofkeyvalue.k"}}} ])
вы можете использовать ниже запрос для получения ключей в одном документ.
db.things.aggregate([ {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}}, {"$project":{"keys":"$arrayofkeyvalue.k"}} ])
Если ваша целевая коллекция не слишком велика, вы можете попробовать это в клиенте Mongo shell:
var allKeys = {}; db.YOURCOLLECTION.find().forEach(function(doc){Object.keys(doc).forEach(function(key){allKeys[key]=1})}); allKeys;
использование 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() )
вот пример работы в Python: Этот пример возвращает результаты inline.
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']
Это прекрасно работает для меня:
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); }
очищенное и многоразовое решение с использованием 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', ... ]
Я думаю, что лучший способ сделать это, как упоминалось здесь находится в 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" } } ] } } } ]
С небольшим усилием мы можем получить ключ для всех вложенных документов в поле массива, где элементы являются объектом как что ж.
Я пытался писать в 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; });
чтобы получить список всех ключей минус
_id
, рассмотрите возможность запуска следующего конвейера aggregate: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"];
согласно 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
index, вернет следующее (тестовая коллекция имеет только один документ время проведения теста):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 one), эти индексы должны быть установлены как
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" } }} ])
вот работает пример
Я немного расширил решение 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; } } }
Run:
schemafy(db.collection.findOne());
выход
_id <number> id <number> t <string> p <number> ls <object>: 0 <object>: l <string> p <object>: pp <number>
У меня есть 1 проще обойти...
Что вы можете сделать, это при вставке данных / документа в вашу основную коллекцию "вещи" вы должны вставить атрибуты в 1 отдельную коллекцию, скажем "things_attributes".
поэтому каждый раз, когда вы вставляете в "вещи", вы получаете от "things_attributes" сравнить значения этого документа с новыми ключами документа, если какой-либо новый ключ присутствует добавить его в этот документ и снова вставить его.
Так things_attributes будет есть только 1 документ уникальных ключей, которые вы можете легко получить, когда вам нужно с помощью findOne ()