ActiveRecord通过多个连接进行查询

时间:2013-10-04 06:53:17

标签: ruby-on-rails-4 rails-activerecord active-record-query

我有这样的架构。

database schema

managers  
    has_many :emails  
    has_many :stores

emails  
    belongs_to :manager

stores  
    belongs_to :manager  
    belongs_to :region

regions  
    has_many :stores  
    has_many :readings

readings  
    belongs_to :regions

我想获得经理的阅读材料。在SQL中我会做这样的事情。

SELECT * FROM managers  
    JOIN stores ON stores.manager_id = managers.id  
    JOIN regions ON stores.region_id = regions.id  
    JOIN readings ON readings.region_number = regions.number  
WHERE  
    manager.name = 'John Smith'  
AND  
    regions.number = '1234567'
LIMIT 100

我无法弄清楚如何在activerecord中执行此操作。我一直试图理解http://guides.rubyonrails.org/active_record_querying.htmlhttp://guides.rubyonrails.org/association_basics.html,但它并没有陷入其中。我想我只需要从不同的角度来看待它。

我以为我会访问这样的数据,但我想我不明白它是如何工作的。

managers.name  
managers.stores.name  
managers.stores.regions.readings.limit(10)

我一直都是这样的事情,这是一件非常丑陋的事。

managers.first.stores.first.regions.first.readings.limit(10)

4 个答案:

答案 0 :(得分:44)

考虑以下模型(并使用has_many):

class Reading < ActiveRecord::Base
  belongs_to :region,
    inverse_of: :readings
end

class Region < ActiveRecord::Base
  has_many :readings,
    inverse_of: :region

  has_many :stores,
    inverse_of: :region    
end

class Store < ActiveRecord::Base
  belongs_to :region,
    inverse_of: :stores

  belongs_to :manager,
    inverse_of: :stores
end

class Manager < ActiveRecord::Base
  has_many :stores,
    inverse_of: :region

  has_many :emails,
    inverse_of: :manager

  has_many :regions,
    through: :stores

  has_many :readings,
    through: :regions
end

class Email < ActiveRecord::Base
  belongs_to :manager,
    inverse_of: :emails
end

现在你的问题有点含糊不清,因为你说你想获得经理的读数,但你的SQL根本没有选择读数,也规定了一个区域。

假设您希望所有阅读匹配给定的经理和地区:

@readings = Reading.joins(region: { stores: :manager }).where(
  manager: { name: 'John Smith' },
  region: { id: 1234567 })

假设您还希望加载区域,商店和经理以避免1 + N查询:

@readings = Reading.includes(region: { stores: :manager }).where(
  manager: { name: 'John Smith' },
  region: { id: 1234567 })

假设您有经理姓名并且想要他们的详细信息和读数:

@manager = Manager.where(name: 'John Smith').first!
@readings = manager.readings

以上所有查询示例都会返回ActiveRecord::Relation,可以使用where条件进一步链接,或joinslimitgroup,{ {1}}和having

您还应该考虑orderjoinsincludespreloadeager_load方法的差异。有一个关于他们的简介here我也鼓励你阅读关于Arel的文档,指南和博客,因为它也支持连接和别名。

在愤怒中使用ActiveRecord一段时间后,我得出的结论是Sequel / SequelModel是比ActiveRecord好得多的DB / ORM。没有对开发者的不尊重,但我发现Sequel是一个更好的工具。 Arel多年来一直有瘦文档,ActiveRecord / Arel在连接条件,连接类型控制和热切加载,工会,交叉点,递归查询,树/邻接列表以及许多其他SQL功能等许多方面都存在缺陷覆盖。

由于你似乎刚开始使用AR,你可能希望从Sequel开始,而不是与弱点和ActiveRecord查询的挫折斗争,包括AR和Arel的脱节使用,关系与关联和查询组成的怪异,它继续下去。没有什么比知道你想要的SQL更令人沮丧但是ActiveRecord / Arel合谋阻止你,所以你被迫使用被吹捧的逃生路线并且'只使用SQL字符串片段'然后你得到一个无法链接的结果,但你的其余代码需要一个关系! (例如分页结果)

答案 1 :(得分:8)

我想评论来自 user1757006 2013年10月4日14:39 的评论,但我没有足够的分数。 无论如何,这解决了更深层次的嵌套方案。我添加了行星和国家模型,以显示在需要链接其他模型时语法如何工作。

@readings = Reading.includes(planet: [ country: [ region: [ {stores:
:manager}]]]).where(
manager: { name: 'John Smith' },   
region: {id: 1234567 })

答案 2 :(得分:5)

尝试这样的事情:

managers.joins(stores: {regions: :readings}).where('managers.name = ? AND regions.number = ?', 'John Smith', '1234567').limit(100) 

答案 3 :(得分:5)

假设managers是型号名称

 managers.find(:all,
               :joins=>" JOIN stores ON stores.manager_id = managers.id  
                         JOIN regions ON stores.region_id = regions.id  
                         JOIN readings ON readings.region_number = regions.number"
               :conditions=>"manager.name = 'John Smith' AND regions.number = '1234567'"
               :limit=>100)