1、MongoDB的复杂查询
首先,需要统计的数据结构如下,可以看到每一篇文章/视频及用户组成了一个文档,然后具体的用户行为,比如阅读/观看、点赞、不喜欢等,存在了UserBehaviorItems这个数组里。现在,需要统计的数据是,每一天视频的播放次数。这里如果是在关系型数据库下,SQL写起来还是挺简单的:根据用户行为的记录时间分组,然后筛出视频的播放行为,进行count()操作即可。但是,因为我们需要分组的据存储在每一个文档的数组中,使用mongo shell处理起来,还是有一些繁琐的。下面就展开说一下,根据子文档中的数据进行分组查询的具体处理。
2、根据mongo中的子文档数据进行分组查询
首先,我们要将子文档中的数据"展开",拆分成多条数据,每条数据包含数组中的一个值,这里需要用到:$unwind。
例如:
{ "_id" : 1, "item" : "ABC", list: [ "1", "2", "3"] }
执行:db.test.aggregate( [ { $unwind : "$list" } ] )
得到:
{ "_id" : 1, "item" : "ABC", "list" : "1" }
{ "_id" : 1, "item" : "ABC", "list" : "2" }
{ "_id" : 1, "item" : "ABC", "list" : "3" }
复制代码
将数据都展开后,就可以进行分组操作了。这里需要特别说明下,mongo中的管道这个概念。管道表示:由一个或多个处理文档的阶段组成,这里的阶段就是指的$unwind这些操作。上一个管道的处理结果将会交给下一个管道进行处理。然后,在这里又遇到了一个很坑的问题,我们要根据用户行为的记录日期来分组,但是这里的日期是以时间戳的形式记录的。而且这个时间戳是APP端传过来的,ios端传的是10位时间戳,android端传的是13位,这就很蛋疼了。这里我是用了两个管道来处理的,先判断时间戳的位数,如果为10位那么就乘以1000,统一调整成13位后再转换成日期,然后进行分组排序操作。最后,实现命令如下:
db.getCollection('user_behavior').aggregate([
{
# 根据行为类型筛选出视频观看记录
$match: {
"behavior_object_type": {
$ne: 1
},
}
},
{
$unwind: "$UserBehaviorItems"
},
{
#把时间戳统一成13位
$project : {
resDate: {
$cond: { if: { $gte: [ {$strLenCP : {$toString: "$UserBehaviorItems.start_time"}}, 12 ] }, then: {$multiply:["$UserBehaviorItems.start_time", 1]}, else: {$multiply:["$UserBehaviorItems.start_time", 1000]} }
}
}
},
{
#将13位时间戳格式化位yyyy-MM-dd形式
$project: {
convertedDate: {
$dateToString: {
format: "%Y-%m-%d",
date: {
$toDate: "$resDate"
}
}
}
}
},
{
# 根据日期进行分组
$group: {
_id: "$convertedDate",
count: {
$sum: 1
}
}
},
{
# 倒序展示,这里的_id指的是$convertedDate,所以是根据日期进行倒序排序
$sort: {
"_id": - 1
}
}
]);
复制代码
执行结果: