我们说我有一组看起来像这样的文件:
{
"_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);
}
)
答案 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
。因此,避免这种复杂性是本地运营商的另一个原因。