【问题标题】:Match Documents where all array members do not contain a value匹配所有数组成员都不包含值的文档
【发布时间】:2016-07-17 20:27:27
【问题描述】:

MongoDB 选择器很快就会变得复杂,尤其是当您使用 JOIN 和其他花哨的关键字来自 mySQL 时。我尽力使这个问题的标题尽可能清晰,但惨遭失败。

例如,让 MongoDB 集合的文档具有以下架构:

{
    _id : int
    products : [
        {
            qte : int
            status : string
        },            
        {
            qte : int
            status : string
        },
        {
            qte : int
            status : string
        },
        ...
    ]
}

我正在尝试运行db.collection.find({ }) 查询返回文档,其中所有 产品 将字符串“完成”作为状态。请注意products 数组的长度是可变的。

我们也可以说我们希望所有文档至少包含一种状态不是“完成”的产品。

如果我将它作为 Javascript 循环 运行,我们会得到如下内容:

// Will contain queried documents
var matches = new Array();

// The documents variable contains all documents of the collection
for (var i = 0, len = documents.length; i < len; i++) {
    var match = false;

    if (documents[i].products && documents[i].products.length !== 0) {
        for (var j = 0; j < documents[i].products; j++) {
            if (documents[i].products[j].status !== "finished") {
                match = true;
                break;
            }
        }
    } 

    if (match) {
        matches.push(documents[i]);
    }
}

// The previous snippet was coded directly in the Stack Overflow textarea; I might have done nasty typos.

matches 数组将包含我要查找的文档。现在,我希望有一种类似于collection.find({"products.$.status" : {"$ne":"finished"}}) 的方法,但是当我这样做时,MongoDB 讨厌我的脸。

此外,没有任何产品的文档需要被忽略,但我已经用$and 子句解决了这个问题。请注意,我需要返回 ENTIRE 文档,而不仅仅是产品数组。如果文档包含未“完成”的产品,则应显示整个文档。如果一个文档的所有产品都设置为“已完成”,则根本不会返回该文档。

MongoDB 版本:3.2.4

示例

假设我们有一个包含三个文档的集合。

这个会匹配,因为其中一个状态不是“完成”。

{
    _id : 1,
    products : [
        {
            qte : 10,
            status : "finished"
        },
        {
            qte : 21,
            status : "ongoing"
        },
    ]
}

这不匹配,因为所有状态都设置为“完成”

{
    _id : 2,
    products : [
        {
            qte : 35,
            status : "finished"
        },
        {
            qte : 210,
            status : "finished"
        },
        {
            qte : 2,
            status : "finished"
        },
    ]
}

这也不匹配,因为没有产品。如果 products 字段未定义,它也不会匹配。

{
    _id : 3,
    products : []
}

同样,如果我们在本例中包含三个文档的集合中运行查询,输出将是:

[
    {
        _id : 1,
        products : [
            {
                qte : 10,
                status : "finished"
            },
            {
                qte : 21,
                status : "ongoing"
            },
        ]
    }
]

只有第一个文档被退回,因为它至少有一个产品的状态不是“已完成”,但最后两个没有成功,因为它们的所有产品的状态都设置为“已完成” ",或者根本没有任何产品。

【问题讨论】:

  • 所以帮助我用简单的英语理解!您是否正在尝试查找状态已完成但想要排除没有状态的文档的文档?
  • @Saleem 正如第二段中提到的,这正是我正在寻找的。 “products”数组中包含的所有对象的“status”字段与“finished”字段不同的文档。
  • 听起来不错。那么 products 数组可能有多个status 字段?如果是这种情况,请使用完整的示例文档更新您的帖子。
  • @Saleem 否。数组包含对象。每个对象都有一个status 字段。看一下第一段文字。它代表一个文档模式。数组products 包含的对象都具有status 字段。
  • 知道了。看看我下面的帖子。希望它是你在找什么。

标签: javascript mongodb mongodb-query aggregation-framework


【解决方案1】:

尝试以下查询。它正在获取状态不等于"finished"的文档

注意:此查询仅适用于 MongoDB 3.2+

