查找字段与数组中的另一个字段进行比较的文档 [英] Find Documents Where a Field Compares with Another in an Array
问题描述
假设我有一个看起来像这样的文档集合:
<代码>{"_id" : ObjectId("5afa6df3a24cdb1652632ef5"),由...制作" : {_id":59232a1a41aa651ddff0939f"},所有者" : {_id":5abc4dc0f47f732c96d84aac"},ACL":[{轮廓" : {_id":59232a1a41aa651ddff0939f"}},{轮廓" : {_id":5abc4dc0f47f732c96d84aac"}}]}
我想找到 createdBy._id != owner._id
的所有文档,以及 createdBy._id
出现在 的条目之一中的所有文档acl
数组.最终,我将要更新所有此类文档以将 owner._id
字段设置为等于 createdBy._id
字段.现在,我只是想弄清楚如何查询我想要更新的文档子集.
到目前为止,我想出了这个:
db.boards.find({$where: "this.createdBy._id != this.owner._id",$where:函数(){返回 this.acl.some(函数(e) =>{e.profile._id === this.createdBy._id}, 这);})
(我使用了 ES5 语法,以防 ES6 不行)
但是当我运行此查询时,出现以下错误:
<块引用>错误:错误:{ "ok" : 0, "errmsg" : "TypeError: e.profile is未定义 : _funcs2/<@:2:36 _funcs2@:2:12 ", "code" : 139 }
我如何执行此查询/这里发生了什么?根据 docs,我希望我的查询能够正常工作 我读过.上面,e
应该是 acl
数组的一个元素,所以我希望它有一个字段 profile
,但这似乎没有是这样.
注意,我使用的是 Mongo 3.2,所以我不能使用 $expr,我已经看到一些资源表明这是一种可能性.
分辨率
事实证明我对这个集合的架构做了一个错误的假设.我遇到上述错误的原因是因为某些文档有一个 acl
数组,其中的元素没有 profile
字段.下面的查询检查这种情况.它还有一个 $where
,因为我最初编写它的方式(有两个)似乎最终给了我条件的 OR 而不是 AND.
db.boards.find({$where:函数(){返回 this.acl.some(函数(e) =>{e.profile !== undefined &&e.profile._id === this.createdBy._id &&this.createdBy._id != this.owner._id}, 这);})
您仍然可以在 MongoDB 3.2 中使用 aggregate()
,但只需使用 $redact
代替:
db.boards.aggregate([{$redact":{$cond":{如果": {"$and": [{ "$ne": [ "$createdBy._id", "$owner._id" ] },{ "$setIsSubset": [["$createdBy._id"], "$acl.profile._id"] }]},"then": "$$KEEP","else": "$$PRUNE"}}}])
或者使用 $where
对于 MongoDB 3.2 shell,您只需要保留 this
的范围副本,并且您的语法有点偏离:
db.boards.find({$where":函数(){var self = this;返回(this.createdBy._id != this.owner._id)&&this.acl.some(function(e) {返回 e.profile._id === self.createdBy._id})}})
或者在兼容 ES6 的环境中:
db.boards.find({$where":函数(){返回(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
值存储为这些字段中的字符串".就像在 _id
字段中一样存在真实"ObjectId
的地方,比较需要从 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
.因此,避免这种复杂性是使用本机运算符来代替的另一个原因.
Let's say I have a collection of documents that look like this:
{
"_id" : ObjectId("5afa6df3a24cdb1652632ef5"),
"createdBy" : {
"_id" : "59232a1a41aa651ddff0939f"
},
"owner" : {
"_id" : "5abc4dc0f47f732c96d84aac"
},
"acl" : [
{
"profile" : {
"_id" : "59232a1a41aa651ddff0939f"
}
},
{
"profile" : {
"_id" : "5abc4dc0f47f732c96d84aac"
}
}
]
}
I want to find all documents where createdBy._id != owner._id
, AND where the createdBy._id
appears in one of the entries in the acl
array. Eventually, I will want to update all such documents to set the owner._id
field to equal the createdBy._id
field. For now, I'm just trying to figure out how to query the subset of documents I want to update.
So far, I have come up with this:
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);
}
)
(I have used ES5 syntax just in case ES6 isn't ok)
But when I run this query, I get the following error:
Error: error: { "ok" : 0, "errmsg" : "TypeError: e.profile is undefined : _funcs2/<@:2:36 _funcs2@:2:12 ", "code" : 139 }
How do I perform this query / what is going on here? I would have expected my query to work, based on the docs I've read. Above, e
should be an element of the acl
array, so I expect it to have a field profile
, but that doesn't seem to be the case.
Note, I'm using Mongo 3.2, so I can't use $expr, which I've seen some resources suggest is a possibility.
Resolution
It turns out that I had made an incorrect assumption about the schema of this collection. The reason I was running into the above error is because some documents have an acl
array with an element that doesn't have a profile
field. The below query checks for this case. It also has a single $where
, because the way I had written it originally (with two) seemed to end up giving me an OR of the conditions instead of an 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);
}
)
You can still use aggregate()
here with MongoDB 3.2, but just using $redact
instead:
db.boards.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$and": [
{ "$ne": [ "$createdBy._id", "$owner._id" ] },
{ "$setIsSubset": [["$createdBy._id"], "$acl.profile._id"] }
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Or with $where
for the MongoDB 3.2 shell, you just need to keep a scoped copy of this
, and your syntax was a bit off:
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
})
}
})
Or in an ES6 compatible environment then:
db.boards.find({
"$where": function() {
return (this.createdBy._id != this.owner._id)
&& this.acl.some(e => e.profile._id === this.createdBy._id)
}
})
The aggregate is the most performant option of the two and should always be preferable to using JavaScript evalulation
And for what it's worth, the newer syntax with $expr
would be:
db.boards.find({
"$expr": {
"$and": [
{ "$ne": [ "$createdBy._id", "$owner._id" ] },
{ "$in": [ "$createdBy._id", "$acl.profile._id"] }
]
}
})
Using $in
in preference to $setIsSubset
where the syntax is a little shorter.
NOTE The only reason the JavaScript comparison here works is because you have mistakenly stored
ObjectId
values as "strings" in those fields. Where there is a "real"ObjectId
just like in the_id
field, the comparison needs to take the "string" fromvalueOf()
in order to compare:
return (this.createdBy._id.valueOf() != this.owner._id.valueOf())
&& this.acl.some(e => e.profile._id.valueOf() === this.createdBy._id.valueOf())
Without that it's actually an "Object Comparison" with JavaScript and
{ a: 1 } === { a: 1 }
is actuallyfalse
. So avoiding that complexity is another reason there are native operators for this instead.
这篇关于查找字段与数组中的另一个字段进行比较的文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!