使用$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
相关产品推荐
相关产品推荐

