$expr比较字段必须用$前缀引用,如{"$expr":{"$gt":["$size","$limit"]}};不支持字面量或JavaScript表达式,且无法利用普通索引。

用 $expr 比较同一文档的两个字段,必须用 $ 开头引用字段名
直接写 fieldA > fieldB 会报错,MongoDB 会把它当成字面量比较,而不是字段取值。$expr 要求所有字段引用都带 $ 前缀,且必须包裹在聚合表达式里(比如 $gt、$eq)。
常见错误现象:{ $expr: { $gt: ["size", "limit"] } } —— 这里 "size" 和 "limit" 是字符串字面量,不是字段值;正确写法是 { $expr: { $gt: ["$size", "$limit"] } }。
- 只在
$expr内部用$字段名,外面查字段不用加$(比如find({ status: "active" })) -
$expr不支持 JavaScript 表达式,只能用 MongoDB 的聚合操作符 - 字段名不能带引号(除非是嵌套路径中的点号需转义,但一般避免)
$expr 支持的常用比较操作符和写法差异
$expr 本身不执行比较,它只是把聚合表达式“接入”查询上下文。真正做比较的是 $gt、$gte、$ne 这类操作符,它们要求参数是数组形式,且顺序固定(左操作数在前)。
例如判断库存是否低于预警值:{ $expr: { $lt: ["$stock", "$alertThreshold"] } }。如果写成 { $expr: "$stock ,会直接语法错误——MongoDB 不解析这种内联 JS。
-
$eq、$ne:用于相等/不等,注意null比较时行为($eq对null返回false,除非两边都是null) -
$gt/$gte等数值比较:字段类型必须一致或可隐式转换,否则可能意外匹配失败(如字符串"10""2" 成立) - 字符串比较用
$strcasecmp更安全,比如{ $expr: { $eq: [{ $strcasecmp: ["$name", "$alias"] }, 0] } }
在 update 和 aggregation 中使用 $expr 的区别
$expr 在 find 和 update 的 filter 部分行为一致,但在 update 的 update 部分(即修改内容)不能直接用 $expr;它只用于条件筛选。真正要用字段值计算新值,得进聚合 pipeline update(MongoDB 4.2+)。
比如想把 status 设为 "over" 当 used > quota,不能写:update({ $expr: { $gt: ["$used", "$quota"] } }, { $set: { status: "over" } }) —— 这没问题;但不能写 { $set: { status: { $cond: [ { $gt: ["$used", "$quota"] }, "over", "ok" ] } } },因为普通 update 不支持表达式更新。
- 需要字段计算更新?改用
updateOne(..., { pipeline: [...] })形式 - 聚合 pipeline 中的
$expr用法相同,但上下文是当前阶段的文档,注意字段是否已被前面阶段投影或重命名 -
find中用$expr无法利用普通索引(除非是带$expr的索引,如 4.4+ 的collation或 5.0+ 的$function索引限制多)
容易忽略的类型和空值陷阱
字段类型不一致时,$expr 的比较可能静默失败。比如 "$price" 是字符串 "99.99","$minPrice" 是数字 100,$lt 会按字符串规则比较("99.99" 为 true),结果不符合业务预期。
更隐蔽的是 null 或缺失字段:如果文档没有 discount 字段,"$discount" 取值就是 null,$gt: ["$price", "$discount"] 会返回 false(任何值与 null 比较都为 false),而不是跳过该文档。
- 用
$and显式检查字段存在且非空:{ $expr: { $and: [ { $ne: ["$a", null] }, { $ne: ["$b", null] }, { $gt: ["$a", "$b"] } ] } } - 必要时用
$convert强制类型(如{ $convert: { input: "$price", to: "double" } }),但注意性能开销 - 测试时别只用有值的样例文档,一定要覆盖字段缺失、
null、类型混杂的 case
实际写的时候,先确认字段是否存在、类型是否统一,再套 $expr;临时调试可以先在 aggregate 里用 $addFields 把表达式结果挂出来看,比直接查更直观。











