Отримайте назви всіх ключів у колекції


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: Як можливо, я отримую цілі речі, перелічені за допомогою клавіш, під час використання цього в колекції речей . Це схоже на механізм історії, тому що я отримую речі, які я змінив у минулому ..
Shawn,

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 (функція (doc) {Object.keys (doc) .forEach (функція (клавіша) {if (/YOURREGEXP/.test(key)) { allKeys [key] = 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).
Skippy le Grand Gourou

10

Використання пітона. Повертає набір усіх клавіш верхнього рівня в колекції:

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

1
Я виявив, що це працює, але наскільки це ефективно в порівнянні з необробленим запитом монгода?
Ісус Гомес

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

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

Ось робочий приклад


Це найкраща відповідь. Ви також можете використовувати $matchна початку конвеєра агрегації лише отримання ключів документів, які відповідають умовам.
RonquilloAeon

5

Мене дивує, що тут ніхто не має ans, використовуючи просту 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);
}

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"
                        }
                    }
                ]
            }
        }
    }
]

Трохи доклавши зусиль, ми зможемо отримати ключ для всіх піддокументів у полі масиву, де елементи також є об’єктом.


Так $unwind, вибухне колекція (no.of поля * 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)
}

Корисно, коли всі об'єкти в колекції мають однакову структуру.


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"];

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 / wait для 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.


0

Ми можемо досягти цього, використовуючи файл mongo js. Додайте нижче код у свій файл 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 хвилин, щоб мою схему експортували з командного рядка.


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

Я трохи продовжив рішення Карлоса ЛМ, щоб воно було більш детальним.

Приклад схеми:

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> 

3
його відповідь неправильна, і ви побудували поверх цього. вся суть полягає у виведенні всіх полів усіх документів, а не першого документа, який може мати різні поля, ніж кожен наступний.
Ася Камський

-3

У мене є 1 простіша робота ...

Що ви можете зробити, це вставляючи дані / документ у свою основну колекцію "речі", ви повинні вставити атрибути в 1 окрему колекцію, щоб сказати "things_attributes".

тож кожен раз, коли ви вставляєте "речі", ви отримуєте з "things_attributes" порівнювати значення цього документа з вашими новими ключами документа, якщо будь-який новий присутній ключ додає його до цього документа і знову його вставляє.

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


Для баз даних з багатьма записами, де запити для всіх ключів є частими, а вставки нечасті, кешування результату запиту "дістати всі ключі" має сенс. Це один із способів зробити це.
Скотт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.