首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Mongo组聚合:生成一个键字典,而不是一个带有_ids的数组

Mongo组聚合:生成一个键字典,而不是一个带有_ids的数组
EN

Stack Overflow用户
提问于 2016-11-15 11:01:46
回答 2查看 2.1K关注 0票数 1

假设我们从MongoDB的$group文档中收集了以下书籍:

代码语言:javascript
复制
{ "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }
{ "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }
{ "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }
{ "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }
{ "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 }

如果我们效仿他们,按作者分组,如下所示:

代码语言:javascript
复制
db.books.aggregate(
   [
     { $group : { _id : "$author", books: { $push: "$title" } } }
   ]
)

然后我们得到一个数组:

代码语言:javascript
复制
[
  { "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] },
  { "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
]

但是我更喜欢使用字典而不是数组

代码语言:javascript
复制
{
  "Homer": { "books" : [ "The Odyssey", "Iliad" ] },
  "Dante": { "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
}

换句话说,我想在字典中使用_id作为键。这将使接收方更容易访问,因为当他们想要查找特定的作者时,不需要在数组中搜索。

显然,当接收者得到数据时,他们可以重新安排数据。但是,有什么办法可以通过Mongo的聚合管道来实现呢?

(对于额外的分数,当_id具有多个属性时,输出嵌套字典,例如,每个发布者有一个键,然后在发布者下面为每个作者提供一个键。)

EN

回答 2

Stack Overflow用户

发布于 2016-11-15 11:56:21

如果您需要比聚合框架所允许的更多的灵活性,您可以尝试使用map- than。

代码语言:javascript
复制
map = function() {
  var books = {};
  books[this._id] = this.title;
  emit(this.author, books);
}

reduce = function(key, values) {
  var result = {};
  values.forEach(function(value) {
    for (var id in value) {
       result[id] = value[id];
    }
  });
  return result;
}
票数 1
EN

Stack Overflow用户

发布于 2016-11-16 02:38:18

我可能会在某个时候尝试一下地图-减少方法。

现在,我收到数据时正在用Javascript处理数据:

代码语言:javascript
复制
/**
 * Flattens an array of items with _ids into an object, using the _ids as keys.
 *
 * For example, given an array of the form:
 *
 *     [
 *       { _id: 'X', foo: 'bar' },
 *       { _id: 'Y', foo: 'baz' }
 *     ]
 *
 * Will produce an object ("dictionary") of the form:
 *
 *     {
 *       X: { foo: 'bar' },
 *       Y: { foo: 'baz' }
 *     }
 *
 * Note that the `_id` properties will be removed from the input array!
 */
function flattenBy_id (array) {
    const obj = {};
    array.forEach(item => {
        const id = item._id;

        if (typeof id !== 'string' && typeof id !== 'number' && typeof id !== 'boolean') {
            throw Error(`Cannot flatten: _id is non-primitive (${typeof id}) in item: ${JSON.stringify(item)}`);
        }

        delete item._id;
        obj[id] = item;
    });
    return obj;
}

可以在一行中使用LoDash产生类似的结果。

代码语言:javascript
复制
_.keyBy(array, '_id')

但是,这不会删除_id属性,对于我的目的来说,这是更干净的。

下面是当_id具有多个属性时创建嵌套对象的版本:

代码语言:javascript
复制
/**
 * Flattens an array of items with _ids into an object, using the _ids as keys.
 *
 * For example, given an array of the form:
 *
 *     [
 *       { _id: {publisher: 'P', author: 'Q', book: 'Alice in Wonderland'},   date: 1940, content: '...' },
 *       { _id: {publisher: 'X', author: 'Y', book: 'The Hobbit'},            date: 1950, content: '...' },
 *       { _id: {publisher: 'X', author: 'Y', book: 'The Lord of the Rings'}, date: 1960, content: '...' },
 *       { _id: {publisher: 'X', author: 'Z', book: 'Harry Potter'},          date: 1990, content: '...' },
 *     ]
 *
 * Will produce an object ("dictionary") of the form:
 *
 *     {
 *       P: {
 *         Q: {
 *           'Alice in Wonderland':   {date: 1940, content: '...'}
 *         }
 *       },
 *       X: {
 *         Y: {
 *           'The Hobbit':            {date: 1950, content: '...'},
 *           'The Lord of the Rings': {date: 1960, content: '...'}
 *         },
 *         Z: {
 *           'Harry Potter':          {date: 1990, content: '...'}
 *         }
 *       }
 *     }
 *
 * Note that the `_id` properties will be removed from the input array!
 */
function flattenBy_id (array) {
    const dictionary = {};
    array.forEach(item => {
        const path = item._id;

        const pathArray = typeof path === 'object' ? Object_values(path) : [path];

        let target = dictionary;

        pathArray.forEach((key, i) => {
            // Check that key is a primitive
            // Not throwing on 'undefined' because we sometimes have (very old) data with that key
            if (typeof key !== 'string' && typeof key !== 'number' && typeof key !== 'boolean') {
                throw Error(`Cannot flatten: _id is non-primitive (${typeof key}) in item: ${safeStringify(item)}`);
            }

            // Are we on the final step of the path, or before it?
            if (i < pathArray.length - 1) {
                // We are not at the end of the path.  Travel one step.
                if (target[key] === undefined) {
                    target[key] = {};
                }
                target = target[key];
            } else {
                // We are on the final step of the path

                // We don't want to overwrite data that already exists.  We should never be given input of that form.
                if (target[key] !== undefined) {
                    throw Error(`Cannot flatten: The key "${key}" already appears in ${safeStringify(target)} while trying to add: ${safeStringify(item._id)}`);
                }

                delete item._id;
                target[key] = item;
            }
        });

    });
    return dictionary;
}

它假定_id属性总是以相同的顺序排列。希望这是蒙戈的$group运营商的一贯行为。

如果_id不总是包含相同数量的属性,则它将无法正常工作,并可能引发错误。(例如,_id: {foo: 1, bar: 2}后面跟着_id: {foo: 1}会带来麻烦。如果某些文档未定义bar,则可能发生这种情况。)如果您有这种类型的数据,则需要一种不同的方法。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40608174

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档