当模型中存在关联时,Rails需要努力理解为什么需要连接/包含

时间:2016-11-25 15:59:57

标签: sql ruby-on-rails postgresql ruby-on-rails-4 activerecord

我正在尝试创建一个非常复杂的查询并且遇到麻烦 - 所以我要回到基础知识来尝试找出我所缺少的内容。 我一直在阅读Rails GuidesActive Record Associations Active Record Query Interface(特别是section 12 - 加入)的class Customer < ActiveRecord::Base has_many :orders end class Order < ActiveRecord::Base belongs_to :customer end ,我无法理解它们之间的关系以及加入/包含的原因是什么需要的。

“关联”页面显示“通过Active Record关联,我们可以通过声明性地告诉Rails两个模型之间存在连接来简化这些操作和其他操作。” “查询”页面的第12.2节说“Active Record允许您使用模型上定义的关联名称作为在使用连接方法时为这些关联指定JOIN子句的快捷方式。”

这两个陈述对我来说似乎有些不一致。如果我创建为belongs_to关联,为什么在尝试从两个表中提取数据时需要连接?另一种看法:

@orders = Order.all

如果我@orders.first.customer.name,我可以通过@orders=Order.where('customer.name ilike "%smith%"').joins(:customer)输出客户名称。但是,如果我想在名称中选择所有带有“smith”的订单,我会执行{{1}}

之类的操作

这种“关系”如何在上半年发挥作用,但需要在下半场加入?

1 个答案:

答案 0 :(得分:1)

您无需加入,但在调用您的关联之前,您的数据将无法加载。

这是ActiveRecord::Base的质量,称为延迟加载。

您可以在控制台的SQL输出中看到这一点。

user = User.find(1)
User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1

此特定用户模型具有hundread关联。

为什么没有人被加载?

因为我们尚未调用它们。

user.articles
Article Load (0.3ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1

现在我们看到查询已执行。

按顺序,当使用普通的Ruby时会出现问题。

例如,请考虑以下事项:

users.each do |user|
  puts user.articles.first.title
end

运行以下代码是有问题的,因为每次Ruby迭代用户时,它都只为该用户调用文章。

您最终会反复查询每个用户执行以下SQL:

Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 2 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 3 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 4 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 5 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 6 LIMIT 1
etc.

我们可以通过在单个查询中最初加载所有数据来解决此问题。

users.joins(:articles).each do |user|
  puts user.articles.first.title
end

在枚举开始之前将执行以下SQL:

Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN(1, 2, 3, 4, 5, 6, etc.)

这就是ActiveRecord::Baseincludesjoins方法发挥作用的地方。

以下是关于此事的两篇好文章:

http://blog.arkency.com/2013/12/rails4-preloading/

https://rubyinrails.com/2014/01/08/what-is-lazy-loading-in-rails/