查找字段与阵列中另一个字段比较的文档

时间:2018-05-26 00:27:55

标签: javascript mongodb mongodb-query aggregation-framework

我们说我有一组看起来像这样的文件:

{
    "_id" : ObjectId("5afa6df3a24cdb1652632ef5"),
    "createdBy" : {
        "_id" : "59232a1a41aa651ddff0939f"
    },
    "owner" : {
        "_id" : "5abc4dc0f47f732c96d84aac"
    },
    "acl" : [
        {
            "profile" : {
                "_id" : "59232a1a41aa651ddff0939f"
            }
        },
        {
            "profile" : {
                "_id" : "5abc4dc0f47f732c96d84aac"
            }
        }
    ]
}

我想找到createdBy._id != owner._id的所有文档,以及createdBy._id数组中某个条目中acl出现的位置。最后,我想更新所有此类文档,将owner._id字段设置为等于createdBy._id字段。目前,我只想弄清楚如何查询我想要更新的文档子集。

到目前为止,我已经想出了这个:

db.boards.find({
  $where: "this.createdBy._id != this.owner._id", 
  $where: function() {
    return this.acl.some(
      function(e) => {
        e.profile._id === this.createdBy._id
      }, this);
  }
)

(我已经使用ES5语法,以防ES6不行)

但是当我运行此查询时,我收到以下错误:

  

错误:错误:{" ok" :0," errmsg" :" TypeError:e.profile是   undefined:\ n_funcs2 /< @:2:36 \ n_funcs2 @:2:12 \ n"," code" :139}

如何执行此查询/此处发生了什么?我希望我的查询能够根据docs我已阅读的内容进行操作。上面,e应该是acl数组的元素,所以我希望它有一个字段profile,但似乎并非如此。

注意,我使用的是Mongo 3.2,因此我无法使用$expr,我已经看到一些资源建议是可能的。

解决

事实证明,我对这个集合的架构做了一个不正确的假设。我遇到上述错误的原因是因为某些文档的acl数组中的元素没有profile字段。以下查询检查此案例。它也有一个$where,因为我原来写的方式(有两个)似乎最终给了我条件的OR而不是AND。

db.boards.find({
  $where: function() {
    return this.acl.some(
      function(e) => {
        e.profile !== undefined && e.profile._id === this.createdBy._id && this.createdBy._id != this.owner._id
      }, this);
  }
)

1 个答案:

答案 0 :(得分:3)

你仍然可以在MongoDB 3.2中使用aggregate(),但只使用$redact代替:

db.boards.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$and": [
          { "$ne": [ "$createdBy._id", "$owner._id" ] },
          { "$setIsSubset": [["$createdBy._id"], "$acl.profile._id"] }
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

或者对于MongoDB 3.2 shell使用$where,您只需要保留this的范围副本,并且您的语法有点偏离:

db.boards.find({
  "$where": function() {
    var self = this;
    return (this.createdBy._id != this.owner._id)
      && this.acl.some(function(e) {
        return e.profile._id === self.createdBy._id
     })
  }
})

或者在ES6兼容环境中:

db.boards.find({
  "$where": function() {
    return (this.createdBy._id != this.owner._id)
      && this.acl.some(e => e.profile._id === this.createdBy._id)
  }
})

聚合是两者中性能最高的选项,应始终优于使用JavaScript评估

对于它的价值,使用$expr的新语法将是:

db.boards.find({
  "$expr": {
    "$and": [
      { "$ne": [ "$createdBy._id", "$owner._id" ] },
      { "$in": [ "$createdBy._id", "$acl.profile._id"] }
    ]
  }
})

使用$in优先于语法稍短的$setIsSubset

  

注意此处JavaScript比较的唯一原因是因为您错误地将ObjectId值存储为"字符串"在那些领域。哪里有一个真实的" ObjectId就像在_id字段中一样,比较需要采用"字符串"来自valueOf()以进行比较:

    return (this.createdBy._id.valueOf() != this.owner._id.valueOf())
      && this.acl.some(e => e.profile._id.valueOf() === this.createdBy._id.valueOf())
  

没有它,它实际上是一个"对象比较"使用JavaScript和{ a: 1 } === { a: 1 }实际上是false。因此,避免这种复杂性是本地运营商的另一个原因。