db.collection.aggregate([
    {
      $project:{
        "projectid" : 1,
        "campname" : 1,
        "campstatus" : 1,
        "clientid" : 1,
        "paymentreq" : 1,
        products:{
          $filter:{
            input:"$products", 
            as: "product", 
            cond:{$ne: ["$$product.status", "finished"]}
           }
        }
      }
    },
    {
      $match:{"products":{$gt: [0, {$size:"products"}]}}
    }
])

【讨论】:

  • 不幸的是,它只会检查products数组的第一个对象是否具有不等于“完成”的字段状态。我在一个集合中运行查询,该集合包含一个包含两个产品的文档,其中只有一个产品的状态为“已完成”并且它没有返回文档。数组中的所有产品都必须具有“已完成”状态,以便查询忽略文档。呵呵,好复杂啊。
  • 这就是我要求您更新包含多个产品的真实文档样本的原因。发布架构是不够的。还要提及您的 MongoDB 版本。
  • 全部完成!感谢您的帮助,很抱歉给您带来麻烦。
  • 出现以下错误:解析失败:过滤器:[ { $project: { products: { $filter: { input: \"$products\", as: \"product\", cond : { $ne: [ \"$$product.status\", \"finished\" ] } } } } } ]。 'filter' 字段必须是 BSON 类型的 Object。
  • 你确定你有 MongoDB 3.2 吗?在 MongoDB shell 中尝试上述查询。
【解决方案2】:

你需要.aggregate()而不是.find()。这是确定 ALL 元素是否实际上不包含您想要的内容的唯一方法:

// Sample data
db.products.insertMany([
  { "products": [
    { "qte": 1 },
    { "status": "finished" },
    { "status": "working" }
  ]},
  { "products": [
    { "qte": 2 },
    { "status": "working" },
    { "status": "other" }
  ]}
])

然后用$redact进行聚合操作:

db.products.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$anyElementTrue": [
          { "$map": {
            "input": "$products",
            "as": "product",
            "in": { 
              "$eq": [ "$$product.status", "finshed" ]
            }
          }}
        ]
      },
      "then": "$$PRUNE",
      "else": "$$KEEP"
    }
  }}
])

或者你也可以使用更穷更慢的表弟$where

db.products.find(function(){
  return !this.products.some(function(product){
    return product.status == "finished"
  })
})

两者都只返回一个示例文档:

{
        "_id" : ObjectId("56fb4791ae26432047413455"),
        "products" : [
                {
                        "qte" : 2
                },
                {
                        "status" : "working"
                },
                {
                        "status" : "other"
                }
        ]
}

所以$anyElementTrue$map 输入或.some() 基本上在这里做同样的事情并评估是否有任何匹配。您使用“否定”断言来“排除”实际找到匹配项的文档。

【讨论】:

  • 如果集合“直接”包含数组,那肯定会起作用。问题是 products 数组包含在文档中。我尝试运行查询,它首先运行,但返回了所有文档。请使用我在问题底部提供的示例数据。
  • @ÉrikDesjardins 我认为您对我也将集合称为"products"这一事实感到困惑。当然,所有数据都表示在一个“数组”中,这里也称为"products"。所以再看一遍。这次小心点。
  • 好吧,我的错。对此感到抱歉——但查询仍然返回所有值,即使是所有状态为“已完成”或数组中没有产品的值。
  • @ÉrikDesjardins 不,它没有,这里的例子证明了这一点。您可能对自己的挫败感和过去一个小时一直在忍受的喋喋不休感到有些疲惫。深呼吸,运行示例并查看结果。你肯定没有在几分钟内接受它。放松,你会发现我是对的。
  • 我真的不想和你争论,因为你在 MongoDB 方面比我更有经验。如果您查看 pastebin 链接,我运行了您提供的查询,并获得了包含在 projects 数组中的对象都包含 "status" : "finished" 的文档。那些不应该被退回。也不应返回具有空 projects 数组的对象。如果您查看查询的输出(也在粘贴箱上)。您会看到返回的两个文件。两者都不应该存在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-15
  • 2021-07-09
  • 1970-01-01
  • 2021-06-10
  • 1970-01-01
  • 2022-01-22
相关资源
最近更新 更多