MongoDB中同现关系需用聚合管道手动实现,核心是$group配合$sum与$cond统计字段共现频次,避免$unwind膨胀,优先前置$match过滤并利用复合索引提升效率。

MongoDB 中的同现关系,本质是“多个字段在同一个文档中同时出现”的统计需求,不是内置功能,得靠聚合管道手动实现。
用 $group + $sum 统计字段共现频次
同现(co-occurrence)在推荐、风控、日志分析中很常见,比如“用户既点了广告 A,又加购了商品 B”——这需要统计 A 和 B 在同一文档中同时为 true 的次数。MongoDB 没有 $cooccur 这种操作符,必须用聚合表达式组合实现。
- 先用
$group按需分组(如按user_id),再用$sum配合布尔转数字:{$sum: {$cond: [{$and: ["$has_ad_a", "$has_product_b"]}, 1, 0]}} - 别直接对字段做
$sum: "$field"——如果字段是字符串或缺失,会报错或结果为 0;务必用$cond显式判断 - 若字段是嵌套结构(如
behavior.click.ad_id),注意路径有效性,空值或类型不匹配会导致整个条件为 false
避免 $unwind 导致的爆炸式膨胀
当原始文档含数组(如 tags: ["news", "sports"]),想统计“news 和 sports 是否同现于一个文档”,容易下意识用 $unwind 展开再匹配——但这是危险操作。
-
$unwind会让一条含 10 个 tag 的文档变成 10 条,如果后续还要两两组合判断,数据量可能指数级增长 - 正确做法是用
$setIsSubset或$allElementsTrue:例如{$setIsSubset: [["news", "sports"], "$tags"]}直接判断数组是否包含两个值 - 如果数组很长且只关心是否存在,用
$in配合$expr更轻量:{$expr: {$and: [{$in: ["news", "$tags"]}, {$in: ["sports", "$tags"]}]} }
索引对同现查询没直接加速,但影响底层扫描效率
同现逻辑运行在聚合阶段,MongoDB 不会对 $cond 或 $and 表达式自动走索引。但前置过滤能大幅减少输入文档数。
- 把高选择性的过滤条件(如时间范围、状态码)放在
$match阶段最前面,越早筛掉无效文档,后续计算越快 - 复合索引可以覆盖部分前置条件,例如
{created_at: 1, status: 1}能加速$match: {created_at: {$gt: ...}, status: "active"} - 别指望给
field_a和field_b分别建单字段索引就能优化同现查询——MongoDB 不会合并两个单字段索引去服务一个$and条件
真正难的不是写出来,而是意识到“同现”不是原子操作:它依赖文档粒度,一旦业务要求跨文档(比如“用户 A 点击后,用户 B 一小时内下单”),就得跳出单集合聚合,引入时间窗口、变更流或外部状态协调——那已经是另一个问题域了。

















