高效的 ActiveRecord 关联条件

Posted

技术标签:

【中文标题】高效的 ActiveRecord 关联条件【英文标题】:Efficient ActiveRecord association conditions 【发布时间】:2013-04-22 10:00:13 【问题描述】:

假设您在其中一个模型中有这样的关联:

class User
  has_many :articles
end

现在假设您需要获取 3 个数组,一个用于昨天写的文章,一个用于最近 7 天写的文章,一个用于最近 30 天写的文章。

你当然可以这样做:

articles_yesterday = user.articles.where("posted_at >= ?", Date.yesterday)
articles_last7d    = user.articles.where("posted_at >= ?", 7.days.ago.to_date)
articles_last30d   = user.articles.where("posted_at >= ?", 30.days.ago.to_date)

但是,这将运行 3 个单独的数据库查询。更有效的是,您可以这样做:

articles_last30d   = user.articles.where("posted_at >= ?", 30.days.ago.to_date)
articles_yesterday = articles_last30d.select  |article| 
  article.posted_at >= Date.yesterday 

articles_last7d    = articles_last30d.select  |article| 
  article.posted_at >= 7.days.ago.to_date

当然,这是一个人为的例子,并不能保证数组选择实际上会比数据库查询快,但我们只是假设它是。

我的问题是:有没有什么方法(例如一些 gem)可以通过确保您简单地指定关联条件来消除此问题的方式编写此代码,并且应用程序本身将决定是否需要执行另一个数据库查询与否?

ActiveRecord 本身似乎并没有适当地解决这个问题。您必须在每次查询数据库或将关联视为数组之间做出决定。

【问题讨论】:

你想摆脱什么?额外的数组,额外的查询,还是只是打字? 在我的实际代码中,我通常不知道是否已经加载了某些关联。我想编写不需要关心这个的代码,并且仍然总是进行最少数量的数据库查询。 我不认为有任何开箱即用的解决方案。跟踪所有条件和加载的数据需要付出很大的努力。更不用说数据库可以在幕后更改的事实。您需要手动创建最佳查询。为此,您可以使用技巧使其变得简单。 【参考方案1】:

有几种方法可以解决这个问题:

您可以通过在关联定义上指定条件哈希来为所需的每个级别创建单独的关联。然后,您可以简单地为您的用户查询预先加载这些关联,您将在整个操作中达到 3 倍的数据库,而不是为每个用户打 3 倍。

class User
  has_many articles_yesterday, class_name: Article, conditions: ['posted_at >= ?', Date.yesterday]
   # other associations the same way
end

User.where(...).includes(:articles_yesterday, :articles_7days, :articles_30days)

你可以分组。

归根结底,您需要分析您的代码并确定什么对您的应用程序来说是最快的(或者您是否应该费心去做)

【讨论】:

不幸的是,这还不够。我已经尽可能地预先加载,在这种情况下,只需要预先加载一个关联,而不是三个,因为它是一个性能关键的应用程序。【参考方案2】:

您可以使用下面的代码摆脱检查查询的必要性。

class User
  has_many :articles

  def article_30d
    @articles_last30d ||= user.articles.where("posted_at >= ?", 30.days.ago.to_date)
  end

  def articles_last7d 
    @articles_last7d ||= articles_last30d.select  |article| article.posted_at >= 7.days.ago.to_date 
  end

  def articles_yesterday 
    @articles_yesterday ||= articles_last30d.select  |article| article.posted_at >= Date.yesterday 
  end

end

它的作用:

如果使用了三个中的任何一个,则最多只进行一个查询 只计算使用过的数组,以及任何情况下的 30d 版本,但只计算一次

但是,即使您不使用它,它也不会简化最初的 30 天查询。够了吗,还是需要更多?

【讨论】:

这实际上是我现在正在做的,我上面的例子只是一个简单的例子。

以上是关于高效的 ActiveRecord 关联条件的主要内容,如果未能解决你的问题,请参考以下文章

基于条件的Rails关联查询

Rails 查询具有关联条件的多个主键

rails 获取多态类的所有关联类

ActiveRecord 何时会保存关联?

是否可以获得关联的 ActiveRecord::Relation 对象

为啥这个对 ActiveRecord 关联的引用失败了?