从阵列匹配条件和最新日期

时间:2017-06-08 10:11:56

标签: mongodb aggregation-framework

For user1=1 and user2, message hiii0 should only come
For user1=1 and user3, message hiii2 should only come
For user1=1 and user4, message hiii1 should only come

上面的mongodb集合商店在user1和user2之间聊天。 我正在寻找一个查询,它将给我最新消息的结果 message.sender =每行1个。

即寻找3行作为输出

db.chat.find({"messages.sender":1})

即。只有三排。

db.chat.aggregate([ {$unwind:"$messages"}, {$match:{"messages.sender":1}}, {$sort:{"messages.datetime":-1} ]) 正在提供所有行,而我正在寻找具有最新日期时间的匹配行。 请帮忙

示例:

{ "_id" : ObjectId("593921425ccc8150f35e7663"), "user1" : 1, "user2" : 3, "messages" : { "sender" : 1, "datetime" : ISODate("2017-06-10T10:04:50Z"), "body" : "hiii 2" } }
{ "_id" : ObjectId("593921425ccc8150f35e7664"), "user1" : 1, "user2" : 4, "messages" : { "sender" : 1, "datetime" : ISODate("2017-06-09T10:04:50Z"), "body" : "hiii 1" } }
{ "_id" : ObjectId("593921425ccc8150f35e7662"), "user1" : 1, "user2" : 2, "messages" : { "sender" : 1, "datetime" : ISODate("2017-06-08T10:04:50Z"), "body" : "hiii 0" } }
{ "_id" : ObjectId("593921425ccc8150f35e7663"), "user1" : 1, "user2" : 3, "messages" : { "sender" : 1, "datetime" : ISODate("2017-06-08T10:04:50Z"), "body" : "hiii 0" } }
{ "_id" : ObjectId("593921425ccc8150f35e7664"), "user1" : 1, "user2" : 4, "messages" : { "sender" : 1, "datetime" : ISODate("2017-06-08T10:04:50Z"), "body" : "hiii 0" } }

正在输出

@Bean DateProvider dateProvider()

最后两行不可取,因为它不是user1-user2记录的最新记录。

如果我要添加{$ limit:1},它只给出一行。

1 个答案:

答案 0 :(得分:1)

这里的基本概念是您需要聚合框架才能应用条件来“过滤”数组元素到条件。根据可用的版本,可以应用不同的技术。

在所有情况下都是结果:

{
    "_id" : ObjectId("593921425ccc8150f35e7664"),
    "user1" : 1,
    "user2" : 4,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-09T10:04:50Z"),
            "body" : "hiii 1"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7663"),
    "user1" : 1,
    "user2" : 3,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-10T10:04:50Z"),
            "body" : "hiii 2"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7662"),
    "user1" : 1,
    "user2" : 2,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-08T10:04:50Z"),
            "body" : "hiii 0"
    }
}

MongoDB 3.4及以上

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$replaceRoot": {
    "newRoot": {
      "$let": {
        "vars": {
          "messages": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "maxDate": {
            "$max": {
              "$map": {
                "input": {
                  "$filter": {
                    "input": "$messages",
                    "as": "m",
                    "cond": { "$eq": [ "$$m.sender", 1 ] }
                  }
                },
                "as": "m",
                "in": "$$m.datetime"
              }
            }
          }
        },
        "in": {
          "_id": "$_id",
          "user1": "$user1",
          "user2": "$user2",
          "messages": {
            "$arrayElemAt": [
              { "$filter": {
                "input": "$$messages",
                "as": "m",
                "cond": { "$eq": [ "$$m.datetime", "$$maxDate" ] }
              }},
              0
            ]
          }    
        }
      }
    }
  }}
])

这是利用$replaceRoot的最有效方式,它允许我们使用$let声明要在“替换”结构中使用的变量。这里的主要优点是这只需要“两个”流水线阶段。

为了匹配数组内容,您使用$filter应用$eq逻辑运算来测试"sender"的值。在条件匹配的情况下,只返回匹配的数组条目。

使用相同的$filter以便仅考虑匹配的“发件人”条目,然后我们希望将$max应用于“已过滤”列表中"datetime"中的值。 $max] 5值是条件的“最新”日期。

我们想要这个值,以便稍后我们可以将“过滤”数组的返回结果与此“maxDate”进行比较。这是在$let "in"块内发生的事情,其中​​为过滤内容先前声明的两个“变量”和“maxDate”再次应用于$filter以便返回应该返回的内容是唯一符合“最新日期”条件的价值。

由于您只想要“一个”结果,我们使用$arrayElemAt来使用值而不是数组。

MongoDB 3.2

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$filter": {
        "input": "$messages",
        "as": "m",
        "cond": { "$eq": [ "$$m.sender", 1 ] }
      }
    },
    "maxDate": {
      "$max": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "as": "m",
          "in": "$$m.datetime"
        }
      }
    }         
  }},
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$arrayElemAt":[
       { "$filter": {
         "input": "$messages",
          "as": "m",
          "cond": { "$eq": [ "$$m.datetime", "$maxDate" ] }
       }},
       0
      ]
    }
  }}
])

这基本上与描述的过程相同,但没有$replaceRoot管道阶段,我们需要在两个$project阶段应用。这样做的原因是我们需要“maxDate”中的“计算值”来执行最终$filter,并且它不能在复合语句中执行,因此我们拆分管道。这对整体运营成本影响很小。

在MongoDB 2.6到3.0中,我们可以使用除$arrayElemAt之外的大多数技术,并使用单个条目接受“数组”结果或放入$unwind阶段来处理应该现在只需一个条目。

MongoDB早期版本

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$unwind": "$messages" },
  { "$match": { "messages.sender": 1 } },
  { "$sort": { "_id": 1, "messages.datetime": -1 } },
  { "$group": {
    "_id": "$_id",
    "user1": { "$first": "$user1" },
    "user2": { "$first": "$user2" },
    "messages": { "$first": "$messages" }
  }}
])

虽然看起来很简短,但这是迄今为止成本最高的操作。在这里,您必须使用$unwind才能将条件应用于数组元素。这是一个非常昂贵的过程,因为它为每个数组条目生成每个文档的副本,并且基本上被在“过滤”情况下避免这种情况的现代运算符所取代。

此处的第二个$match阶段会丢弃与“发件人”条件不匹配的任何元素(现在为“文档”)。然后我们应用$sort以便_id将每个文档的“最新”日期置于顶部,因此两个“排序”键。

最后,我们应用$group来引用原始文档,使用$first作为累加器来获取“在顶部”的元素。