当前位置:  开发笔记 > 编程语言 > 正文

使用mongo计算所有文档中的数组出现次数

如何解决《使用mongo计算所有文档中的数组出现次数》经验,为你挑选了1个好方法。

我试图在一组文件上提取数据,看起来像:

[
  {
    name: 'john',
    sex: 'male',
    hobbies: ['football', 'tennis', 'swimming']
  },
  {
    name: 'betty'
    sex: 'female',
    hobbies: ['football', 'tennis']
  },
  {
    name: 'frank'
    sex: 'male',
    hobbies: ['football', 'tennis']
  } 
]

我正在尝试使用聚合框架来呈现按性别划分的数据,计算最常见的爱好.结果看起来应该是这样的.

{ _id: 'male', 
  total: 2, 
  hobbies: {
    football: 2,
    tennis: 2,
    swimming: 1
  } 
},
{ _id: 'female', 
  total: 1, 
    hobbies: {
      football: 1,
      tennis: 1
    } 
}

到目前为止,我可以得到每个性别的总数,但我不知道我怎么可能使用放松来获得爱好阵列的总数.

我的代码到目前为止:

collection.aggregate([
        { 
            $group: { 
                _id: '$sex', 
                total: { $sum: 1 }
            }
        }
    ])

Blakes Seven.. 7

就个人而言,我不是将"数据"转换为结果中键的名称的忠实粉丝.聚合框架原则倾向于聚合,因为这种操作也不受支持.

因此,个人偏好是将"数据"维护为"数据",并接受处理后的输出实际上更好,更符合一致的对象设计:

db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
])

产生如下结果:

[
    {
            "_id" : "female",
            "total" : 1,
            "hobbies" : [
                {
                    "name" : "tennis",
                    "count" : 1
                },
                {
                    "name" : "football",
                    "count" : 1
                }
            ]
    },
    {
        "_id" : "male",
        "total" : 2,
        "hobbies" : [
            {
                "name" : "swimming",
                "count" : 1
            },
            {
                "name" : "tennis",
                "count" : 2
            },
            {
                "name" : "football",
                "count" : 2
            }
        ]
    }
]

因此,最初$group的每个"性别"计数,并将业余爱好堆积成阵列数组.然后将你去标准化$unwind两次以获得单数项目,$group以获得每个性别下的每个爱好的总数,并最终重新组合每个性别的阵列.

它是相同的数据,它具有易于处理的一致且有机的结构,MongoDB和聚合框架在生成此输出时非常满意.

如果你真的必须将数据转换为按键的名称(我还是建议你不要因为它是不是一个好的模式在设计上遵循),然后做这样的穿越 - 从最终状态是客户端代码处理相当琐碎.作为适合shell的基本JavaScript示例:

var out = db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
]).toArray();

out.forEach(function(doc) {
    var obj = {};
    doc.hobbies.sort(function(a,b) { return a.count < b.count });
    doc.hobbies.forEach(function(hobby) {
        obj[hobby.name] = hobby.count;
    });
    doc.hobbies = obj;
    printjson(doc);
});

然后你基本上将每个游标结果处理成所需的输出形式,这实际上不是服务器上真正需要的聚合函数:

{
    "_id" : "female",
    "total" : 1,
    "hobbies" : {
        "tennis" : 1,
        "football" : 1
    }
}
{
    "_id" : "male",
    "total" : 2,
    "hobbies" : {
        "tennis" : 2,
        "football" : 2,
        "swimming" : 1
    }
}

在那里,将这种操作实现到游标结果的流处理中以便根据需要进行转换也应该是相当简单的,因为它基本上是相同的逻辑.

另一方面,您始终可以使用mapReduce在服务器上实现所有操作:

db.people.mapReduce(
    function() {
        emit(
            this.sex,
            { 
                "total": 1,
                "hobbies": this.hobbies.map(function(key) {
                    return { "name": key, "count": 1 };
                })
            }
        );
    },
    function(key,values) {
        var obj  = {},
            reduced = {
                "total": 0,
                "hobbies": []
            };

        values.forEach(function(value) {
            reduced.total += value.total;
            value.hobbies.forEach(function(hobby) {
                if ( !obj.hasOwnProperty(hobby.name) )
                    obj[hobby.name] = 0;
                obj[hobby.name] += hobby.count;
            });
        });

        reduced.hobbies = Object.keys(obj).map(function(key) {
            return { "name": key, "count": obj[key] };
        }).sort(function(a,b) {
            return a.count < b.count;
        });

        return reduced;
    },
    { 
        "out": { "inline": 1 },
        "finalize": function(key,value) {
            var obj = {};
            value.hobbies.forEach(function(hobby) {
                obj[hobby.name] = hobby.count;
            });
            value.hobbies = obj;
            return value;
        }
    }
)

