我有User
和Gift
个型号。用户可以将礼物发送给其他用户。我有一个关系表告诉我哪些用户收到了礼物。另一方面,用户属于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,
答案 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查询有用:)。