Uzyskaj nazwy wszystkich kluczy w kolekcji


322

Chciałbym uzyskać nazwy wszystkich kluczy w kolekcji MongoDB.

Na przykład z tego:

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

Chciałbym zdobyć unikalne klucze:

type, egg, hello

Odpowiedzi:


346

Możesz to zrobić za pomocą 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"
})

Następnie uruchom odrębną kolekcję wynikową, aby znaleźć wszystkie klucze:

db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]

2
Cześć! Właśnie opublikowałem odpowiedź na to pytanie, w której pytam, jak sprawić, by ten fragment działał nawet z kluczami znajdującymi się na głębszych poziomach w strukturze danych ( stackoverflow.com/questions/2997004/... ).
Andrea Fiore,

1
@kristina: Jak to możliwe, że wyświetlam całe rzeczy z kluczami, gdy używam tego w kolekcji rzeczy . Wygląda na związany z mechanizmem historii, ponieważ dostaję rzeczy, które zmodyfikowałem w przeszłości ..
Shawn

3
Wiem, że to stary wątek, ale wydaje mi się, że mam podobną potrzebę. Korzystam z natywnego sterownika nodejs mongodb. Wynikowa kolekcja tymczasowa wydaje się zawsze pusta. Używam do tego funkcji mapreduce w klasie kolekcji. Czy to nie jest możliwe?
Deepak

6
Może to być oczywiste, ale jeśli chcesz uzyskać listę wszystkich unikatowych kluczy w poddokumentie, po prostu zmodyfikuj ten wiersz:for (var key in this.first_level.second_level.nth_level) { emit(key, null); }
dtbarne

3
Zamiast zapisywać w kolekcji, a następnie działa na niej odrębnie, używam map ():db.runCommand({..., out: { "inline" : 1 }}).results.map(function(i) { return i._id; });
Ian Stanley


74

Można użyć agregacji z nowego $objectToArrrayw 3.4.4wersji przekonwertować cały górny klawisz i parę wartości do tablic dokumentów następnie $unwind& $group z $addToSet, aby uzyskać różne klucze na całej kolekcji.

$$ROOT do odwoływania się do dokumentu najwyższego poziomu.

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

Możesz użyć poniższego zapytania, aby uzyskać klucze w jednym dokumencie.

