在mongodb聚合中将多个对象合并为一个对象

Posted

技术标签:

【中文标题】在mongodb聚合中将多个对象合并为一个对象【英文标题】:Merging multiple objects into a single object in the mongodb aggregation 【发布时间】:2021-12-18 05:05:51 【问题描述】:

这就是我所处的场景。我有以下一种由聚合阶段返回的数组。为简单起见,删除了所有不必要的道具。该数组已经按日期排序,这意味着 clocked_in_at 属性将按照每个对象的创建时间排序。

[
        
            "_id": "618192d4654484639c47fa2d",
            "clocked_out_at": "2021-11-05T10:00:00.000Z",
            "clocked_in_at": "2021-11-05T03:00:00.000Z",
            "visitor_id": "6166c10965959d147c69aa90" // this is here as a string
        ,
        
            "_id": "6182552fde30e84900ba33fd",
            "clocked_out_at": "2021-11-05T11:00:00.000Z",
            "clocked_in_at": "2021-11-05T04:00:00.000Z",
            "visitor_id": "6182e4cea8b52121d01dff1b"
        ,
        
            "_id": "6182552fde30e84900ba33fd",
            "clocked_out_at": "2021-11-05T12:00:00.000Z",
            "clocked_in_at": "2021-11-05T05:00:00.000Z",
            "visitor_id": "6166c10965959d147c69aa90"
        ,
        
            "_id": "6182552fde30e84900ba33fd",
            "clocked_out_at": "2021-11-06T13:00:00.000Z",
            "clocked_in_at": "2021-11-06T06:00:00.000Z",
            "visitor_id": "6166c10965959d147c69aa90"
        
]

因此您可以看到第一个、第三个和最后一个对象来自同一个访问者,而第二个对象来自另一个访问者。所以我基本上需要的是根据visitor_id和日期/时间合并数组中的所有对象,并在同一天从数组中的最后一个现有值设置clocked_out_at值,如果这有意义的话。基本上,我们需要根据 clocked_in_at 值将它们分别分组。如果同一访问者处于不同的 clocked_in_at 日期,则它们仍应位于两个对象中。

所以预期的输出是这样的:

[
        
            "_id": "618192d4654484639c47fa2d",
            "clocked_out_at": "2021-11-05T12:00:00.000Z",
            "clocked_in_at": "2021-11-05T03:00:00.000Z",
            "visitor_id": "6166c10965959d147c69aa90"
        ,
        
            "_id": "6182552fde30e84900ba33fd",
            "clocked_out_at": "2021-11-05T11:00:00.000Z",
            "clocked_in_at": "2021-11-05T04:00:00.000Z",
            "visitor_id": "6182e4cea8b52121d01dff1b"
        ,
        
            "_id": "6182552fde30e84900ba33fd",
            "clocked_out_at": "2021-11-06T13:00:00.000Z",
            "clocked_in_at": "2021-11-06T06:00:00.000Z",
            "visitor_id": "6166c10965959d147c69aa90"
        ,
]

所以,在这里您可以看到原始数组中的第一个和第三个对象被合并了。因为他们对clocked_in_at 和visitor_id 有相同的日期(不考虑时间)。即使最后一个对象来自同一个visitor_id,它也没有合并,因为它是在11月6日,第二个对象很明显,它没有被合并,因为它有一个完全不同的visitor_id。

请注意,原始数组中第三个对象的clocked_out_at值被合并到结果合并对象中,即结果数组中第一个对象的clocked_out_at。

我不太确定这样做的可能性,但我很想知道我们是否有任何解决方案。我希望像$mergeObjects 或$group 这样的东西。我尝试了他们,但没有运气。

感谢您的宝贵时间,谢谢!

【问题讨论】:

【参考方案1】:

如果clocked_in_atclocked_out_at 是字符串,我们可以使用$toDate 首先将它们转换为日期(这将使订购更容易)。如果它们已经是日期,我们可以跳过这一步。

