Dapatkan nama semua kunci dalam koleksi

322

Saya ingin mendapatkan nama semua kunci dalam koleksi MongoDB.

Sebagai contoh, dari ini:

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

Saya ingin mendapatkan kunci unik:

type, egg, hello
Steve
sumber

Jawaban:

346

Anda bisa melakukan ini dengan 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"
})

Kemudian jalankan berbeda pada koleksi yang dihasilkan sehingga menemukan semua kunci:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
kristina
sumber
2
Hai yang disana! Saya baru saja memposting tindak lanjut untuk pertanyaan ini yang menanyakan bagaimana cara membuat snippet ini bekerja bahkan dengan kunci yang terletak pada level yang lebih dalam ke dalam struktur data ( stackoverflow.com/questions/2997004/… ).
Andrea Fiore
1
@ Kristina: Bagaimana mungkin saya mendapatkan seluruh hal yang terdaftar dengan kunci ketika menggunakan ini pada koleksi hal - hal . Tampaknya terkait dengan mekanisme sejarah karena saya mendapatkan hal-hal yang telah saya modifikasi di masa lalu ..
Shawn
3
Saya tahu ini adalah utas lama, tetapi tampaknya saya memiliki kebutuhan yang sama. Saya menggunakan driver asli nodejs mongodb. Koleksi sementara yang dihasilkan tampaknya selalu kosong. Saya menggunakan fungsi mapreduce di kelas koleksi untuk ini. Apakah itu tidak mungkin?
Deepak
6
Ini mungkin terlihat jelas, tetapi jika Anda ingin mendapatkan daftar semua kunci unik di sebuah sub dokumen, cukup modifikasi baris ini:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne
3
Alih-alih menyimpan ke koleksi kemudian menjalankan berbeda itu, saya menggunakan peta ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley
203

Dengan jawaban Kristina sebagai inspirasi, saya menciptakan alat sumber terbuka yang disebut Variety yang melakukan ini: https://github.com/variety/variety

James Cropcho
sumber
13
Ini alat yang luar biasa, selamat. Itu tidak persis apa yang ditanyakan, dan dapat dikonfigurasi dengan batas, kedalaman dll. Direkomendasikan oleh siapa pun yang mengikuti.
Paul Biggar
74

Anda dapat menggunakan agregasi dengan yang baru $objectToArrraydi 3.4.4versi mengkonversi semua tombol atas & pasangan nilai ke array dokumen diikuti oleh $unwind& $group dengan $addToSetmendapatkan kunci yang berbeda di seluruh koleksi.

$$ROOT untuk referensi dokumen tingkat atas.

db.things.aggregate([
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$unwind":"$arrayofkeyvalue"},
  {"$group":{"_id":null,"allkeys":{"$addToSet":"$arrayofkeyvalue.k"}}}
])

Anda dapat menggunakan kueri di bawah ini untuk mendapatkan kunci dalam satu dokumen.

db.things.aggregate([
  {"$match":{_id: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])
Sagar Veeram
sumber
20
Ini benar-benar jawaban terbaik. Memecahkan masalah tanpa melibatkan beberapa bahasa pemrograman lain atau paket, dan bekerja dengan semua driver yang mendukung kerangka kerja agregat (bahkan Meteor!)
Micah Henning
2
Jika Anda ingin mengembalikan array daripada kursor yang berisi entri peta tunggal dengan kunci "allkey", Anda dapat menambahkan .next()["allkeys"]perintah (dengan asumsi koleksi memiliki setidaknya satu elemen).
M. Justin
19

Coba ini:

doc=db.thinks.findOne();
for (key in doc) print(key);
Carlos LM
sumber
49
jawaban yang salah karena ini hanya menghasilkan bidang untuk satu dokumen dalam koleksi - yang lain mungkin memiliki kunci yang sama sekali berbeda.
Asya Kamsky
15
Itu masih jawaban yang paling berguna bagi saya, menjadi minimum yang wajar dan sederhana.
Boris Burkov
11
Itu tidak berguna? Bagaimana ini berguna jika memberi Anda jawaban yang salah?
Zlatko
4
Konteksnya menunjukkan apa yang berguna: jika data dinormalkan (mis. Berasal dari file CSV), itu berguna ... Untuk data yang diimpor dari SQL berguna.
Peter Krauss
5
itu bukan jawaban yang baik itu adalah jawaban tentang cara mendapatkan kunci dari satu elemen dalam koleksi, tidak semua kunci dalam koleksi!
yonatan
16

Jika koleksi target Anda tidak terlalu besar, Anda dapat mencoba ini di bawah mongo shell client:

var allKeys = {};

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

allKeys;
Li Chunlin
sumber
di sini bagaimana saya bisa memberikan regExp untuk kunci tertentu jika saya ingin melihat?
TB.M
@ TB.M Anda dapat mencoba ini: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { allKeys [kunci] = 1}})});
Li Chunlin
Apa maksud tes di sini? bisakah kamu menjelaskan?
TB.M
14

