匹配所有数组成员不包含值的文档

时间:2016-03-30 02:29:47

标签: javascript mongodb mongodb-query aggregation-framework

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子句想出了这个。请注意,我需要返回整个文档,而不仅仅是产品数组。如果文档的产品未“完成”,则应存在整个文档。如果文档的所有产品都设置为“已完成”,则根本不会返回该文档。

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"
            },
        ]
    }
]

只返回第一个文档,因为它至少有一个产品没有“已完成”状态,但最后两个没有进行剪切,因为他们要么将所有产品的状态设置为“已完成” “,或根本没有任何产品。

2 个答案:

答案 0 :(得分:3)

尝试以下查询。它正在获取状态不等于"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"}]}}
    }
])

答案 1 :(得分:0)

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

// 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()基本上在这里做同样的事情并评估是否有任何匹配。您使用“否定”断言来“排除”实际找到匹配项的文档。