【问题标题】:Mongo query to find where no matching element after a different matching elementMongo查询以查找在不同匹配元素之后没有匹配元素的位置
【发布时间】:2023-03-11 13:57:02
【问题描述】:

我正在尝试使用以下文档查询 MongoDB 集合(我无法控制),这些文档用作每个作业定义的作业日志:

{
    "definition": ...,
    "jobs": [ // sequential
        {
            "time": ...,
            "result": "success"
        },
        {
            "time": ... (after previous),
            "result": "failure"
        }
        {
            "time": ...,
            "result": "running"
        }
    ]
}

我想查找最近完成作业运行失败的每个作业定义,其中“失败”和“成功”是仅有的两种“已完成”结果类型,但可能有任何我不知道的其他结果的数量。

到目前为止,我已经想出这个来找到所有失败的工作:

db.collection.find({jobs: {$elemMatch: {result: "failure"}}})

如果纯 mongo 无法做到这一点,我当然可以获取失败列表并以编程方式搜索过滤器以匹配我的特定要求。

编辑:

我只能使用 mongo 2.6。我可以忽略任何非失败和非成功结果,但在成功和失败结果之前、之后和中间可能有任意数量的结果。

【问题讨论】:

  • 你为什么回滚我的编辑?您认为当前的标题很好地描述了您的问题吗?
  • @user3100115 我不认为我的标题描述得特别好,但你的标题有点误导,因为我不一定要寻找最后一个元素。请参阅编辑 cmets。
  • 如果您不是在寻找最后一个,那么最近完成的作业运行失败是什么意思
  • 我的意思是“不完整”的工作可能是最后一个元素。我想要最后一个“完整”的工作,但它可能不是数组的最后一个元素。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

这超出了基本的find() 查询。您需要使用聚合框架。

最有效的方法是在 MongoDB 3.2 或更高版本中,因为我们有 $slice$arrayElemAt 可以在 $project 阶段使用索引 @987654337 获取数组中的最后一个元素@,但在这里你应该使用$arrayElemAt,因为它返回元素,而$slice 返回一个元素数组。此外,您需要使用$let 运算符来访问使用dot notation 的子文档中的“结果”字段。

管道中的第一个阶段必须是 $match 阶段。这减少了通过线路发送的数据量以及管道中使用的时间和内存。

管道中的最后一个阶段也是$match 阶段,您只选择那些符合您的条件的文档。

db.collection.aggregate([
    { "$match": { "jobs.result": "failure" } }, 
    { "$project": { 
        "definition": 1,
        "result": { 
            "$let": { 
                "vars": { "job": { "$arrayElemAt": [ "$jobs", -1 ] } },
                "in": "$$job.result" 
            } 
        } 
    }}, 
    { "$match": { "result": "failure" } } 
])

从 MongoDB 3.0 向后,您需要一种效率较低的不同方法,因为这意味着您 $unwind 匹配后的“jobs”数组和 $group _id 您的文档,使用 $last 累加器运算符获取数组中的最后一个元素。当然小组赛的$first操作符是用来保留“定义”字段值的。

在对文档进行分组后,您需要使用$redact 管道阶段,当条件为true 或@ 时,使用逻辑条件将最后一个“作业”与您的条件匹配的所有文档返回到$$KEEP 文档987654352@false

最后一个阶段是$project,您可以在其中指定要包含在结果中的字段。这也减少了通过网络发送的数据量以及用于在客户端解码文档的时间和内存。

db.collection.aggregate([
    { "$match": { "jobs.result": "failure" } }, 
    { "$unwind": "$jobs" }, 
    { "$group": { 
        "_id": "$_id", 
        "job": { "$last": "$jobs" }, 
        "definition": { "$first": "$definition" } 
    }}, 
    { "$redact": { 
        "$cond": [ 
            { "$eq": [ "$job.result", "failure" ] }, 
            "$$KEEP", 
            "$$PRUNE" 
        ] 
    }}, 
    // optional stage
    { "$project": { 
        "definition": 1, 
        "result": "$job.result" 
    }} 
])

【讨论】:

  • 对不起,我应该提到我只能使用 mongo 2.6
  • @RobertHickman 第二个查询将在 2.6 上运行。你试过了吗?
  • 没有,但从那以后我得到了"errmsg" : "exception: Unrecognized pipeline stage name: '$redact'"
  • $redact 是 2.6 版中的新功能,因此如果您收到该错误消息,则表示您使用的是 2.4 或 2.2。
  • mongos> version() 2.6.4
【解决方案2】:

您可以为此使用聚合框架

  db.robert.aggregate([     
         {
            $project : {
                _id : 1,
                def : 1,
                jobs : { //filter only fields by OK/NOK
                    $filter : {
                        input : "$jobs",
                        as : "item",
                        cond : {
                            $or : [{
                                    $eq :
                                    [{
                                            $cmp : ["$$item.staus", "success"]
                                        }, 0]
                                }, {
                                    $eq : [{
                                            $cmp : ["$$item.staus", "failure"]
                                        }, 0]
                                }
                            ]

                        }

                    }
                }
            }
        },
        {
            $project : {
                _id : 1,
                def : 1,
                jobs : {
                    $slice : ["$jobs", 2]//take first two entries of array
                    // this could be also last two use -2
                }
            }
        }, {
            $match : {
                jobs : {
                    $size : 2 //eliminate nulls and only one entry in the array
                }
            }
        }, {
            $project : { // this is a kind of creating parameters for $match
                _id : 1,
                def : 1,
                firstShouldBeFalied : {
                    $slice : ["$jobs", 1]
                },
                secondShouldBeSuccess : {
                    $slice : ["$jobs", 1, 1]
                },
            }
        }, {
            $match : {

                $and : [{
                        "firstShouldBeFalied.staus" : "failure"
                    }, {
                        "secondShouldBeSuccess.staus" : "success"
                    },
                ]

            }
        },

    ])

欢迎任何 cmets!

【讨论】:

  • 直到今天我才听说聚合框架。听起来很有趣。你介意解释一下你写的查询吗?乍一看,它似乎仅限于在特定索引处获取数组中的元素。如果这是真的,它对我不起作用。就我而言,我可能会遇到失败、任意数量的未完成状态,然后是成功状态。所以我无法知道要检查哪些索引。
  • @RobertHickman 好的 - 这意味着我们可以从数组中消除其他状态并仅关注 OK/NOK?,将编辑答案以给您一个概述 - 但更多 here
  • 是的,我们可以完全忽略除了成功和失败状态之外的任何其他状态。我想我知道这将如何有助于更改您的查询并获得我需要的结果。
  • @RobertHickman - 请参阅使用查询编辑 $filter 输入字段然后处理它们
  • 也许这是因为我没有指定我需要符合 mongo 2.6 的代码,但是我收到了这个错误:"errmsg" : "exception: invalid operator '$filter'"
猜你喜欢
  • 1970-01-01
  • 2013-08-10
  • 2020-06-01
  • 2021-06-24
  • 1970-01-01
  • 1970-01-01
  • 2016-01-26
  • 2021-12-19
相关资源
最近更新 更多