【问题标题】:Search for a record that has a relationship to objects with specific value using Mongoid使用 Mongoid 搜索与具有特定值的对象有关系的记录
【发布时间】:2019-08-11 05:24:19
【问题描述】:

考虑以下代表我的 Rails 项目的两个模型的模型图。

Implement 模型表示一个人拥有的“对象”,例如,一个人可以拥有头盔、包、盾牌等。从图中可以看出,每个工具都有一个级别,这意味着一个用户可以拥有 1 级头盔、2 级包、5 级盾牌等。另外重要的是要说一个人不能拥有两个相同类型的工具,所以一个人的记录不能有两个头盔。

我目前正在尝试解决的问题是使用 Mongoid 以这样的形式查询我的 MongoDB 数据库,以便我可以找到执行级别大于或等于给定级别的人。例如,我想要所有盾牌等级大于或等于 2 且头盔等级大于或等于 5 的 Person 记录。

目前,我的解决方案涉及一种蛮力方法,即循环所有 Person 记录并查看它们是否与我想要的实现有关系,这样做的问题是它效率低下并且在使用数千条记录进行此操作时无法扩展数据库。

required_bag_level = 2
required_helmet_level = 1
Person.select do |record|
  record.implements.where(name: 'bag').first.level >= required_bag_level && record.implements.where(name: 'helmet').first.level >= required_helmet_level
end

我还尝试向 Person 对象添加一个一般级别,该级别由他拥有的每个工具级别的总和组成,例如,如果这个人有一个 2 级的包和一个 1 级的头盔,我给他一个一般级别3,然后在进行查询时,我会查找一般级别等于 3 的所有记录。

required_bag_level = 2
required_helmet_level = 1
total = required_bag_level + required_helmet_level
Person.where(general_level: total)

最后一种方法的问题在于它不适用于多种工具,例如,一个人的包级别为 2,另一个人的包级别为 1,头盔级别为 1,两者都具有一般级别 2。对于具有 2 级包的人,我会搜索所有具有 2 级一般记录的记录,这两个记录都给了我。这将是错误的,因为即使最后一条记录具有一般级别 2,他也只有包级别 1。

我想问是否有人可以为我指明正确的方向并帮助我找出可扩展的解决方案。谢谢。

【问题讨论】:

    标签: ruby-on-rails mongodb database-design mongoid querying


    【解决方案1】:

    当只与一个协会合作时,一种获得请求的方法 行为是查询“多”关联,然后转到“一”关联。 例如,给定以下设置:

    class Band
      include Mongoid::Document
      field :name, type: String
      has_many :members
    end
    
    class Member
      include Mongoid::Document
      field :name, type: String
      field :age, type: Integer
      belongs_to :band
    end
    
    foo = Band.create!(name: 'foo')
    Member.create!(band: foo, name: 'Foo Singer', age: 20)
    Member.create!(band: foo, name: 'Foo Drummer', age: 25)
    bar = Band.create!(name: 'bar')
    Member.create!(band: bar, name: 'Bar Drummer', age: 23)
    baz = Band.create!(name: 'baz')
    Member.create!(band: bar, name: 'Baz Soloist', age: 23)
    

    简单的解决方法是:

    Band.find(
      Member.all.or(
        {age: {'$gte' => 25}},
        {name: /Drummer/},
      ).pluck(:band_id)
    )
    

    这本质上等同于 ActiveRecord 执行连接的方式 急于加载。没有通过网络传输不需要的数据,并且 总共发出了两个查询。

    请注意,在解决 https://jira.mongodb.org/browse/MONGOID-4697 之前, 需要仔细构建 AND/OR 查询,以便它们 返回正确的结果。

    一种更通用、更复杂的方法是使用 MongoDB 聚合管道 (https://docs.mongodb.com/manual/core/aggregation-pipeline/)。 Ruby 驱动程序支持它 (https://docs.mongodb.com/ruby-driver/master/tutorials/ruby-driver-aggregation/) 但在 Mongoid 中没有方便的糖,因此它是较低的 水平解决方案。

    band_ids = Band.collection.aggregate([
      {'$lookup' => {
        from: 'members',
        localField: '_id',
        foreignField: 'band_id',
        as: 'members',
      }},
      {'$match' => {
        '$or' => [
          {'members.age' => {'$gte' => 25}},
          {'members.name' => {'$regex' => /Drummer/}},
        ],
      }},
      {'$project' => {_id: 1}},
    ]).to_a
    Band.find(band_ids)
    

    聚合管道允许使用来自多个集合的数据 相同的查询,通过多个 $lookup 阶段。

    聚合管道/$lookup 的一个问题是,如果未正确指定 localField 和 foreignField,服务器似乎返回一个交叉连接,而不是我预期的空结果集。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-12
      • 1970-01-01
      • 2023-03-24
      • 2021-12-12
      • 2018-08-03
      • 1970-01-01
      • 2021-05-26
      相关资源
      最近更新 更多