Solusi bersih dan dapat digunakan kembali menggunakan 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')

Pemakaian:

get_keys('dbname', 'collection')
>> ['key1', 'key2', ... ]
Ingo Fischer
sumber
1
Bagus sekali. Akhirnya masalah saya terpecahkan .... ini adalah solusi paling sederhana yang saya lihat di stack overflow ..
Smack Alpha
Dan untuk memfilter menurut jenis, tambahkan saja misalnya if (typeof(this[key]) == 'number')sebelumnya emit(key, null).
Skippy le Grand Gourou
10

Menggunakan python. Mengembalikan set semua kunci tingkat atas dalam koleksi:

#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
sumber
1
Saya menemukan ini berfungsi tetapi seberapa efisien itu dibandingkan dengan permintaan mongod mentah?
Jesus Gomez
1
Saya cukup yakin ini sangat tidak efisien dibandingkan dengan melakukan ini secara langsung di Mongodb
Ingo Fischer
9

Berikut ini contoh yang digunakan dalam Python: Sampel ini mengembalikan hasil 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']
BobHy
sumber
9

Jika Anda menggunakan mongodb 3.4.4 dan yang lebih tinggi maka Anda dapat menggunakan agregasi $objectToArraydan $groupagregasi di bawah ini

db.collection.aggregate([
  { "$project": {
    "data": { "$objectToArray": "$$ROOT" }
  }},
  { "$project": { "data": "$data.k" }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": null,
    "keys": { "$addToSet": "$data" }
  }}
])

Ini contoh kerjanya

Ashh
sumber
Ini jawaban terbaik. Anda juga dapat menggunakan $matchdi awal pipa agregasi untuk hanya mendapatkan kunci dokumen yang cocok dengan suatu kondisi.
RonquilloAeon
5

Saya heran, tidak ada seorang pun di sini yang menggunakan ans dengan logika sederhana javascriptdan Setsecara otomatis memfilter nilai duplikat, contoh sederhana pada mongo shell seperti di bawah ini:

var allKeys = new Set()
db.collectionName.find().forEach( function (o) {for (key in o ) allKeys.add(key)})
for(let key of allKeys) print(key)

Ini akan mencetak semua kunci unik yang mungkin ada dalam nama koleksi : collectionName .

krishna Prasad
sumber
3

Ini berfungsi baik untuk saya:

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
sumber
3

Saya pikir cara terbaik untuk melakukan ini seperti yang disebutkan di sini adalah di mongod 3.4.4+ tetapi tanpa menggunakan $unwindoperator dan hanya menggunakan dua tahap dalam pipa. Sebaliknya kita bisa menggunakan $mergeObjectsdan $objectToArrayoperator.

Pada $grouptahap ini, kami menggunakan $mergeObjectsoperator untuk mengembalikan satu dokumen di mana kunci / nilai berasal dari semua dokumen dalam koleksi.

Lalu tibalah $projecttempat kami menggunakan $mapdan $objectToArraymengembalikan kunci.

let allTopLevelKeys =  [
    {
        "$group": {
            "_id": null,
            "array": {
                "$mergeObjects": "$$ROOT"
            }
        }
    },
    {
        "$project": {
            "keys": {
                "$map": {
                    "input": { "$objectToArray": "$array" },
                    "in": "$$this.k"
                }
            }
        }
    }
];

Sekarang jika kita memiliki dokumen bersarang dan ingin mendapatkan kunci juga, ini bisa dilakukan. Untuk kesederhanaan, mari pertimbangkan dokumen dengan dokumen tersemat sederhana yang terlihat seperti ini:

{field1: {field2: "abc"}, field3: "def"}
{field1: {field3: "abc"}, field4: "def"}

Pipa berikut menghasilkan semua kunci (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"
                        }
                    }
                ]
            }
        }
    }
]

Dengan sedikit usaha, kita bisa mendapatkan kunci untuk semua sub dokumen dalam bidang array di mana elemen-elemennya juga objek.

styvane
sumber
Ya $unwindakan meledak koleksi (no.of bidang * no.of dokumen), kita dapat menghindarinya dengan menggunakan $mergeObjectssemua versi> 3.6.. Lakukan hal yang sama, Seharusnya melihat jawaban ini sebelumnya, hidup saya akan lebih mudah seperti itu ( -_-)
whoami
3

Mungkin sedikit di luar topik, tetapi Anda dapat mencetak semua kunci / bidang suatu objek secara rekursif:

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

Berguna ketika semua objek dalam koleksi memiliki struktur yang sama.

qed
sumber
1

Untuk mendapatkan daftar semua kunci minus _id, pertimbangkan menjalankan pipa agregat berikut:

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
sumber
0

