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


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.