【问题标题】:Strange results with MongoDB group aggregationMongoDB 组聚合的奇怪结果
【发布时间】:2017-01-05 22:27:07
【问题描述】:

我有两个不同的文档,我想用一个匹配字段(在本例中为idOrdem)进行分组。两者都有一个timestamp 字段,只有一个字段具有timeElapsed 字段。我需要在聚合中返回所有这些信息,但我得到的结果似乎不正确。我得到了timeElapsed 字段的null 结果,其中肯定有一个包含该字段的文档。

我的陈述有什么问题?

db.Logging.aggregate( [
 { $match : { $or : [ { "action":"resetDslVerify"}, { "action":"assia/reset/RequestQueryOs" } ] } },
 { $group : {
    _id : "$idOrdem",
    timestamp1: { $first: '$timestamp' },
    timestamp2: { $last: '$timestamp' },
    timeElapsed: { $first: '$timeElapsed' }
    }
 },
 { $sort: { timestamp: -1}  } ]
);

意想不到的结果:

{ "_id" : "159251", "timestamp1" : 1483456382058, "timestamp2" : 1483456382058, "timeElapsed" : 1091 }
{ "_id" : "134601", "timestamp1" : 1482949316671, "timestamp2" : 1482949349410, "timeElapsed" : 821 }
{ "_id" : "168801", "timestamp1" : 1483560599899, "timestamp2" : 1483560564505, "timeElapsed" : null }
{ "_id" : "158901", "timestamp1" : 1483452698756, "timestamp2" : 1483452673424, "timeElapsed" : null }
{ "_id" : "135001", "timestamp1" : 1482949653229, "timestamp2" : 1482949711541, "timeElapsed" : 838 }

idOrdem 与我需要的所有信息相匹配的文档示例:

s-1:PRIMARY> db.Logging.find( { $or : [ { "action":"resetDslVerify"}, { "action":"assia/reset/RequestQueryOs" } ], "idOrdem":"135001" } );
{ "_id" : ObjectId("586404155b88db1209c3f998"), "success" : true, "action" : "assia/reset/RequestQueryOs", "timestamp" : 1482949653229, "httpCode" : 200, "timeElapsed" : 838, "idOrdem" : "135001", "creator" : "TecnicoVirtual" }
{ "_id" : ObjectId("5864044f5b88db1209c3f99b"), "success" : true, "action" : "resetDslVerify", "timestamp" : 1482949711541, "terminal" : "2134599099", "httpCode" : 200, "idOrdem" : "135001", "idOrdem" : "135001", "result" : "OK", "timestamp" : 1482949711541, "isResetDslSuccess" : true, "creator" : "TecnicoVirtual" }

【问题讨论】:

  • 您也可以展示您的预期结果吗?
  • “意外结果”中的数据与下方“匹配文件”中的数据不对应。例如,您能否提供正在生成的数据: { "_id" : "158901", "timestamp1" : 1483452698756, "timestamp2" : 1483452673424, "timeElapsed" : null }

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

$first$last 累加器分别从每个组的第一个/最后一个文档返回一个值。仅当文档按已定义的顺序时才定义顺序,但在您的情况下,您正在订购 他们AFTER 以任意顺序对它们进行分组,这使得累加器无用,因为它们只以未定义的顺序返回文档,因此得到奇怪的结果。

要调试管道,请累积运行它并添加每个步骤,并在每个步骤检查生成的文档。例如,您可以从 $match 管道开始,并验证该阶段的结果是否只是预期的:

db.Logging.aggregate([
    { 
        "$match": {
            "action": { 
                "$in": [
                    "resetDslVerify",
                    "assia/reset/RequestQueryOs"
                ]
            }
        }
    }
])

在此阶段观察生成的文档,这些文档应该是符合给定条件(在您的情况下缩短为使用 $in 运算符)和任意排序的文档。

添加下一个管道步骤:

db.Logging.aggregate([
    { 
        "$match": {
            "action": { 
                "$in": [
                    "resetDslVerify",
                    "assia/reset/RequestQueryOs"
                ]
            }
        }
    },
    {
        "$group": {
            "_id": "$idOrdem",
            "timestamp1": { "$first": "$timestamp" }
            "timestamp2": { "$last": "$timestamp" }
            "timeElapsed": { "$first": "$timeElapsed" }
        }
    }
])

现在事情变得有趣了,生成的管道中的文档是任意顺序的,因为它们以该顺序进入 $group 阶段。即使放置最后一个 $sort 管道步骤也不会消除庆祝中的苍蝇:它不会改变原始文档的顺序,只会改变 GROUP 的顺序。


解决方法是在 $match 步骤中过滤掉空值,将 $sort 运算符放在 $group 之前管道,以及添加另一个排序字段,这将是您的分组键和timeElapsed 属性:

db.Logging.aggregate([
    { 
        "$match": {
            "action": { 
                "$in": [
                    "resetDslVerify",
                    "assia/reset/RequestQueryOs"
                ]
            },
            "timestamp": { "$ne": null },
            "timeElapsed": { "$ne": null }
        }
    },
    { "$sort": { "idOrdem": 1, "timestamp": -1, "timeElapsed": -1 } }
    {
        "$group": {
            "_id": "$idOrdem",
            "timestamp1": { "$first": "$timestamp" }
            "timestamp2": { "$last": "$timestamp" }
            "timeElapsed": { "$first": "$timeElapsed" }
        }
    }
])

不使用 $sort 管道的更好方法是使用 $max$min 运算符:

db.Logging.aggregate([
    { 
        "$match": {
            "action": { 
                "$in": [
                    "resetDslVerify",
                    "assia/reset/RequestQueryOs"
                ]
            }
        }
    },
    {
        "$group": {
            "_id": "$idOrdem",
            "timestamp1": { "$max": "$timestamp" }
            "timestamp2": { "$min": "$timestamp" }
            "timeElapsed": { "$max": "$timeElapsed" }
        }
    }
])

【讨论】:

  • 第一种方法仍然给了我空值。第二个,最大/最小是正确的。谢谢。 :)
  • 不用担心 :) 我已经更新了答案,以便您可以过滤空值。
【解决方案2】:

$first 将采用第一个文档的值,即使此文档中不存在该字段。你可以这样解决这个问题:

db.Logging.aggregate([
   {
      $match:{
         $or:[
            {
               "action":"resetDslVerify"
            },
            {
               "action":"assia/reset/RequestQueryOs"
            }
         ]
      }
   },
   {
      $group:{
         _id:"$idOrdem",
         timestamp1:{
            $first:'$timestamp'
         },
         timestamp2:{
            $last:'$timestamp'
         },
         timeElapsed:{
            $push:"$timeElapsed"
         }
      }
   },
   {
      $project:{
         _id:1,
         timestamp1:1,
         timestamp2:1,
         timeElapsed:{
            $arrayElemAt:[
               "$timeElapsed",
               0
            ]
         }
      }
   },
   {
      $sort:{
         timestamp:-1
      }
   }
]);

输出:(对于您提供的数据)

{ "_id" : "135901", "timestamp1" : 1482950884849, "timestamp2" : 1482950907877, "timeElapsed" : 801 }

【讨论】:

  • 异常:无效的操作符'$arrayElemAt'
  • @ChristianDechery $arrayElemAt 是在 mongoDB 3.2 中引入的,我猜你使用的是旧版本
猜你喜欢
  • 2014-07-18
  • 1970-01-01
  • 2019-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-17
相关资源
最近更新 更多