Saya mencoba menulis dalam nodejs dan akhirnya muncul dengan ini:

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

Setelah membaca koleksi yang baru dibuat "allFieldNames", hapus.

db.collection("allFieldNames").remove({}, function (err,result) {
     db.close();
     return; 
});
Gautam
sumber
0

Sesuai dokumentasi mongoldb , kombinasi daridistinct

Menemukan nilai berbeda untuk bidang tertentu di satu koleksi atau melihat dan mengembalikan hasilnya dalam array.

dan operasi pengumpulan indeks adalah apa yang akan mengembalikan semua nilai yang mungkin untuk kunci yang diberikan, atau indeks:

Mengembalikan array yang menyimpan daftar dokumen yang mengidentifikasi dan menjelaskan indeks yang ada pada koleksi

Jadi dalam metode yang diberikan seseorang dapat menggunakan metode seperti yang berikut, untuk menanyakan koleksi untuk semua indeks terdaftarnya, dan kembali, ucapkan objek dengan indeks untuk kunci (contoh ini menggunakan async / menunggu NodeJS, tetapi jelas Anda dapat menggunakan pendekatan asinkron lainnya):

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

Jadi, meminta koleksi dengan _idindeks dasar , akan mengembalikan yang berikut (koleksi tes hanya memiliki satu dokumen pada saat tes):

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

Pikiran Anda, ini menggunakan metode asli ke Driver NodeJS. Seperti yang disarankan beberapa jawaban lain, ada beberapa pendekatan lain, seperti kerangka kerja agregat. Saya pribadi menemukan pendekatan ini lebih fleksibel, karena Anda dapat dengan mudah membuat dan menyempurnakan cara mengembalikan hasil. Jelas, ini hanya membahas atribut tingkat atas, bukan yang bersarang. Juga, untuk menjamin bahwa semua dokumen terwakili jika ada indeks sekunder (selain yang _id utama), indeks tersebut harus ditetapkan sebagai required.

jlmurph
sumber
0

Kita dapat mencapai ini dengan Menggunakan file mongo js. Tambahkan kode di bawah ini di file getCollectionName.js Anda dan jalankan file js di konsol Linux seperti yang diberikan di bawah ini:

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

Terima kasih @ackuser

Irshad Khan
sumber
0

Mengikuti utas dari jawaban @James Cropcho, saya mendarat di yang berikut yang menurut saya super mudah digunakan. Ini adalah alat biner, yang persis seperti apa yang saya cari: mongoeye .

Dengan menggunakan alat ini, dibutuhkan sekitar 2 menit untuk mengekspor skema saya dari baris perintah.

paneer_tikka
sumber
0

Saya tahu pertanyaan ini berumur 10 tahun tetapi tidak ada solusi C # dan ini membutuhkan waktu berjam-jam untuk mencari tahu. Saya menggunakan driver .NET dan System.Linqmengembalikan daftar kunci.

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());
Andrew Samole
sumber
-1

Saya sedikit memperluas solusi Carlos LM sehingga lebih detail.

Contoh skema:

var schema = {
    _id: 123,
    id: 12,
    t: 'title',
    p: 4.5,
    ls: [{
            l: 'lemma',
            p: {
                pp: 8.9
            }
        },
         {
            l: 'lemma2',
            p: {
               pp: 8.3
           }
        }
    ]
};

Ketik ke konsol:

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

Lari:

schemafy(db.collection.findOne());

Keluaran

_id <number>
id <number>
t <string>
p <number>
ls <object>:
    0 <object>:
    l <string>
    p <object>:
        pp <number> 
va5ja
sumber
3
jawabannya salah dan Anda membangun di atasnya. intinya adalah untuk menampilkan semua bidang semua dokumen, bukan dokumen pertama yang mungkin memiliki bidang berbeda dari masing-masing berikutnya.
Asya Kamsky
-3

Saya punya 1 pekerjaan sederhana di ...

Apa yang dapat Anda lakukan adalah ketika memasukkan data / dokumen ke "koleksi" utama Anda, Anda harus memasukkan atribut dalam 1 koleksi terpisah, katakanlah "things_attributes".

jadi setiap kali Anda memasukkan "hal-hal", Anda mendapatkan dari "hal-hal_attributes" membandingkan nilai-nilai dokumen itu dengan kunci dokumen baru Anda jika ada hadiah kunci baru menambahkannya dalam dokumen itu dan memasukkan kembali lagi.

Jadi things_attributes hanya akan memiliki 1 dokumen kunci unik yang dapat Anda dapatkan dengan mudah kapan pun Anda perlukan dengan menggunakan findOne ()

Paresh Behede
sumber
Untuk database dengan banyak entri di mana kueri untuk semua kunci sering dan menyisipkan jarang, caching hasil kueri "dapatkan semua kunci" akan masuk akal. Ini adalah salah satu cara untuk melakukannya.
Scott