查找不存在连接的记录

我可以根据用户是否对其进行投票来限制所有questions 。 在模型中:

 scope :answered_by, lambda {|u| joins(:votes).where("votes.user_id = ?", u.id) } scope :unanswered_by, lambda {|u| joins(:votes).where("votes.user_id != ?", u.id) } 

在控制器中,我这样称呼它们:

 @answered = Question.answered_by(current_user) @unanswered = Question.unanswered_by(current_user) 

unanswered_by范围不正确。 我基本上想找到没有投票的地方。 相反,它试图寻找是否存在与当前用户不相等的投票。 任何想法如何返回不存在连接的所有记录?

使用EXISTS反半连接:

 WHERE NOT EXISTS ( SELECT 1 FROM votes v WHERE v.some_id = base_table.some_id AND v.user_id = ? ) 

区别

…… NOT EXISTS() (e)和NOT IN() (i)之间是双重的:

  • 性能
    (e)通常更快。 一旦找到第一个匹配的行,它就可以停止扫描表或索引。 (i)也可以由查询规划器进行优化,但程度较小,因为NULL处理更复杂。

  • 正确性
    如果子查询表达式中的结果值之一可以为NULL ,则(i)的结果可能是您期望的结果 – 并且(e)将返回 – TRUE 。 手册:

如果所有每行结果不等或为null,并且至少有一个null,则NOT IN的结果为null。

从本质上讲, (NOT) EXISTS在大多数情况下是更好的选择。

您的查询可能如下所示:

 SELECT * FROM questions q WHERE NOT EXISTS ( SELECT 1 FROM votes v WHERE v.question_id = q.id AND v.user_id = ? ) 

不要在基本查询中加入votes 。 这将使努力无效。

除了NOT EXISTSNOT IN ,还有LEFT JOIN / IS NULL 。 此相关答案中有更多语法变体:

  • 选择其他表中不存在的行

试试这个,让我知道它是否有效

编辑-1

 scope :unanswered_questions, lambda { joins('LEFT OUTER JOIN votes ON questions.id = votes.question_id').where('votes.question_id IS NULL') } 

编辑-2

 scope :unanswered_by, lambda {|u| where("questions.id NOT IN (SELECT votes.question_id from votes where votes.user_id = ?)",u.id) } 

如果你想以优雅和Rails-ish的方式进行EXISTS查询,你可以使用我写的Where Exists gem:

 Question.where_not_exists(:votes, user_id: current_user.id) 

当然,您也可以使用它的范围:

 scope :unanswered_by, ->(user){ where_not_exists(:votes, user_id: user.id) }