查找不存在连接的记录

Mar*_*arc 9 sql postgresql ruby-on-rails

我有一个范围来限制所有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) }
Run Code Online (Sandbox Code Playgroud)

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

@answered = Question.answered_by(current_user)
@unanswered = Question.unanswered_by(current_user)
Run Code Online (Sandbox Code Playgroud)

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

Erw*_*ter 17

使用EXISTS反半连接:

WHERE NOT EXISTS (
   SELECT 1
   FROM   votes v
   WHERE  v.some_id = base_table.some_id
   AND    v.user_id = ?
   )
Run Code Online (Sandbox Code Playgroud)

区别

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

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

  • 正确性
    如果子查询表达式中的结果值之一可以是NULL,则(i)的结果可能是NULL您期望的结果 - 并且(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 = ?
    )
Run Code Online (Sandbox Code Playgroud)

千万不能加入到votes在基本查询.这将使努力无效.

此外NOT EXISTS并且NOT IN也有LEFT JOIN / IS NULL.此相关答案中的更多语法变体: