MongoDB:如何基于同数组内条目过滤tags数组字段
给定集合
db.playground.insertMany([ { "id": 1, "tags": [{"t": "T1", "v": "t11", "c": 1 }, {"t": "T2", "v": "t12"}, {"t": "T3", "v": "t13"}]}, { "id": 2, "tags": [{"t": "T1", "v": "t21", "c": 2 }, {"t": "T2", "v": "t22"}, {"t": "T3", "v": "t23"}]}, { "id": 3, "tags": [{"t": "T1", "v": "t22", "c": 2 }, {"t": "T2", "v": "t21"}, {"t": "T3", "v": "t33"}]}, ])
过滤需求
对每个文档的tags数组进行过滤,规则如下:
- 若数组中存在至少一个
t=T1、v=t21且c=1的标签,则保留t=T2或t=T3的标签; - 若数组中存在至少一个
t=T1、v=t21且c=2的标签,则保留t=T1或t=T3的标签。
尝试代码及问题
尝试代码
db.playground.find({}, { id: 1, tags: { $filter: { input: "$tags", as: "tag", cond: { $or: [ { $and: [ // { $eq: ["$tags.v", "t21"]}, // 此谓词为何不生效 { $eq: ["$tags.c", [1]]}, // 为何需要用数组形式? { $in: ["$$tag.t", ["T2", "T3"]] } ] }, { $and: [ // { $eq: ["$tags.v", "t21"]}, // 此谓词为何不生效 { $eq: ["$tags.c", [2]]}, // 为何需要用数组形式? { $in: ["$$tag.t", ["T1", "T3"]] } ] } ] } } }} )
遇到的问题
- 谓词
{ $eq: ["$tags.v", "t21"] }无法生效,原因是什么? - 为什么需要用数组形式
{ $eq: ["$tags.c", [1]]},用NumberInt(1)却不行? - 如何确保
c和v属于同一个标签?当前代码会错误匹配第三个条目,因为条件分别在不同标签上满足。
解决方案
问题1:$tags.v无法生效的原因
$tags是数组字段,直接取$tags.v会返回整个数组中所有元素的v值组成的数组(比如第一个文档会返回["t11", "t12", "t13"])。用$eq把这个数组和单个字符串t21比较,数组与字符串永远不相等,所以这个条件永远不成立。
问题2:数组形式匹配$tags.c的原因
同理,$tags.c返回的是数组中所有元素的c值组成的数组(比如第一个文档返回[1, undefined, undefined])。你写$eq: ["$tags.c", [1]]是在比较两个数组是否完全相等,这并不是正确的逻辑——你需要的是数组中是否存在某个元素的c=1,而不是整个c数组等于[1]。用NumberInt(1)直接比较的话,数组和数字类型不匹配,自然条件不成立。
问题3:确保c和v属于同一个标签的正确做法
要判断文档的tags数组中是否存在满足t=T1、v=t21且c=指定值的元素,需要先做文档级别的全局判断,再基于判断结果过滤tags数组。可以用$anyElementTrue结合$map实现这个全局判断。
正确查询代码
db.playground.aggregate([ { $addFields: { // 判断当前文档是否存在t=T1、v=t21、c=1的标签 hasCondition1: { $anyElementTrue: { $map: { input: "$tags", as: "tag", in: { $and: [ { $eq: ["$$tag.t", "T1"] }, { $eq: ["$$tag.v", "t21"] }, { $eq: ["$$tag.c", 1] } ] } } } }, // 判断当前文档是否存在t=T1、v=t21、c=2的标签 hasCondition2: { $anyElementTrue: { $map: { input: "$tags", as: "tag", in: { $and: [ { $eq: ["$$tag.t", "T1"] }, { $eq: ["$$tag.v", "t21"] }, { $eq: ["$$tag.c", 2] } ] } } } } } }, { $project: { id: 1, tags: { $filter: { input: "$tags", as: "tag", cond: { $or: [ // 满足条件1时,保留T2或T3标签 { $and: ["$hasCondition1", { $in: ["$$tag.t", ["T2", "T3"]] }] }, // 满足条件2时,保留T1或T3标签 { $and: ["$hasCondition2", { $in: ["$$tag.t", ["T1", "T3"]] }] } ] } } }, // 移除临时判断字段 hasCondition1: 0, hasCondition2: 0 } } ])
代码说明
$addFields阶段:通过$map遍历每个tags元素,检查是否符合两个条件之一,再用$anyElementTrue判断数组中是否存在至少一个符合条件的元素,生成两个临时判断字段。$project阶段:基于临时字段的结果,用$filter过滤tags数组,只保留符合规则的标签,最后移除临时字段。
内容的提问来源于Stack Exchange,提问作者Bobby Tables
相关产品推荐
相关产品推荐

