25

我有这样的架构。

数据库模式

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

4 回答 4

49

考虑以下模型(并通过 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 根本没有选择读数,还规定了一个区域。

假设您希望所有 Reading 都与给定的 Manager 和 Region 匹配:

@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's,可以进一步与where条件链接,或joins、、、等limitgrouphavingorder

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

在愤怒地使用 ActiveRecord 一段时间后,我得出结论,Sequel/SequelModel 是比 ActiveRecord 更好的 DB/ORM。没有不尊重开发人员,但我发现 Sequel 是一个更好的工具。多年来,Arel 的文档很薄,而 ActiveRecord/Arel 在许多领域都存在失败,例如连接条件、连接类型的控制和急切加载、联合、交集、递归查询、树/邻接列表以及 Sequel 的许多其他 SQL 特性盖子。

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

于 2013-10-04T16:56:44.190 回答
8

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

@readings = Reading.includes(planet: [ country: [ region: [ {stores:
:manager}]]]).where(
manager: { name: 'John Smith' },   
region: {id: 1234567 })
于 2016-04-17T00:37:59.203 回答
6

假设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)
于 2013-10-04T07:22:51.407 回答
5

尝试这样的事情:

managers.joins(stores: {regions: :readings}).where('managers.name = ? AND regions.number = ?', 'John Smith', '1234567').limit(100) 
于 2013-10-04T07:17:33.627 回答