有没有一种更有效的方法来使用ActiveRecord进行编码?

时间:2012-03-07 04:39:06

标签: sql ruby-on-rails ruby-on-rails-3 activerecord

我有三张桌子:

  1. 用户
  2. 问题
  3. User_Questions

    1.用户问题的列有user_id,question_id和answer

  4. 我想找一个未经回答的随机问题,因此在user_questions表中没有行

    如果每个问题都已得到回答,那么请回答任何随机问题。

    我被告知这可以通过OUTER JOIN完成,但我是一个SQL菜鸟,我不知道如何在Rails中这样做。
    这就是我所拥有的:

    def next_question          
      q = Question.all - Question.joins(:user_questions).where
           (user_questions: { user_id: user_id })
      q = Question.all if q.empty?
      return q[rand(q.size)]
    end  
    

2 个答案:

答案 0 :(得分:1)

在模型类上调用all方法几乎没有充分的理由。这会将该类型数据库中的每条记录加载到内存中,除非您绝对确定这是一小组记录,否则您可能会挂起整个系统。即使这样,加载所有内容然后樱桃选择一件事并丢弃其余部分是非常糟糕的形式。这就像从亚马逊订购每件商品中的一件,挑选出你想要的笔,然后把剩下的交货扔进垃圾桶。

您可能想要的是随机选择尚未分配的一个记录。这可能看起来像这样:

Question.where('id NOT IN (SELECT question_id FROM user_questions WHERE user_id=?)', user_id).order('RAND()').first

JOIN的问题在于,您要查找user_questions表中匹配的记录,而不是相反的记录。

此查询假定用户回答的问题数量相对较少,或者NOT IN可能会显着增加费用。

答案 1 :(得分:1)

是的,你可以使用LEFT OUTER JOIN。普通的INNER JOIN只包含与连接条件匹配的行,LEFT JOIN将包含匹配的行,并通过在所有列中放置NULL来充实不匹配的行(PostgreSQL docs具有合理的描述)。

因此,您执行LEFT JOIN,然后通过查找NULL来查找不匹配的行。 SQL看起来有点像这样:

select ...
from questions
left outer join user_questions on questions.id = user_questions.question_id
where user_questions.question_id is null

这会给你所有尚未回答的问题。在ActiveRecord中,您可以执行以下操作:

Question.joins('left outer join user_questions on question.id = user_questions.question_id')
        .where(:user_questions => { :question_id => nil })
        .limit(1)

您可能也希望使用随机排序,但这应该让您开始。如果这不能给你一个匹配,那么你可以用这样的东西抓住一个随机问题:

n = Questions.count
q = Questions.offset(rand(n)).limit(1)

您也可以使用Questions.order('random()').limit(1)执行上述操作,但ORDER BY random()可能会对大型数据库感到不快;获得计数应该非常快,所以在Ruby中进行随机选择应该足够快,并且不会让你的数据库讨厌你。

另外看看documentation有关整理joins的方法,我正在使用一个略微非标准结构的数据库,所以我必须做很长时间的事情;除非我拼写出来,否则ActiveRecord拒绝为我做LEFT JOIN,YMMV。