DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

MongoDB 查询报 Projection cannot have a mix of inclusion and exclusion

MongoDB find 投影不能混用普通字段的包含和排除。按输出字段约定选择投影模式,核对 _id 例外,避免修复报错时扩大接口返回范围。

MongoDB 查询只想返回 name,写成 { name: 1, created: 0 } 后,却报 Projection cannot have a mix of inclusion and exclusion。这份投影同时指定了“只选这些字段”和“排除这些字段”,普通字段不能这样混用。

本文针对 find() 中使用 0、1 选择返回字段的普通投影。聚合表达式、数组投影和驱动自己的对象映射还有其他规则,不能把下面的例子当作所有投影语法的完整说明。

找到真正传给服务器的投影对象

在 mongosh 中,find() 的第一个参数是过滤条件,第二个参数才是投影。以下是会触发问题的结构示例:

db.sample_items.find({}, { name: 1, created: 0 })

集合名和字段名都是示例。name: 1 选择包含模式,未列出的普通字段本来就不会返回,无需再写 created: 0。若通过 ORM 或驱动调用,应查看它最终生成的投影,而不是把 shell 的参数位置直接套进另一种 API。

错误与 created 保存成什么类型没有关系,也不要求删除文档里的字段。先检查投影中的数值与字段拼写;修改筛选条件或重建索引不会修复这种混用。

只返回指定字段:列出要保留的字段

只需要 name,并且不需要文档标识时,使用:

db.sample_items.find({}, { name: 1, _id: 0 }).limit(5)

这里 _id: 0 是特殊允许的排除项。默认情况下,包含投影仍会返回 _id;如果下游需要它,可以省略这一项:

db.sample_items.find({}, { name: 1 }).limit(5)

需要更多字段,就在同一包含投影里追加对应的 1。不要为了隐藏 created、password 或其他未选中的字段再补 0。

注意 _id 中的下划线。id 只是另一个普通字段,{ name: 1, id: 0 } 不具有同样的例外。应用模型把标识显示成 id,也不意味着数据库字段已经改名。

返回其余字段:只列出要排除的字段

业务确实需要除 created 外的全部字段时,可以使用排除投影:

db.sample_items.find({}, { created: 0 }).limit(5)

若同时不需要 _id:

db.sample_items.find({}, { created: 0, _id: 0 }).limit(5)

这与“只返回 name”不是相同的接口约定。即使当前文档只有三个字段,两种查询看起来一样,日后新增字段时,排除投影会把新增字段一起返回。对外接口若只允许一组明确字段,应保留包含模式,避免为了消除报错扩大输出范围。

投影只改变返回文档,不会从集合中删除字段。不要使用更新操作的 $unset 来模拟查询时隐藏字段,那会变成数据修改。

按返回结构验收

修改前保留原投影及接口字段约定,用允许读取的有限样本核对实际输出:要求的字段存在,不需要的字段未暴露,_id 的去留符合约定。示例中的 .limit(5) 仅用于限制检查结果数量,实际过滤条件还需按业务范围设置。

还应检查包含额外字段、缺少可选字段的文档。投影不会为缺失字段自动补出业务默认值;不能只拿字段最齐的一条记录验证。

查询层改动的回退是恢复原先经过确认的字段选择,不需要恢复数据库数据。若改成排除模式后已经把额外字段发送给调用端,回退查询也不能撤回这些响应,因此发布前应重点检查输出范围。