1

我有UserGift模型。用户可以向其他用户发送礼物。我有一个关系表,告诉我哪些用户收到了礼物。另一方面,用户属于 a 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,
4

2 回答 2

1

您不想使用 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_ids 并返回结果,而不是返回所有对象并在内存中计算它们。

于 2012-09-19T16:00:37.693 回答
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 的对象数组(recipient_id 对于接收者的情况),而不是带来将所有对象放入内存。好吧,这只是希望对像我这样通过 Rails 进行 sql 查询的新手有用:)。

于 2012-09-22T00:48:59.653 回答