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

使用$elemMatch查询嵌套数组时Mongoid返回不一致结果

问题描述

环境信息

puts [
  "#{RUBY_ENGINE} #{RUBY_VERSION}",
  "Rails gem: #{Rails.gem_version.version}",
  "MongoDB #{Mongoid.default_client.command(buildInfo: 1).first[:version]}",
  "Mongoid gem: #{Mongoid::VERSION}"
].join("
")

运行输出:

ruby 2.7.6
Rails gem: 5.2.4.5
MongoDB 4.4.18
Mongoid gem: 7.5.1

场景说明

数据关系为Foo#bars、Bar#bazes,需实现以下查询:

  • 找出包含label为cheese的bazes的Bar文档;
  • 找出包含指定id的bazes的Bar文档;
  • 找出包含id数组中任一id的bazes的Bar文档。

其中案例1可正常实现,但案例2、3始终无法得到预期结果,具体示例如下:

示例

数据模型

class Baz
  include Mongoid::Document
  embedded_in :bar
  field :label, type: String
end
  
class Bar
  include Mongoid::Document
  embedded_in :foo
  embeds_many :bazes, class_name: "Baz"
end

class Foo
  include Mongoid::Document
  field :name, type: String
  embeds_many :bars, class_name: "Bar"
end

数据样本

foo = Foo.create!({
  name: "foo",
  bars: [
    {bazes: [{label: "table"}, {label: "chair"}]},
    {bazes: [{label: "bread"}, {label: "cheese"}]},
    {bazes: [{label: "up"}, {label: "down"}]},
    {bazes: [{label: "high"}]},
    {bazes: [{label: "low"}]}
  ]
})

查询及结果

先获取目标baz对象:

baz = foo.bars[1].bazes.last
baz.label
#=> "cheese"
baz.id
#=> BSON::ObjectId('638d3b824b9f26049b42ac0a')

案例1

foo.bars.where(bazes: {"$elemMatch": {label: baz.label}}).count
#=> 1

该查询可正常返回预期结果,验证后确认匹配正确。

案例2

使用与案例1相同的语法,改为匹配id字段:

foo.bars.where(bazes: {"$elemMatch": {id: baz.id}}).count
#=> 0

查询未返回预期结果,无法匹配到目标文档。

案例3

尝试匹配id数组,同样未得到预期结果:

foo.bars.where(bazes: {"$elemMatch": {:id.in => [baz.id]}}).count
#=> 0

核心问题

为何匹配label字段正常,但匹配id及id数组时Mongoid返回结果异常?


原因及解决方案

问题原因

MongoDB嵌入式文档中,id属性的底层存储键是_id——Mongoid在Ruby层面为我们封装了id便捷属性,但数据库实际存的是_id字段。用id作为查询条件时,MongoDB找不到对应字段,所以返回0条结果。

解决方案

案例2:匹配单个id

直接使用_id作为查询字段即可:

# 方式1:保留$elemMatch写法
foo.bars.where(bazes: {"$elemMatch": {_id: baz.id}}).count
#=> 1

# 方式2:简化点路径写法(单字段匹配时更简洁)
foo.bars.where("bazes._id" => baz.id).count
#=> 1

案例3:匹配id数组

替换为_id字段结合$in操作符:

# 方式1:保留$elemMatch写法
foo.bars.where(bazes: {"$elemMatch": {_id: {"$in": [baz.id]}}}).count
#=> 1

# 方式2:简化Mongoid语法写法
foo.bars.where("bazes._id".in => [baz.id]).count
#=> 1

补充说明

不需要额外修改模型配置,遵循MongoDB原生存储规则即可。另外,当仅需匹配嵌入式数组的单个字段时,用"bazes._id"这种点路径语法比$elemMatch更简洁高效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:30:29