【问题标题】:Rails active record querying association with 'exists'Rails 活动记录查询与“存在”的关联
【发布时间】:2013-08-14 14:33:26
【问题描述】:

我正在开发一个允许会员进行调查的应用程序(会员与响应具有一对多的关系)。 Response 包含 member_id、question_id 及其答案。

调查要么全部提交,要么不提交,因此如果该成员的响应表中有任何记录,则他们已完成调查。

我的问题是,如何重新编写下面的查询以使其真正起作用?在 SQL 中,这将是 EXISTS 关键字的主要候选对象。

 def surveys_completed
    members.where(responses: !nil ).count
 end 

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    您可以使用includes,然后像这样测试相关响应是否存在:

    def surveys_completed
      members.includes(:responses).where('responses.id IS NOT NULL')
    end
    

    这是另一种选择,joins:

    def surveys_completed
      members.joins(:responses)
    end
    

    使用 Rails 4 的解决方案

    def surveys_completed
      members.includes(:responses).where.not(responses: { id: nil })
    end
    

    使用activerecord_where_assoc的替代解决方案: 这个 gem 完全按照这里的要求做:使用 EXISTS 来做一个条件。 它适用于最新版本的 Rails 4.1。

    members.where_assoc_exists(:responses)
    

    它还可以做得更多!


    类似问题:

    【讨论】:

    • 第一个选项给出了这个弃用通知pastebin.com/KbsNiqLy。第二个返回相同的记录 68 次,因为它们是 68 个问题,所以我将其修改为:members.joins(:responses).uniq.count
    • 不能避免弃用通知,因为我们需要输入一个字符串来表示“IS NOT NULL”(除非您使用 Rails 4,否则无法在纯 ActiveRecord 中翻译它)。我可能会在一秒钟内放第三个@lee
    • 我更多地查看了弃用通知,并这样做了:members.includes(:responses).where('responses.id IS NOT NULL').references(:responses).count 这也是作品。所以现在的问题是哪种方式是“最好的”方式
    • 哦,我正在使用 Rails 4
    • @MrYoshiji 实际上也可以在 Rails 3 中执行此操作,而无需使用文字 SQL 字符串:members.includes(:responses).where(Response.arel_table[:id].not_eq(nil)) .count
    【解决方案2】:

    您可以使用Where Exists gem 以优雅的 Rails 方式使用 SQL EXISTS 关键字:

    members.where_exists(:responses).count
    

    当然你也可以使用原始 SQL:

    members.where("EXISTS" \
      "(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
      count
    

    【讨论】:

    • 另一个答案不会生成使用 EXIST 关键字的 SQL。这种方式会更高效!
    【解决方案3】:

    您也可以使用子查询:

    members.where(id: Response.select(:member_id))
    

    includes 相比,它不会加载关联的模型(如果您不需要它们,这是一个性能优势)。

    【讨论】:

      【解决方案4】:

      如果您使用的是 Rails 5 及更高版本,则应使用 left_joins。否则,手动“左外连接”也将起作用。这比使用https://stackoverflow.com/a/18234998/3788753 中提到的includes 性能更高。 includes 将尝试将相关对象加载到内存中,而left_joins 将构建一个“LEFT OUTER JOINS”查询。

      def surveys_completed
        members.left_joins(:responses).where.not(responses: { id: nil })
      end
      

      即使没有相关记录(例如上面的查询,您通过 nil 找到)includes 仍然使用更多内存。在我的测试中,我发现在 Rails 5.2.1 上使用了大约 33 倍的内存。在 Rails 4.2.x 上,与手动进行连接相比,内存增加了约 44 倍。

      请参阅此测试要点: https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1

      【讨论】:

        【解决方案5】:

        where.missing(Rails 6.1+)

        Rails 6.1 引入了一种检查关联缺失的新方法 - where.missing

        请看下面的代码sn-p:

        # Before:
        Post.left_joins(:author).where(authors: { id: nil })
        
        # After:
        Post.where.missing(:author)
        

        这是一个在后台使用的 SQL 查询示例:

        Post.where.missing(:author)
        # SELECT "posts".* FROM "posts"
        # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
        # WHERE "authors"."id" IS NULL
        

        因此,您的特定情况可以重写如下:

        def surveys_completed
          members.where.missing(:response).count
        end 
        

        谢谢。

        来源:

        注意事项:

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-01
          相关资源
          最近更新 更多