Использование map/reduce для сопоставления свойств в коллекции

Обновление: продолжение MongoDB Получить имена всех ключей в коллекции.

Как указала Кристина, можно использовать карту/уменьшение Mongodb для перечисления ключей в коллекции:

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

Пока мы хотим получить только ключи, расположенные на первом уровне глубины, это работает нормально. Однако получить те ключи, которые расположены на более глубоких уровнях, не удастся. Если мы добавим новую запись:

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

И снова запустим сниппет map-reduce +distinct выше, получим:

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

Но мы не получим ключи bar и baaar, которые вложены в структуру данных. Вопрос в том, как мне получить все ключи, независимо от их уровня глубины? В идеале я бы хотел, чтобы скрипт спускался на все уровни глубины, производя вывод, например:

["_id","egg","foo","foo.bar","foo.bar.baaar","hello","type"]      

Заранее спасибо!


person Andrea Fiore    schedule 08.06.2010    source источник


Ответы (4)


Хорошо, это немного сложнее, потому что вам нужно будет использовать некоторую рекурсию.

Чтобы рекурсия произошла, вам нужно иметь возможность хранить некоторые функции на сервере.

Шаг 1: определите некоторые функции и разместите их на стороне сервера

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

Шаг 2: определите карту и функции редукции

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

Шаг 3: запустите уменьшение карты и посмотрите на результаты

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

Результаты, которые вы получите:

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

Здесь есть одна очевидная проблема, мы добавляем сюда несколько неожиданных полей: 1. данные _id 2. .0 (яйцо и тип)

Шаг 4: Некоторые возможные исправления

Для проблемы №1 исправить относительно просто. Просто измените функцию map. Измените это:

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

к этому:

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

Проблема №2 немного сложнее. Вам нужны все ключи, и технически "egg.0" является действительным ключом. Вы можете изменить m_sub, чтобы игнорировать такие цифровые клавиши. Но также легко увидеть ситуацию, когда это имеет неприятные последствия. Скажем, у вас есть ассоциативный массив внутри обычного массива, тогда вы хотите, чтобы появился этот «0». Я оставлю остальную часть этого решения на ваше усмотрение.

person Gates VP    schedule 10.06.2010
comment
Спасибо Гейтс! Я также нашел другое решение (которое, однако, не связано с использованием map/reduce), описанное здесь: groups.google.com/group/mongodb-user/browse_thread/thread/ - person Andrea Fiore; 13.06.2010
comment
Смотрите новый код. Первая часть шага 3 относится к функциям, названным в шаге 2. - person Gates VP; 22.10.2013
comment
Итак, дата этого ответа — июнь 2010 г., вполне вероятно, что вам потребуется добавить новый параметр out, которого не существовало на момент написания этой статьи. Честно говоря, вы, вероятно, даже не захотите использовать M/R для этого, поскольку это, вероятно, можно сделать с помощью гораздо лучшей Aggregation Framework. - person Gates VP; 23.10.2013
comment
Вы чертовски круты даже в 2017 году! - person Orelsanpls; 15.09.2017

Вдохновившись ответами вице-президента Гейтса и Кристины, я создал инструмент с открытым исходным кодом под названием Variety, который делает именно это: https://github.com/variety/variety

Надеюсь, вы найдете его полезным. Дайте мне знать, если у вас есть вопросы или проблемы с его использованием.

person James Cropcho    schedule 28.04.2012
comment
То, что нам нужен инструмент с открытым исходным кодом для запроса схемы, немного грустно. Я вижу причину, по которой люди выбирают MongoDB: использовать ее пригодность для истинного хранения документов (в том смысле, что она сильно вложена, или структура не может быть известна априори, или природа запросов такова, что по какой-то причине только MongoDB является способом хранения документов). go), во-первых, и, во-вторых, как способ для ленивых или младших разработчиков избежать использования системы баз данных с более жесткой структурой (обычно реляционной). Последнее, по моему опыту, слишком распространено, особенно среди стартапов. То, что JSON прост, не означает, что это правильно. - person Adam Donahue; 10.04.2014

Я решил проблему #2, заявленную Гейтсом, где, например, были возвращены data.0, data.1, data.2. Несмотря на то, что это действительные ключи, как указано выше, я хотел избавиться от них в целях презентации. Я решил это, быстро отредактировав функцию m_sub, как показано ниже.

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

Это изменение также включает описанное выше решение проблемы #1, и единственное изменение, сделанное в первом операторе if, где я изменил это:

if(key != "_id")

Для этого используйте isNaN(x). функция:

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

Надеюсь, это поможет кому-то, и если есть проблема с этим решением, пожалуйста, дайте отзыв!

person Andreas Häggström    schedule 02.10.2020

как простая функция;

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))
    )
  })
})
person Ahmet Şimşek    schedule 01.07.2018