mapReduce具有自己独特的输出风格,但在累积和操作中使用相同的原则,如果不太可能像聚合框架那样有效:

   "results" : [
        {
            "_id" : "female",
            "value" : {
                "total" : 1,
                "hobbies" : {
                    "football" : 1,
                    "tennis" : 1
                }
            }
        },
        {
            "_id" : "male",
            "value" : {
                "total" : 2,
                "hobbies" : {
                    "football" : 2,
                    "tennis" : 2,
                    "swimming" : 1
                }
            }
        }
    ]

在一天结束时,我仍然说第一种处理形式是最有效的,并且提供了数据输出的最自然和一致的工作,甚至没有尝试将数据点转换为键的名称.最好考虑遵循这种模式,但如果你真的必须这样,那么有很多方法可以在各种处理方法中将结果处理成所需的形式.



1> Blakes Seven..:

就个人而言,我不是将"数据"转换为结果中键的名称的忠实粉丝.聚合框架原则倾向于聚合,因为这种操作也不受支持.

因此,个人偏好是将"数据"维护为"数据",并接受处理后的输出实际上更好,更符合一致的对象设计:

db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
])

产生如下结果:

[
    {
            "_id" : "female",
            "total" : 1,
            "hobbies" : [
                {
                    "name" : "tennis",
                    "count" : 1
                },
                {
                    "name" : "football",
                    "count" : 1
                }
            ]
    },
    {
        "_id" : "male",
        "total" : 2,
        "hobbies" : [
            {
                "name" : "swimming",
                "count" : 1
            },
            {
                "name" : "tennis",
                "count" : 2
            },
            {
                "name" : "football",
                "count" : 2
            }
        ]
    }
]

因此,最初$group的每个"性别"计数,并将业余爱好堆积成阵列数组.然后将你去标准化$unwind两次以获得单数项目,$group以获得每个性别下的每个爱好的总数,并最终重新组合每个性别的阵列.

它是相同的数据,它具有易于处理的一致且有机的结构,MongoDB和聚合框架在生成此输出时非常满意.

如果你真的必须将数据转换为按键的名称(我还是建议你不要因为它是不是一个好的模式在设计上遵循),然后做这样的穿越 - 从最终状态是客户端代码处理相当琐碎.作为适合shell的基本JavaScript示例:

var out = db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
]).toArray();

out.forEach(function(doc) {
    var obj = {};
    doc.hobbies.sort(function(a,b) { return a.count < b.count });
    doc.hobbies.forEach(function(hobby) {
        obj[hobby.name] = hobby.count;
    });
    doc.hobbies = obj;
    printjson(doc);
});

然后你基本上将每个游标结果处理成所需的输出形式,这实际上不是服务器上真正需要的聚合函数:

{
    "_id" : "female",
    "total" : 1,
    "hobbies" : {
        "tennis" : 1,
        "football" : 1
    }
}
{
    "_id" : "male",
    "total" : 2,
    "hobbies" : {
        "tennis" : 2,
        "football" : 2,
        "swimming" : 1
    }
}

在那里,将这种操作实现到游标结果的流处理中以便根据需要进行转换也应该是相当简单的,因为它基本上是相同的逻辑.

另一方面,您始终可以使用mapReduce在服务器上实现所有操作:

db.people.mapReduce(
    function() {
        emit(
            this.sex,
            { 
                "total": 1,
                "hobbies": this.hobbies.map(function(key) {
                    return { "name": key, "count": 1 };
                })
            }
        );
    },
    function(key,values) {
        var obj  = {},
            reduced = {
                "total": 0,
                "hobbies": []
            };

        values.forEach(function(value) {
            reduced.total += value.total;
            value.hobbies.forEach(function(hobby) {
                if ( !obj.hasOwnProperty(hobby.name) )
                    obj[hobby.name] = 0;
                obj[hobby.name] += hobby.count;
            });
        });

        reduced.hobbies = Object.keys(obj).map(function(key) {
            return { "name": key, "count": obj[key] };
        }).sort(function(a,b) {
            return a.count < b.count;
        });

        return reduced;
    },
    { 
        "out": { "inline": 1 },
        "finalize": function(key,value) {
            var obj = {};
            value.hobbies.forEach(function(hobby) {
                obj[hobby.name] = hobby.count;
            });
            value.hobbies = obj;
            return value;
        }
    }
)

mapReduce具有自己独特的输出风格,但在累积和操作中使用相同的原则,如果不太可能像聚合框架那样有效:

   "results" : [
        {
            "_id" : "female",
            "value" : {
                "total" : 1,
                "hobbies" : {
                    "football" : 1,
                    "tennis" : 1
                }
            }
        },
        {
            "_id" : "male",
            "value" : {
                "total" : 2,
                "hobbies" : {
                    "football" : 2,
                    "tennis" : 2,
                    "swimming" : 1
                }
            }
        }
    ]

在一天结束时,我仍然说第一种处理形式是最有效的,并且提供了数据输出的最自然和一致的工作,甚至没有尝试将数据点转换为键的名称.最好考虑遵循这种模式,但如果你真的必须这样,那么有很多方法可以在各种处理方法中将结果处理成所需的形式.

推荐阅读
kikokikolove
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有