对于每个文档,从数组中检索带有 $max 字段的对象

Bla*_*lam 1 grouping mongodb aggregation-framework

我的收藏中有以下文件。每个文档都包含有关特定位置的历史天气数据:

{
'location':'new york', 
'history':[
    {'timestamp':1524542400, 'temp':79, 'wind_speed':1, 'wind_direction':'SW'}
    {'timestamp':1524548400, 'temp':80, 'wind_speed':2, 'wind_direction':'SW'}
    {'timestamp':1524554400, 'temp':82, 'wind_speed':3, 'wind_direction':'S'}
    {'timestamp':1524560400, 'temp':78, 'wind_speed':4, 'wind_direction':'S'}
    ]
},
{
'location':'san francisco', 
'history':[
    {'timestamp':1524542400, 'temp':80, 'wind_speed':5, 'wind_direction':'SW'}
    {'timestamp':1524548400, 'temp':81, 'wind_speed':6, 'wind_direction':'SW'}
    {'timestamp':1524554400, 'temp':82, 'wind_speed':7, 'wind_direction':'S'}
    {'timestamp':1524560400, 'temp':73, 'wind_speed':8, 'wind_direction':'S'}
    ]
},
{
'location':'miami', 
'history':[
    {'timestamp':1524542400, 'temp':84, 'wind_speed':9, 'wind_direction':'SW'}
    {'timestamp':1524548400, 'temp':85, 'wind_speed':10, 'wind_direction':'SW'}
    {'timestamp':1524554400, 'temp':86, 'wind_speed':11, 'wind_direction':'S'}
    {'timestamp':1524560400, 'temp':87, 'wind_speed':12, 'wind_direction':'S'}
    ]
}
Run Code Online (Sandbox Code Playgroud)

我想获取每个位置(或多或少)的最新天气数据列表,如下所示:

{
'location':'new york', 
'history':{'timestamp':1524560400, 'temp':78, 'wind_speed':4, 'wind_direction':'S'}
},
{
'location':'san francisco', 
'history':{'timestamp':1524560400, 'temp':73, 'wind_speed':8, 'wind_direction':'S'}
},
{
'location':'miami', 
'history':{'timestamp':1524560400, 'temp':87, 'wind_speed':12, 'wind_direction':'S'}
}
Run Code Online (Sandbox Code Playgroud)

我很确定它需要某种 $group 聚合,但无法弄清楚如何通过$max:<field>. 例如,下面的查询只返回最大时间戳本身,没有任何伴随的字段。

db.collection.aggregate([{
    '$unwind': '$history'
}, {
    '$group': {
        '_id': '$name',
        'timestamp': {
            '$max': '$history.timestamp'
        }
    }
}])
Run Code Online (Sandbox Code Playgroud)

返回

{ "_id" : "new york", "timestamp" : 1524560400 }
{ "_id" : "san franciscoeo", "timestamp" : 1524560400 }
{ "_id" : "miami", "timestamp" : 1524560400 }
Run Code Online (Sandbox Code Playgroud)

实际的集合和数组非常大,因此客户端处理不会很理想。任何帮助将非常感激。

Nei*_*unn 6

正如您找到的答案的作者一样,我认为我们实际上可以使用现代 MongoDB 版本做得更好。

每个文档单个匹配

简而言之,我们实际上可以应用于$max您的特定情况,与$indexOfArray$arrayElemAt一起使用来提取匹配的值:

db.collection.aggregate([
  { "$addFields": {
    "history": {
      "$arrayElemAt": [
        "$history",
        { "$indexOfArray": [ "$history.timestamp", { "$max": "$history.timestamp" } ] }
      ]
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

这将返回您:

{
        "_id" : ObjectId("5ae9175564de8a00a66b3974"),
        "location" : "new york",
        "history" : {
                "timestamp" : 1524560400,
                "temp" : 78,
                "wind_speed" : 4,
                "wind_direction" : "S"
        }
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3975"),
        "location" : "san francisco",
        "history" : {
                "timestamp" : 1524560400,
                "temp" : 73,
                "wind_speed" : 8,
                "wind_direction" : "S"
        }
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3976"),
        "location" : "miami",
        "history" : {
                "timestamp" : 1524560400,
                "temp" : 87,
                "wind_speed" : 12,
                "wind_direction" : "S"
        }
}
Run Code Online (Sandbox Code Playgroud)

这当然实际上不需要“分组”任何东西,只需$max从每个文档中找到值,就像您试图做的那样。这避免了您需要通过强制通过 a$group或实际上是$unwind.

用法本质上是$max从指定的数组属性返回“最大值”值,因为这$history.timestamp是从数组对象中提取“仅那些值”的一种简短方式。

这用于与相同的“值列表”进行比较以确定匹配的“索引” via $indexOfArray,它将数组作为第一个参数,将要匹配的值作为第二个参数。

$arrayElemAt运算符还接受一个数组作为它的第一个参数,这里我们使用完整"$history"数组,因为我们要提取“完整对象”。我们通过$indexOfArray运算符的“返回索引”值来完成。

每个文档“多个”匹配

当然,这对于“单个”匹配很好,但是如果您想将其扩展为具有相同$max值的“多个”匹配,那么您可以$filter改用:

db.collection.aggregate([
  { "$addFields": {
    "history": {
      "$filter": {
        "input": "$history",
        "cond": { "$eq": [ "$$this.timestamp", { "$max": "$history.timestamp" } ] }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

这将输出:

{
        "_id" : ObjectId("5ae9175564de8a00a66b3974"),
        "location" : "new york",
        "history" : [
                {
                        "timestamp" : 1524560400,
                        "temp" : 78,
                        "wind_speed" : 4,
                        "wind_direction" : "S"
                }
        ]
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3975"),
        "location" : "san francisco",
        "history" : [
                {
                        "timestamp" : 1524560400,
                        "temp" : 73,
                        "wind_speed" : 8,
                        "wind_direction" : "S"
                }
        ]
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3976"),
        "location" : "miami",
        "history" : [
                {
                        "timestamp" : 1524560400,
                        "temp" : 87,
                        "wind_speed" : 12,
                        "wind_direction" : "S"
                }
        ]
}
Run Code Online (Sandbox Code Playgroud)

主要的区别当然是该"history"属性仍然是一个“数组”,因为那是$filter会产生的。当然还要注意,如果实际上有“多个”条目具有相同的时间戳值,那么这当然会返回所有条目,而不仅仅是匹配的“第一个索引”。

比较基本上是针对“每个”数组元素进行的,以查看“当前”( "$$this") 对象是否具有与$max结果匹配的指定属性,并最终仅返回与提供的条件匹配的那些数组元素。


这些本质上是您的“现代”方法,它们避免了 的开销$unwind,实际上$sort以及$group可能不需要它们的地方。当然,它们不需要仅用于处理单个文档。

但是,如果您确实需要$group通过特定的分组键并考虑数组“内部”的值来跨越“多个文档”,那么您发现的初始方法实际上适合该场景,因为最终您“必须”$unwind处理以这种方式在数组“内部”使用项目。并且还考虑了“跨文档”。

所以要留意使用阶段像$group只有在这里你真正需要并在“分组”是您的实际意图。如果您只是想在“文档中”找到一些东西,那么有更有效的方法可以做到这一点,而无需这些阶段为处理带来的所有额外开销。$unwind