然后我们可以$project 将每个打卡和打卡时间放入包含日期和值的对象数组中。 $dateTrunc 用于将clocked_in_atclocked_out_at 转换为天,然后$unwind 新创建的datetime 字段。现在我们可以每天$group ("$datetime.day") and visitor_id`保持$min 及时和$max 每天出时间。我们可以再次$project 清理对象结构:

db.collection.aggregate([
  // (Assuming strings not dates) Convert to DateTime
  
    "$addFields": 
      "clocked_in_at": 
        "$toDate": "$clocked_in_at"
      ,
      "clocked_out_at": 
        "$toDate": "$clocked_out_at"
      
    
  ,
  
    "$project": 
      "s_id": "$s_id",
      "visitor_id": "$visitor_id",
      "datetime": [
        
          "day": 
            "$dateTrunc": 
              "date": "$clocked_in_at",
              "unit": "day"
            
          ,
          "in": "$clocked_in_at"
        ,
        
          "day": 
            "$dateTrunc": 
              "date": "$clocked_out_at",
              "unit": "day"
            
          ,
          "out": "$clocked_out_at"
        
      ]
    
  ,
  
    "$unwind": "$datetime"
  ,
  
    "$group": 
      "_id": 
        "visitor_id": "$visitor_id",
        "day": "$datetime.day"
      ,
      "s_id": 
        "$first": "$s_id"
      ,
      "clocked_in_at": 
        "$min": "$datetime.in"
      ,
      "clocked_out_at": 
        "$max": "$datetime.out"
      
    
  ,
  
    "$project": 
      "_id": "$s_id",
      "clocked_out_at": "$clocked_out_at",
      "clocked_in_at": "$clocked_in_at",
      "visitor_id": "$_id.visitor_id"
    
  
])
[
  
    "_id": "6182552fde30e84900ba33fd",
    "clocked_in_at": ISODate("2021-11-06T06:00:00Z"),
    "clocked_out_at": ISODate("2021-11-06T13:00:00Z"),
    "visitor_id": "6166c10965959d147c69aa90"
  ,
  
    "_id": "618192d4654484639c47fa2d",
    "clocked_in_at": ISODate("2021-11-05T03:00:00Z"),
    "clocked_out_at": ISODate("2021-11-05T12:00:00Z"),
    "visitor_id": "6166c10965959d147c69aa90"
  ,
  
    "_id": "6182552fde30e84900ba33fd",
    "clocked_in_at": ISODate("2021-11-05T04:00:00Z"),
    "clocked_out_at": ISODate("2021-11-05T11:00:00Z"),
    "visitor_id": "6182e4cea8b52121d01dff1b"
  
]

mongoplayground

注意_id 在提供的示例中不是唯一的,因此该字段被修改为s_id

[
  
    "s_id": "618192d4654484639c47fa2d",
    "clocked_out_at": "2021-11-05T10:00:00.000Z",
    "clocked_in_at": "2021-11-05T03:00:00.000Z",
    "visitor_id": "6166c10965959d147c69aa90"
  ,
  
    "s_id": "6182552fde30e84900ba33fd",
    "clocked_out_at": "2021-11-05T11:00:00.000Z",
    "clocked_in_at": "2021-11-05T04:00:00.000Z",
    "visitor_id": "6182e4cea8b52121d01dff1b"
  ,
  
    "s_id": "6182552fde30e84900ba33fd",
    "clocked_out_at": "2021-11-05T12:00:00.000Z",
    "clocked_in_at": "2021-11-05T05:00:00.000Z",
    "visitor_id": "6166c10965959d147c69aa90"
  ,
  
    "s_id": "6182552fde30e84900ba33fd",
    "clocked_out_at": "2021-11-06T13:00:00.000Z",
    "clocked_in_at": "2021-11-06T06:00:00.000Z",
    "visitor_id": "6166c10965959d147c69aa90"
  
]

这意味着初始的$project 需要更新为:

  
    "$project": 
      "s_id": "$_id", // <- grab `_id` instead of `s_id`

【讨论】:

【参考方案2】:

使用$group

db.collection.aggregate([
  
    "$group": 
      "_id": 
        "clocked_in_at": 
          $dateTrunc: 
            date: "$toDate": "$clocked_in_at" ,
            unit: "day"
          
        ,
        "visitor_id": "$visitor_id"
      ,
      "max":  "$max": "$clocked_out_at" ,
      "min":  "$min": "$clocked_in_at",
      "id":  "$first": "$id" 
    
  ,
  
    "$project": 
      _id: "$id",
      "visitor_id": "$_id.visitor_id",
      "clocked_out_at": "$max",
      "clocked_in_at": "$min"
    
  
])

mongoplayground

【讨论】:

以上是关于在mongodb聚合中将多个对象合并为一个对象的主要内容,如果未能解决你的问题,请参考以下文章

Mongodb聚合:从键值对象返回不同值的计数

如何在 mongodb 的数组中合并具有相同键的对象?

如何在MongoDB中将数组转换为对象

在mongodb聚合管道中将毫秒转换为日期以进行分组?

使用聚合和查找 mongodb 从对象数组中获取最小值

使用聚合和查找 mongodb 从对象数组中获取最小值