You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data Mongo聚合中比较同文档两个日期字段的实现问题

问题根因
  1. 同文档字段比较逻辑错误
    • Spring Data MongoDB 普通Criteria的gt/lt等比较方法,默认将传入参数识别为常量值,不会解析为文档字段引用。你最初传入"created_date"或"ISODate('created_date')"时,MongoDB会直接拿字符串值和日期字段做比较,要么类型不匹配无法命中结果,要么逻辑完全错误,自然返回空集。
    • Mongo Shell里的$where写法性能极差(全表BSON解码,无法走索引),生产环境禁止使用;Compass中写的last_modified_date: {$gt: ISODate('created_date')}属于错误语法:ISODate()构造函数会尝试把入参字符串本身解析为日期,不会读取文档内的created_date字段值,能返回结果属于场景巧合,不是标准写法。
  2. 投影后排序报错原因
    聚合管道按顺序执行,每个阶段的输出字段是下一阶段的可用字段。你写的ProjectionOperation仅显式声明了last_modified_date、created_date、greater三个输出字段,原文档用于排序的date字段被投影阶段过滤,后续sort阶段找不到字段引用,就会抛出Invalid reference 'date'异常。另外你写的andExpression用法也错误,该方法第一个参数需要传完整的表达式字符串,不是拆分传操作符和参数。
正确实现方案

方案1:$expr直接匹配(性能最优,推荐)

从Spring Data MongoDB 2.1版本开始支持Criteria.expr(),可以直接在$match阶段写聚合表达式实现同文档字段比较,不需要额外加投影阶段,不会丢失字段,完全避免排序报错,性能远高于$where。

import static org.springframework.data.mongodb.core.aggregation.ComparisonOperators.Gt.valueOf;
import org.springframework.data.mongodb.core.query.Criteria;

// 构造同字段比较条件
Criteria fieldCompareCriteria = Criteria.expr(
    valueOf("last_modified_date").greaterThan("created_date")
);

// 直接放入聚合逻辑即可
Aggregation aggregation =  newAggregation(
    lookup("brands", "brand_dfp_id", "dfp_id", "brand"),
    match(fieldCompareCriteria),
    sort(Sort.Direction.DESC, "date"),
    skip(0L),
    limit(25L)
).withOptions(AggregationOptions.builder()
    .allowDiskUse(true)
    .collation(Collation.of("en").strength(Collation.ComparisonLevel.primary()))
    .build());

上述代码生成的MongoDB管道为标准语法,可正常走查询优化:

{ "$match": { "$expr": { "$gt": ["$last_modified_date", "$created_date"] } } }

方案2:修正投影写法(适合需要提前计算标记字段的场景)

如果确实需要通过投影提前生成比较标记,需要修正两点:一是写对andExpression的表达式语法,二是投影阶段保留所有后续阶段要用到的字段,避免字段丢失。

// 正确投影写法:保留所有后续需要用到的字段,写对表达式
ProjectionOperation projectionOperation = project()
    // 把后续排序、关联、返回需要的所有字段都加入include列表,避免丢失
    .andInclude("id", "created_date", "last_modified_date", "date", "brand_dfp_id", "brand")
    // andExpression第一个参数传完整表达式,字段不需要加$前缀
    .andExpression("$gt(last_modified_date, created_date)").as("greater");

// 匹配比较结果
MatchOperation matchOperation = match(Criteria.where("greater").is(true));

// 后续聚合逻辑不变

如果不想逐个枚举字段,可以用.andInclude("$$ROOT")直接保留整个原文档,再追加greater字段,不会丢失任何原有字段。

注意事项
  • 聚合管道每个阶段都会改变输出文档结构,写投影、分组这类结构变更阶段时,必须确认后续阶段用到的字段都被保留。
  • 禁止生产环境使用$where做查询,性能比标准聚合表达式低一个数量级以上,还存在脚本注入风险。

内容的提问来源于stack exchange,提问作者Bill May

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:30:44