对于每个文档,从数组中使用$ max字段检索对象

时间:2018-05-02 00:41:24

标签: mongodb aggregation-framework grouping

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

{
'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'}
    ]
}

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

{
'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'}
}

我很确定它需要某种$ group聚合但无法弄清楚如何通过$max:<field>选择整个对象。例如,以下查询仅返回最大时间戳本身,没有任何附带字段。

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

返回

{ "_id" : "new york", "timestamp" : 1524560400 }
{ "_id" : "san franciscoeo", "timestamp" : 1524560400 }
{ "_id" : "miami", "timestamp" : 1524560400 }

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

1 个答案:

答案 0 :(得分:1)

作为您找到的答案的作者,我认为现在的MongoDB版本实际上可以做得更好。

每个文档的单个匹配

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

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

哪会回复你:

{
        "_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"
        }
}

当然,这实际上并不需要“分组”任何内容,只需在每个文档中找到$max值,就像您似乎要尝试的那样。这可以避免您需要通过强制$group$unwind来“破坏”任何其他文档输出。

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

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

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

每个文档“多个”匹配

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

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

哪个会输出:

{
        "_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"
                }
        ]
}

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

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

这些基本上是您的“现代”方法,可避免$unwind的开销,实际上$sort$group可能不需要它们。当然,只需处理单个文档就不需要它们。

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

因此,请注意使用$group$unwind 等阶段,而实际需要的地方和“分组”是您的实际意图。如果您只是想在文档中找到某些内容,那么可以采用更有效的方法来实现这一目标,而不会产生这些阶段带来的所有额外开销。