【问题标题】:For each document retrieve object with $max field from array对于每个文档,使用数组中的 $max 字段检索对象
【发布时间】:2018-10-12 00:49:41
【问题描述】:

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

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

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

【问题讨论】:

    标签: mongodb aggregation-framework grouping


    【解决方案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 将产生的。当然还要注意,如果实际上有“多个”条目具有相同的时间戳值,那么这当然会返回所有条目,而不仅仅是匹配的“第一个索引”。

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


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

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

    因此请注意在您真正需要的地方以及“分组”是您的实际意图的地方使用像$group$unwind 这样的阶段。如果您只是想在“文档中”查找某些内容,那么有更有效的方法可以做到这一点,而无需这些阶段为处理带来的所有额外开销。

    【讨论】:

      猜你喜欢
      • 2019-02-07
      • 1970-01-01
      • 1970-01-01
      • 2021-07-18
      • 1970-01-01
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      • 2019-09-26
      相关资源
      最近更新 更多