【问题标题】:Find records where join doesn't exist查找不存在连接的记录
【发布时间】:2012-12-24 10:37:06
【问题描述】:

我可以通过用户是否投票来限制所有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 范围不正确。我基本上想找到没有投票的地方。相反,它会尝试查找是否存在不等于当前用户的投票。任何想法如何返回不存在连接的所有记录?

【问题讨论】:

    标签: sql ruby-on-rails postgresql


    【解决方案1】:

    使用EXISTS 表达式:

    WHERE NOT EXISTS (
       SELECT FROM votes v  -- SELECT list can be empty
       WHERE  v.some_id = base_table.some_id
       AND    v.user_id = ?
       )
    

    区别

    ...NOT EXISTS() (Ⓔ) 和 NOT IN() (Ⓘ) 之间是双重的:

    1. 性能

      Ⓔ 通常更快。一旦找到第一个匹配项,它就会停止处理子查询。 The manual:

      子查询通常只会执行足够长的时间来确定 是否至少返回一行,而不是一直返回。

      Ⓘ也可以通过查询计划器进行优化,但程度较小,因为NULL 处理使其更加复杂。

    2. 正确性

      如果子查询表达式中的结果值之一是NULL,则Ⓘ的结果是NULL,而普通逻辑期望TRUE - 并且Ⓔ将返回@​​987654333@。 The manual:

      如果所有每行结果不相等或为空,则至少 一个null,那么NOT IN的结果就是null。

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

    示例

    您的查询可能如下所示:

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

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

    除了NOT EXISTSNOT IN,还有LEFT JOIN / IS NULLEXCEPT 的附加语法选项。见:

    【讨论】:

    • 不确定您的解决方案和 shweta 的解决方案是否有区别,但也可以
    • @Marc:我为我的建议添加了理由。
    • 谢谢。我认为它有效,但运行了一些测试并且它没有返回所有未回答的问题:SELECT "questions".* FROM "questions" INNER JOIN "votes" ON "votes"."question_id" = "questions"."id" WHERE (不存在 (SELECT 1 FROM votes WHERE user_id = ?))
    • @Marc:我添加了您的查询版本,该版本应该适用于我的答案。我还澄清了原始代码示例,以反映您希望在基表中每行得到一个答案。
    • 万岁! NOT IN 永远不会比相应的 NOT EXISTS 更正确,也永远不会更快。本质上,NOT IN 是邪恶的。 (我只是不喜欢anti-pattern这个词)
    【解决方案2】:

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

    EDIT-1

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

    EDIT-2

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

    【讨论】:

    • 我已经更新了答案,现在它会返回所有没有答案的问题
    • 似乎更近了。我正在尝试为特定用户获取未回答的问题,即 u 变量。任何想法如何检查特定用户的投票不存在?
    【解决方案3】:

    如果你想以优雅和 Rails 风格的方式进行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) }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-12
      • 1970-01-01
      • 2014-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多