Używanie map/reduce do mapowania właściwości w kolekcji

Update: kontynuacja MongoDB pobiera nazwy wszystkich kluczy w kolekcji .

Jak wskazuje Kristina , można użyć map/reduce Mongodb 'a, aby wyświetlić listę kluczy w kolekcji:

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

mr = db.runCommand({"mapreduce" : "things",
"map" : function() {
    for (var key in this) { emit(key, null); }
},  
"reduce" : function(key, stuff) { 
   return null;
}}) 

db[mr.result].distinct("_id")

//output: [ "_id", "egg", "hello", "type" ]

Tak długo, jak chcemy uzyskać tylko klucze znajdujące się na pierwszym poziomie głębi, to działa dobrze. Jednak nie uda się odzyskać tych kluczy, które znajdują się na głębszych poziomach. Jeśli dodamy nowy rekord:

db.things.insert({foo: {bar: {baaar: true}}})

I uruchamiamy ponownie mapę-zmniejsz + wyraźny fragment powyżej, otrzymamy:

[ "_id", "egg", "foo", "hello", "type" ] 

Ale nie otrzymamy bar i baaar kluczy, które są zagnieżdżone w strukturze danych. Pytanie brzmi: jak odzyskać wszystkie klucze, bez względu na ich głębokość? Najlepiej byłoby, gdyby skrypt przeszedł na cały poziom głębi, tworząc wyjście takie jak:

["_id","egg","foo","foo.bar","foo.bar.baaar","hello","type"]      
Z góry dziękuję!
Author: Community, 2010-06-08

4 answers

OK, to jest trochę bardziej skomplikowane, ponieważ musisz użyć rekurencji.

Aby rekurencja się odbyła, musisz mieć możliwość przechowywania niektórych funkcji na serwerze.

Krok 1: zdefiniuj niektóre funkcje i umieść je po stronie serwera

isArray = function (v) {
  return v && typeof v === 'object' && typeof v.length === 'number' && !(v.propertyIsEnumerable('length'));
}

m_sub = function(base, value){
  for(var key in value) {
    emit(base + "." + key, null);
    if( isArray(value[key]) || typeof value[key] == 'object'){
      m_sub(base + "." + key, value[key]);
    }
  }
}

db.system.js.save( { _id : "isArray", value : isArray } );
db.system.js.save( { _id : "m_sub", value : m_sub } );

Krok 2: zdefiniuj mapę i zmniejsz funkcje

map = function(){
  for(var key in this) {
    emit(key, null);
    if( isArray(this[key]) || typeof this[key] == 'object'){
      m_sub(key, this[key]);
    }
  }
}

reduce = function(key, stuff){ return null; }

Krok 3: Uruchom mapę redukuj i spójrz na wyniki

mr = db.runCommand({"mapreduce" : "things", "map" : map, "reduce" : reduce,"out": "things" + "_keys"});
db[mr.result].distinct("_id");

Wyniki, które otrzymasz to:

["_id", "_id.isObjectId", "_id.str", "_id.tojson", "egg", "egg.0", "foo", "foo.bar", "foo.bar.baaaar", "hello", "type", "type.0", "type.1"]

Jest tu jeden oczywisty problem, dodajemy kilka nieoczekiwanych pól tutaj: 1. dane _id 2. the .0 (Na jajko i typ)

Krok 4: niektóre możliwe poprawki

Dla problem #1 poprawka jest stosunkowo łatwa. Wystarczy zmodyfikować funkcję map. Zmień to:

emit(base + "." + key, null); if( isArray...

Do tego:

if(key != "_id") { emit(base + "." + key, null); if( isArray... }

Problem # 2 jest trochę bardziej ryzykowny. Chciałeś wszystkie klucze i technicznie "jajko".0 " jest poprawnym kluczem. Możesz zmodyfikować m_sub, aby ignorować takie klucze numeryczne. Ale łatwo też dostrzec sytuację, w której to się odwraca. Powiedzmy, że masz tablicę asocjacyjną wewnątrz zwykłej tablicy, wtedy chcesz, aby pojawiło się "0". Resztę rozwiązania zostawię Tobie.

 27
Author: Gates VP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-01 15:06:09

Z odpowiedziami Gatesa i Kristiny jako inspiracją, stworzyłem narzędzie open source o nazwie Variety, które robi dokładnie to: https://github.com/variety/variety

Mam nadzieję, że się przyda. Daj mi znać, jeśli masz pytania lub problemy z jego użyciem.

 8
Author: James Cropcho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-02 08:48:50

Jako funkcja prosta;

const getProps = (db, collection) => new Promise((resolve, reject) => {
  db
  .collection(collection)
  .mapReduce(function() {
    for (var key in this) { emit(key, null) }
  }, (prev, next) => null, {
    out: collection + '_keys'
  }, (err, collection_props) => {
    if (err) reject(err)

    collection_props
    .find()
    .toArray()
    .then(
      props => resolve(props.map(({_id}) => _id))
    )
  })
})
 0
Author: Ahmet Şimşek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-01 21:23:46

I solved problem #2 podane przez Gates, gdzie na przykład Dane.0, data.1, data.2 został zwrócony. Mimo że są to ważne klucze, jak wspomniano powyżej, chciałem się ich pozbyć w celach prezentacyjnych. Rozwiązałem to przez szybką edycję w funkcji m_sub, Jak pokazano poniżej.

const m_sub = function (base, value) {
for (var key in value) {
    if(key != "_id" && isNaN(key)){
        emit(base + "." + key, null);
        if (isArray(value[key]) || typeof value[key] == 'object') {
            m_sub(base + "." + key, value[key]);
        }
    }
}

Ta zmiana ma również powyższe rozwiązanie problemu #1 zaimplementowano i jedyną wprowadzoną zmianą jest pierwsza instrukcja if, w której zmieniłem to:

if(key != "_id")

Do tego korzystanie z funkcji isNaN(x) :

if(key != "_id" && isNaN(key))

Mam nadzieję, że to komuś pomoże, a jeśli jest problem z tym rozwiązaniem, proszę o opinie!

 0
Author: Andreas Häggström,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-02 08:01:01