【问题标题】:Rails :include vs. :joinsRails :include 与 :joins
【发布时间】:2010-11-15 13:40:51
【问题描述】:

这更像是一个“为什么事情会这样运作”的问题,而不是一个“我不知道该怎么做”的问题......

因此,提取您知道将要使用的关联记录的福音是使用:include,因为您将获得一个连接并避免一大堆额外的查询:

Post.all(:include => :comments)

但是,当您查看日志时,并没有发生连接:

Post Load (3.7ms)   SELECT * FROM "posts"
Comment Load (0.2ms)   SELECT "comments.*" FROM "comments" 
                       WHERE ("comments".post_id IN (1,2,3,4)) 
                       ORDER BY created_at asc) 

走捷径,因为它一次拉出所有 cmets,但它仍然不是连接(所有文档似乎都这么说)。我可以加入的唯一方法是使用:joins 而不是:include

Post.all(:joins => :comments)

日志显示:

Post Load (6.0ms)  SELECT "posts".* FROM "posts" 
                   INNER JOIN "comments" ON "posts".id = "comments".post_id

我错过了什么吗?我有一个有六个关联的应用程序,我在一个屏幕上显示所有关联的数据。似乎最好有一个联合查询而不是 6 个个人。我知道从性能方面来说,进行联接而不是单独查询并不总是更好(实际上,如果您按时间计算,看起来上面的两个单独查询比联接要快),但毕竟文档我一直在阅读我很惊讶地看到 :include 没有像宣传的那样工作。

也许 Rails 意识到了性能问题,除非在某些情况下才加入?

【问题讨论】:

标签: ruby-on-rails ruby database join rails-activerecord


【解决方案1】:

Rails 2.1 似乎更改了:include 功能。 Rails 过去在所有情况下都进行连接,但出于性能原因,它被更改为在某些情况下使用多个查询。 Fabio Akita 的This blog post 提供了有关更改的一些很好的信息(请参阅标题为“Optimized Eager Loading”的部分)。

【讨论】:

  • 这很有帮助,谢谢。我希望有一种方法可以强制 Rails 进行连接,即使没有需要它的“位置”。在某些情况下,您知道加入会更有效率,并且不会产生重复的风险。
  • @JonathanSwartz 现在支持 Eager load
【解决方案2】:

.joins 只会加入表格并返回选定的字段。如果您在连接查询结果上调用关联,它将再次触发数据库查询

:includes 将预先加载包含的关联并将它们添加到内存中。 :includes 加载所有包含的表属性。如果您在包含查询结果上调用关联,它不会触发任何查询

