查找一个字段与数组中另一个字段进行比较的文档
Posted
技术标签:
【中文标题】查找一个字段与数组中另一个字段进行比较的文档【英文标题】:Find Documents Where a Field Compares with Another in an Array 【发布时间】:2018-11-05 09:20:56 【问题描述】:假设我有一组如下所示的文档:
"_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 is 未定义:\n_funcs2/
如何执行此查询/这里发生了什么?根据我读过的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);
)
【问题讨论】:
您认为提供的答案中是否有某些内容无法解决您的问题?如果是这样,那么请对答案发表评论,以澄清究竟需要解决哪些尚未解决的问题。如果它确实回答了您提出的问题,请注意Accept your Answers您提出的问题 【参考方案1】:您仍然可以在此处将 aggregate()
与 MongoDB 3.2 一起使用,但只需使用 $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
。因此,避免这种复杂性是另一个原因有本机运算符来代替。
【讨论】:
@Ashish 他们经常这样做,因为我主要在这里输入它们,这是在早餐时完成的。更正了命名和缺少大括号。 嗨,尼尔,感谢您的回复,尽管我需要更多帮助。关于第一个查询(使用aggregate()
),我看到它返回的结果是owner._id
= createdBy._id
,这是我不想要的。我还想更新所有生成的文档以将owner._id
设置为等于createdBy._id
(我将编辑我的问题以包含此内容)。我不确定我是否可以使用聚合来做updateMany()
。关于接下来的两个(javascript)查询,我仍然收到关于“e.profile is undefined”的相同错误消息
@Kevin 这里没有一个例子返回这两个字段匹配的地方,因为它们都有一个明确的条件表明它们不应该匹配。如果您看到不同的结果,那么您可能在一个字段中有一个“字符串”,而在另一个字段中有一个 ObjectId
。解决这是一个不同的问题。您可以使用$expr
(如果可用)或$where
进行更新,我已经通过复制您在问题中提供的数据和我的代码来运行查询,并且一切正常。如果你正在做一些不同的事情,那么不要这样做,我建议你也从这里复制文档并从这里运行查询。
@Kevin 由于此处的查询实际上“没有错误”,并且它使用问题中提供的实际数据按设计工作,那么您可以确认并继续前进,如果你有一个新问题然后Ask a New Question。但是这里提出的问题已经得到了充分的回答。事实上,您在ObjectId
上获得了详细的“注意”,因为您的错误似乎很明显。
所以事实证明,发生 TypeError 的原因是因为某些文档有 acl
数组元素,而这些元素没有 profile
字段。这是我的疏忽,因为我对这个集合的模式做出了错误的假设。我将对此添加注释。谢谢。以上是关于查找一个字段与数组中另一个字段进行比较的文档的主要内容,如果未能解决你的问题,请参考以下文章
通过使用 Map Filter 与对象数组进行比较来返回字段
将一个数据帧的数组列与scala中另一个数据帧的数组列的子集进行比较