使此ActiveRecord查询更有效?

时间:2012-09-19 15:53:27

标签: ruby-on-rails ruby

我有UserGift个型号。用户可以将礼物发送给其他用户。我有一个关系表告诉我哪些用户收到了礼物。另一方面,用户属于School,可以是免费的或付费的。

我想要在过去一周内收到特定类型学校礼品的用户数(这是免费或付费的)。

我能做到:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:gift_recipients).flatten.uniq.count.

或者,我想知道上周有多少用户送过礼物。这有效:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id).uniq.count.

如果我想知道有多少用户在上周发送或收到了礼物,我可以这样做:

(Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:gift_recipients).flatten + Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id)).uniq.count

这一切都运行正常,但如果数据库足够大,这真的很慢。您是否有任何建议可以提高效率,可能需要使用原始SQL?

"gifts" 
  user_id (integer) 
  school_id (integer) 
  created_at (datetime) 
  updated_at (datetime) 
"gift_recipients" is a table like 
  gift_id | recipient_id,

2 个答案:

答案 0 :(得分:2)

您不希望使用collect()执行此操作,该方法将所有结果加载到内存中并在ActiveRecords数组中过滤它们。这是缓慢且危险的,因为它可能会泄漏/使用所有可用内存,具体取决于数据与服务器的大小。

发布架构后,我可以帮助您在SQL中查询/聚合,这是正确的方法。

例如,而不是:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id).uniq.count

您应该使用:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).count('distinct user_id')

...它将计算SQL中的不同user_id并返回结果,而不是返回所有对象并将它们计入内存中。

答案 1 :(得分:0)

我看到这篇旧帖子,我想发几条评论: 正如温菲尔德所说的

Gift.joins(:school).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).count('distinct user_id')

这是一个很好的方法。我会做的

Gift.joins(:school).count('distinct user_id', :conditions => ["gifts.created_at >= ? AND free_school = ?", Time.now.beginning_of_week, true])

但是因为这对我来说更好,一个人的事情,你可以检查两者是否产生完全相同的SQL查询。请注意,写入

是必要的
gifts.created_at 

避免歧义,因为两个表都有一个具有此名称的列,如果是列名

free_school

没有歧义,因为这不是礼品表中的列名。对于我正在做的第一个查询

Gift.joins(:school).where("created_at >= ? AND schools.free_school = ?", Time.now.beginning_of_week, true).collect(&:user_id).uniq.count

这很尴尬。这样做效果更好

Gift.joins(:school).count("distinct user_id", :conditions => ["gifts.created_at >= ? AND free_school = ?", Time.now.beginning_of_week, true])

避免将礼物带入记忆并用红宝石过滤它们的问题。

到目前为止,没有什么新东西。这里的关键点是我的问题是计算在上周发送或收到礼物的用户数量。为此我想出了以下

  senders_ids = Gift.joins(:school).find(:all, :select => 'distinct user_id', :conditions => ['gifts.created_at >= ? AND free_school = ?', Time.now.beginning_of_week, type]).map {|g| g.user_id}
  receivers_ids = Gift.joins(:school).find(:all, :select => 'distinct rec.recipient_id', :conditions => ['gifts.created_at >= ? AND free_school = ?', Time.now.beginning_of_week, type], :joins => "INNER JOIN gifts_recipients as rec on rec.gift_id = gifts.id").map {|g| g.recipient_id}
  (senders_ids + receivers_ids).uniq.count

我很确定存在更好的方法,我的意思是,在单个SQL查询中返回这个数字,但至少结果是只包含id的对象数组(接收者case的recipient_id) ,不将所有对象都带入内存。好吧,这只是希望通过像我这样的rails来对新的SQL查询有用:)。