【讨论】:

    【解决方案3】:

    连接和包含之间的区别在于,使用包含语句会生成一个更大的 SQL 查询,将来自其他表的所有属性加载到内存中。

    例如,如果您有一个充满 cmets 的表,并且您使用 :joins => users 来提取所有用户信息以用于排序等目的,那么它将工作正常并且比 :include 花费更少的时间,但是说你想要显示评论以及用户名、电子邮件等。要使用 :joins 获取信息,它必须为它获取的每个用户进行单独的 SQL 查询,而如果您使用 :include ,则此信息已准备好使用。

    很好的例子:

    http://railscasts.com/episodes/181-include-vs-joins

    【讨论】:

      【解决方案4】:

      我最近阅读了有关 Rails 中 :joins:includes 之间区别的更多信息。这是我所理解的解释(带有示例:))

      考虑这种情况:

      • 一个用户有_many cmets 和一个评论属于_一个用户。

      • User 模型具有以下属性:名称(字符串)、年龄(整数)。 Comment 模型具有以下属性:Content、user_id。对于评论,user_id 可以为空。

      加入:

      :joins 在两个表之间执行内连接。因此

      Comment.joins(:user)
      
      #=> <ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first   comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">, 
           #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,    
           #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">]>
      

      将获取 user_id(cmets 表)等于 user.id(users 表)的所有记录。因此,如果你这样做

      Comment.joins(:user).where("comments.user_id is null")
      
      #=> <ActiveRecord::Relation []>
      

      你会得到一个如图所示的空数组。

      此外,连接不会将连接的表加载到内存中。因此,如果你这样做

      comment_1 = Comment.joins(:user).first
      
      comment_1.user.age
      #=>←[1m←[36mUser Load (0.0ms)←[0m  ←[1mSELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1←[0m  [["id", 1]]
      #=> 24
      

      如您所见,comment_1.user.age 将在后台再次触发数据库查询以获取结果

      包括:

      :includes 在两个表之间执行左外连接。因此

      Comment.includes(:user)
      
      #=><ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
         #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
         #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">,    
         #<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>
      

      将导致 一个包含 cmets 表中所有记录的连接表。因此,如果你这样做

      Comment.includes(:user).where("comment.user_id is null")
      #=> #<ActiveRecord::Relation [#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>
      

      它将获取 cmets.user_id 为 nil 的记录,如图所示。

      此外,还包括在内存中加载两个表。因此,如果你这样做

      comment_1 = Comment.includes(:user).first
      
      comment_1.user.age
      #=> 24
      

      如您所见,comment_1.user.age 只是从内存中加载结果,而无需在后台触发数据库查询。

      【讨论】:

      • @HunterStevens:是的
      【解决方案5】:

      除了性能方面的考虑之外,还有功能上的差异。 当您加入 cmets 时,您要求的帖子具有 cmets - 默认情况下为内部连接。 当您包含 cmets 时,您要求的是所有帖子 - 外部联接。

      【讨论】:

        【解决方案6】:

        tl;博士

        我用两种方式对比它们:

        joins - 用于有条件地选择记录。

        包括 - 在结果集的每个成员上使用关联时。

        加长版

        Joins 旨在过滤来自数据库的结果集。您可以使用它对您的表进行设置操作。将此视为执行集合论的 where 子句。

        Post.joins(:comments)

        一样

        Post.where('id in (select post_id from comments)')

        除非有多个评论,否则您将收到重复的帖子以及加入。但是每个帖子都将是具有 cmets 的帖子。您可以使用 distinct 更正此问题:

        Post.joins(:comments).count
        => 10
        Post.joins(:comments).distinct.count
        => 2
        

        在合同中,includes 方法将简单地确保在引用关系时没有额外的数据库查询(这样我们就不会进行 n + 1 次查询)

        Post.includes(:comments).count
        => 4 # includes posts without comments so the count might be higher.
        

        寓意是,当您想要执行条件集合操作时使用joins,当您要对集合的每个成员使用关系时使用includes

        【讨论】:

        • distinct 每次都能吸引我。谢谢!
        【解决方案7】:

        .joins 用作数据库连接,它连接两个或多个表并从后端(数据库)获取选定的数据。

        .includes 作为数据库的左连接工作。它加载了左侧的所有记录,没有右侧模型的相关性。它用于预先加载,因为它将所有关联的对象加载到内存中。如果我们在包含查询结果上调用关联,那么它不会触发对数据库的查询,它只是从内存中返回数据,因为它已经在内存中加载了数据。

        【讨论】:

          【解决方案8】:

          'joins' 只是用于连接表,当您在连接上调用关联时,它将再次触发查询(这意味着将触发许多查询)

          lets suppose you have tow model, User and Organisation
          User has_many organisations
          suppose you have 10 organisation for a user 
          @records= User.joins(:organisations).where("organisations.user_id = 1")
          QUERY will be 
           select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
          
          it will return all records of organisation related to user
          and @records.map{|u|u.organisation.name}
          it run QUERY like 
          select * from organisations where organisations.id = x then time(hwo many organisation you have)
          

          本例中的 SQL 总数为 11

          但是随着 'includes' 将急切加载包含的关联并将它们添加到内存中(在第一次加载时加载所有关联)并且不会再次触发查询

          当您获得包含类似的记录时 @records= User.includes(:organisations).where("organisations.user_id = 1") 那么查询将是

          select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
          and 
          
          
           select * from organisations where organisations.id IN(IDS of organisation(1, to 10)) if 10 organisation
          and when you run this 
          

          @records.map{|u|u.organisation.name} 不会触发任何查询

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-04
            • 1970-01-01
            • 2023-03-06
            • 2011-11-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多