db.things.aggregate([
  {"$match":{_id: "5e8f968639bb8c67726686bc"}}, /* Replace with the document's ID */
  {"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
  {"$project":{"keys":"$arrayofkeyvalue.k"}}
])

20
To naprawdę najlepsza odpowiedź. Rozwiązuje problem bez angażowania innego języka programowania lub pakietu i współpracuje ze wszystkimi sterownikami, które obsługują agregację frameworka (nawet Meteor!)
Micah Henning

2
Jeśli chcesz zwrócić tablicę zamiast kursora zawierającego pojedynczy wpis mapy za pomocą klawisza „allkeys”, możesz dołączyć .next()["allkeys"]do polecenia (zakładając, że kolekcja zawiera co najmniej jeden element).
M. Justin,

19

Spróbuj tego:

doc=db.thinks.findOne();
for (key in doc) print(key);

49
niepoprawna odpowiedź, ponieważ powoduje to wyświetlenie pól tylko dla jednego dokumentu w kolekcji - pozostałe mogą mieć zupełnie inne klucze.
Asya Kamsky

15
Nadal jest to dla mnie najbardziej użyteczna odpowiedź, ponieważ jest to proste rozsądne minimum.
Boris Burkov

11
To nie jest przydatne? Jak to jest przydatne, jeśli daje złą odpowiedź?
Zlatko

4
Kontekst pokazuje, co jest przydatne: jeśli dane są znormalizowane (np. Pochodzą z pliku CSV), jest to przydatne ... Przydatne są dane importowane z SQL.
Peter Krauss,

5
to nie jest dobra odpowiedź, to odpowiedź na pytanie, jak zdobyć klucze do jednego elementu w kolekcji, nie wszystkie klucze w kolekcji!
yonatan

16

Jeśli twoja kolekcja docelowa nie jest zbyt duża, możesz wypróbować to w kliencie powłoki mongo:

var allKeys = {};

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

allKeys;

tutaj, jak mogę podać regExp dla poszczególnych kluczy, jeśli chcę je zobaczyć?
TB.M

@ TB.M możesz spróbować: db.configs.find (). ForEach (function (doc) {Object.keys (doc) .forEach (function (key) {if (/YOURREGEXP/.test(key)) { allKeys [klucz] = 1}})});
Li Chunlin

co tutaj oznacza test? czy możesz wyjaśnić?
TB.M


14

Oczyszczone i wielokrotnego użytku rozwiązanie za pomocą 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')

Stosowanie:

get_keys('dbname', 'collection')
>> ['key1', 'key2', ... ]

1
Działa świetnie. W końcu udało mi się rozwiązać problem .... to najprostsze rozwiązanie, jakie widziałem w przepełnieniu stosu.
Smack Alpha

Aby filtrować według typu, wystarczy dodać np . if (typeof(this[key]) == 'number')Wcześniej emit(key, null).
Skippy le Grand Gourou

10

Używanie pytona. Zwraca zestaw wszystkich kluczy najwyższego poziomu w kolekcji:

#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
Przekonałem się, że to działa, ale jak wydajne jest w porównaniu do surowego zapytania Mongod?
Jesus Gomez

1
Jestem całkiem pewien, że jest to wyjątkowo nieefektywne w porównaniu do robienia tego bezpośrednio w Mongodb
Ingo Fischer

9

Oto przykład działający w Pythonie: Ta próbka zwraca wyniki bezpośrednio w wierszu.

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

Jeśli używasz mongodb 3.4.4 i nowszych, możesz użyć poniżej agregacji za pomocą $objectToArrayi $groupagregacji

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

Oto działający przykład


To najlepsza odpowiedź. Można również użyć $matchna początku potoku agregacji, aby uzyskać tylko klucze dokumentów spełniających warunki.
RonquilloAeon

5

Dziwię się, że nikt tutaj nie ma ans, używając prostej javascripti Setlogicznej metody automatycznego filtrowania duplikatów wartości, prosty przykład na powłoce mangowej, jak poniżej:

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

Spowoduje to wydrukowanie wszystkich możliwych unikalnych kluczy w nazwie kolekcji : collectionName .


3

To działa dobrze dla mnie:

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

Myślę, że najlepszym sposobem, aby to zrobić, jak wspomniano tutaj, jest mongod 3.4.4+, ale bez użycia $unwindoperatora i tylko dwóch etapów w przygotowaniu. Zamiast tego możemy użyć operatorów $mergeObjectsi $objectToArray.

Na $groupetapie używamy $mergeObjectsoperatora, aby zwrócić pojedynczy dokument, w którym klucz / wartość pochodzą ze wszystkich dokumentów w kolekcji.

Potem przychodzi miejsce, w $projectktórym korzystamy $mapi zwracamy $objectToArrayklucze.

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

Teraz, jeśli mamy zagnieżdżone dokumenty i chcemy również uzyskać klucze, jest to wykonalne. Dla uproszczenia rozważmy dokument z prostym osadzonym dokumentem, który wygląda następująco:

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

Poniższy potok daje wszystkie klucze (pole1, pole2, pole3, pole4).

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

Przy odrobinie wysiłku możemy uzyskać klucz do całego dokumentu podrzędnego w polu tablicy, w której elementy również są obiektami.


Tak $unwind, eksploduje kolekcję (liczba pól * liczba dokumentów), możemy tego uniknąć, używając $mergeObjectswszystkich wersji> 3.6.. Czy to samo, Powinienem był zobaczyć tę odpowiedź wcześniej, moje życie byłoby łatwiejsze w ten sposób ( -_-)
whoami

3

Może nieco nie na temat, ale możesz rekurencyjnie wydrukować wszystkie klucze / pola obiektu:

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

Przydatne, gdy wszystkie obiekty w kolekcji mają tę samą strukturę.


1

Aby uzyskać listę wszystkich kluczy minus _id, rozważ uruchomienie następującego potoku agregacji:

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

Próbowałem pisać w nodejs i wreszcie wymyśliłem:

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

Po przeczytaniu nowo utworzonej kolekcji „allFieldNames” usuń ją.

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

0

Zgodnie z dokumentacją mongoldb , połączeniedistinct

Znajduje odrębne wartości dla określonego pola w pojedynczej kolekcji lub widoku i zwraca wyniki w tablicy.

a operacje zbierania indeksów zwracają wszystkie możliwe wartości dla danego klucza lub indeksu:

Zwraca tablicę zawierającą listę dokumentów identyfikujących i opisujących istniejące indeksy w kolekcji

Tak więc w danej metodzie można użyć metody podobnej do następnej, w celu przeszukania kolekcji dla wszystkich zarejestrowanych indeksów i zwrócenia, powiedzmy obiektu z indeksami dla kluczy (w tym przykładzie użyto asynchronizacji / oczekiwania na NodeJS, ale oczywiście możesz użyć dowolnego innego podejścia asynchronicznego):

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

Zapytanie o kolekcję z _idindeksem podstawowym zwróciłoby następujące elementy (kolekcja testowa ma tylko jeden dokument w momencie testu):

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

Pamiętaj, że używa to metod natywnych dla sterownika NodeJS. Jak sugerują niektóre inne odpowiedzi, istnieją inne podejścia, takie jak struktura zagregowana. Osobiście uważam to podejście za bardziej elastyczne, ponieważ można łatwo tworzyć i dostosowywać sposób zwracania wyników. Oczywiście dotyczy to tylko atrybutów najwyższego poziomu, a nie zagnieżdżonych. Ponadto, aby zagwarantować, że wszystkie dokumenty będą reprezentowane w przypadku indeksów wtórnych (innych niż główny _id), indeksy te należy ustawić jako required.


0

Możemy to osiągnąć za pomocą pliku js mongo. Dodaj poniższy kod w swoim pliku getCollectionName.js i uruchom plik js w konsoli systemu Linux, jak podano poniżej:

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

Dzięki @ackuser


0

Podążając za wątkiem z odpowiedzi Jamesa Cropcho, wylądowałem na następujących, które okazały się bardzo łatwe w użyciu. Jest to narzędzie binarne, którego dokładnie szukałem: mongoeye .

Za pomocą tego narzędzia wyeksportowanie mojego schematu z wiersza polecenia zajęło około 2 minut.


0

Wiem, że to pytanie ma 10 lat, ale nie ma rozwiązania C # i zajęło mi to kilka godzin. Korzystam ze sterownika .NET i zwracam System.Linqlistę kluczy.

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

Rozbudowałem nieco rozwiązanie Carlosa LM, aby było bardziej szczegółowe.

Przykład schematu:

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

Wpisz w konsoli:

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

Biegać:

schemafy(db.collection.findOne());

Wynik

_id <number>
id <number>
t <string>
p <number>
ls <object>:
    0 <object>:
    l <string>
    p <object>:
        pp <number> 

3
jego odpowiedź jest zła, a ty zbudowałeś na niej. chodzi o to, aby wyprowadzić wszystkie pola wszystkich dokumentów, a nie pierwszy dokument, który może mieć inne pola niż każdy następny.
Asya Kamsky

-3

Mam 1 prostsze obejście ...

To, co możesz zrobić, to wstawiając dane / dokument do głównej kolekcji „rzeczy”, musisz wstawić atrybuty do 1 osobnej kolekcji, powiedzmy „rzeczy_atrybuty”.

więc za każdym razem, gdy wstawiasz „rzeczy”, z „rzeczy_atrybuty” porównujesz wartości tego dokumentu z nowymi kluczami dokumentu, jeśli jakikolwiek nowy klucz dołącza go do tego dokumentu i ponownie wstawia.

Więc Things_attributes będzie mieć tylko 1 dokument unikatowych kluczy, które możesz łatwo uzyskać, kiedy tylko potrzebujesz za pomocą findOne ()


W przypadku baz danych z wieloma wpisami, w których zapytania dotyczące wszystkich kluczy są częste, a wstawki są rzadkie, buforowanie wyniku zapytania „pobierz wszystkie klucze” ma sens. Jest to jeden ze sposobów, aby to zrobić.
Scott
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.