MongoDB 聚合 - 使用 $cond 的 $project

6

我正在尝试使用MongoDB(v.3.2.11)聚合框架来处理一些类似以下格式的日志文档:

{ 
    "_id" : ObjectId("58b753c6d4421f00216de942"), 
    "session_id" : "7CB8725A-3994-45B8-9CA2-92FC19406288", 
    "event_type" : "connect_begin", 
    "timestamp" : "1488409541.674997", 
    "user_id" : "f6830aac-60be-44df-9fa7-7aa530d637ce", 
    "u_at" : ISODate("2017-03-01T23:05:42.077Z"), 
    "c_at" : ISODate("2017-03-01T23:05:42.077Z") 
}

我的收藏包含以上共享一个session_id的日志对,一个是begin事件的日志,另一个是end事件的日志。最终目标是通过时间戳之间的差异来计算这些会话的长度。
到目前为止,我已经能够编写一个聚合管道,按$session_id将日志分组,并提供与会话相关联的两个$events数组。我的想法是接下来我将使用$cond检查数组中每个$eventevent_type,这将告诉我它是begin还是end事件,并将开始和结束时间戳投影到最终结果上。我已经粘贴了我目前为止的内容如下:
db.time_spent_logs.aggregate([
    { $group: {
            _id: '$session_id',
            events: {
                $push: {
                    event_type: '$event_type', 
                    timestamp: '$timestamp'
            }
        }
    }}, 
    { $project: {
        start: {
            $cond: { 
                if: { $or: [ { $strcasecmp: [ "$events[0].event_type", "trending_begin" ]}, { $strcasecmp: [ "$events[0].event_type", "connect_begin" ]}] },
                then: '$events[0].timestamp', 
                else: '$events[1].timestamp'
            }
        },
        end: {
            $cond: {
                if: { $or: [ { $strcasecmp: [ "$events[0].event_type", "trending_end" ]}, { $strcasecmp: [ "$events[0].event_type", "connect_end" ]}] },
                then: '$events[0].timestamp', 
                else: '$events[1].timestamp'

            }
        }
    }}
])

这将生成以下列表:
{ "_id" : "4EC4B831-D3C7-49C6-9EC8-301981639ED7" }

我认为我的问题出现在我的$cond的if语句中,在这个if语句中,我正在比较每个$event的event_type字段的值与一个字符串,以查看它是否为我们的两种begin或end事件类型之一。我相信我的问题就出现在这里的$if $or $strcasecompare中,可能是某些地方写错了...
我已经尝试使用$literal来进行event_type的比较,但没有结果。
非常感谢任何帮助!

3
不能使用方括号[]来索引数组元素。话虽如此,您能告诉我们您正在运行的mongod版本吗?您的查询预期输出是什么? - styvane
@styvane - 版本3.2.11,我已更新问题。我没有意识到您不能使用括号表示法-感谢您提供的信息! - lloudermilk
@laurynloudermilk 你的唯一会话是否只与一个事件类型配对? - s7vr
1个回答

5

对于MongoDB v3.2及以上版本,您可以使用$filter代替手动应用条件,例如:

{
 $project: {
  start: {
   //Filter the events, keep only 'begin' events
   $filter: {
    input: '$events',
    as: 'event',
    cond: {$in: ['$$event.event_type', ['trending_begin', 'connect_begin']]}
   }
  },
  end: {
   //Same with 'end' events
   $filter: {
    input: '$events',
    as: 'event',
    cond: {$in: ['$$event.event_type', ['trending_end', 'connect_end']]}
   }
  }
 }
}

因此,结果中的“start”和“end”属性将分别是开始和结束事件的数组。如果您确信数据一致且恰好有两个事件(开始和结束)记录与会话匹配,则可以安全地使用$arrayElemAt来获取数组的第一个元素:

{
 $project: {
  start: {
   //Take first of the filtered events
   $arrayElemAt: [{
    $filter: {
     input: '$events',
     as: 'event',
     cond: {$in: ['$$event.event_type', ['trending_begin', 'connect_begin']]}
    }
   }, 0]
  },
  end: {
   //Take first of the filtered events
   $arrayElemAt: [{
    $filter: {
     input: '$events',
     as: 'event',
     cond: {$in: ['$$event.event_type', ['trending_end', 'connect_end']]}
    }
   }, 0]
  }
 }
}

您将拥有“start”和“end”作为普通对象。

这是整个查询


@laurynloudermilk 你好!嗯...问题可能是由于MongoDB版本引起的。$arrayElemAt是在v3.2中添加的。你使用的是哪个版本? - Antonio Narkevich
安东尼 - 我原以为我在运行 v3.2.11 版本,但事实证明那是我的 shell 版本,而不是我的数据库版本。我已经开了一个工单来升级我的 mongodb,并会在升级后在这里更新,如果您的解决方案可行的话。 - lloudermilk
太棒了!由于我们只能升级到3.2版本,所以我不得不将$setIsSubset替换为$in。因为$in是3.4版本才引入的新功能。一旦我修改了这个地方,你的管道就完美地运行起来了。非常感谢你! - lloudermilk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接