わからないことが一杯!!今回は ActiveRecord で JOIN するために使う :include オプションと :joins オプションの違いを調べてみました。ActiveRecord 2.3.5 です。
利用するデータとしては blog has many articles な関係で、1つの blog あたり10個の記事を持っているとします。具体的にはこのような状態です。
# blogs テーブル
# articles テーブル
実際に、:include オプションを使った場合と :joins オプションを使った場合で試してみます。コメントで書かれている SQL が実際に発行される SQL です。
どうですか? :joins の方が予想しやすい SQL が発行されているかなと思いますが、注意すべき二つの違いがあります。
1) は今までも何となく知っていました。関連テーブルを先に読むのが :include 、必要になったら読むのが :joins です(よね?)。:joins は関連のテーブルを条件としてデータを引っ張ってくるとき(関連テーブルのデータ自体は取得する必要が無いとき)に使うのでしょう。
このように関連テーブルのデータを読み込む場合、:include だと SQL は発行されません(先読みしているため)が、:joins の場合はその都度、SQL が発行されます。
が、2) はびっくりしました。全然発行される SQL が違うんですね。以前の ActiveRecord では :include は LEFT JOIN で、:joins が INNER JOIN ていう違いだった気がする。。
※ ActiveRecord 2.1 以前はそうだったようです→ eager loadingって何?
これは要注意な気がします。例えばこんな感じで LIMIT を付けた場合
:include では blogs テーブルから15個のデータを取得して(id:1~15)、それに対応する articles テーブルのデータを取ってきます。それに対して :joins の場合は INNER JOIN した結果を15個取ってきます。この違いは怖い。。
もうちょっと実用的な例として :conditions で関連テーブルでの条件を付けてみます。その場合はこんな感じになりました(ちょっと見にくいですが)。:conditions で関連テーブルを指定すればどちらも同じ結果が取得できるようですね。ひと安心。
何はともあれ、:include で発行される SQL は直感的じゃないと思うので、どのような SQL が発行されているのかには気を付けたいところです!
ところで INNER JOIN で先読みさせたいときはどうしたらいいんでしょうか。。
利用するデータとしては blog has many articles な関係で、1つの blog あたり10個の記事を持っているとします。具体的にはこのような状態です。
# blogs テーブル
+----+--------+---------------------+---------------------+ | id | name | created_at | updated_at | +----+--------+---------------------+---------------------+ | 1 | name_1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 2 | name_2 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 3 | name_3 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 4 | name_4 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | .. | .... | ............... | ............... |
# articles テーブル
+----+----------+---------+---------------------+---------------------+ | id | title | blog_id | created_at | updated_at | +----+----------+---------+---------------------+---------------------+ | 1 | title_1 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 2 | title_2 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 3 | title_3 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 4 | title_4 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 5 | title_5 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 6 | title_6 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 7 | title_7 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 8 | title_8 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 9 | title_9 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 10 | title_10 | 1 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 11 | title_1 | 2 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 12 | title_2 | 2 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 13 | title_3 | 2 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | 14 | title_4 | 2 | 2010-04-24 14:48:58 | 2010-04-24 14:48:58 | | .. | ..... | .. | ............... | ............... |
実際に、:include オプションを使った場合と :joins オプションを使った場合で試してみます。コメントで書かれている SQL が実際に発行される SQL です。
@blogs = Blog.all(:include => :articles) # SELECT * FROM `blogs` # SELECT `articles`.* FROM `articles` WHERE (`articles`.blog_id IN (1,2,3,4,5,6,....)) @blogs = Blog.all(:joins => :articles) # SELECT `blogs`.* FROM `blogs` INNER JOIN `articles` ON articles.blog_id = blogs.id
どうですか? :joins の方が予想しやすい SQL が発行されているかなと思いますが、注意すべき二つの違いがあります。
1) 先読み(eager reading)されるのは :include の場合だけ
2) そもそも発行される SQL が全然違う!!:include は単純な JOIN じゃない
1) は今までも何となく知っていました。関連テーブルを先に読むのが :include 、必要になったら読むのが :joins です(よね?)。:joins は関連のテーブルを条件としてデータを引っ張ってくるとき(関連テーブルのデータ自体は取得する必要が無いとき)に使うのでしょう。
このように関連テーブルのデータを読み込む場合、:include だと SQL は発行されません(先読みしているため)が、:joins の場合はその都度、SQL が発行されます。
<% @blogs.each do |blog| -%> <%= blog.articles.each do |article| -%> <%= article.name %> <% end -%> <% end -%>
が、2) はびっくりしました。全然発行される SQL が違うんですね。以前の ActiveRecord では :include は LEFT JOIN で、:joins が INNER JOIN ていう違いだった気がする。。
※ ActiveRecord 2.1 以前はそうだったようです→ eager loadingって何?
これは要注意な気がします。例えばこんな感じで LIMIT を付けた場合
@blogs = Blog.all(:include => :articles, :limit => 15) # SELECT * FROM `blogs` LIMIT 15 # SELECT `articles`.* FROM `articles` WHERE (`articles`.blog_id IN (1,2,3,4,5,6,....,15)) @blogs = Blog.all(:joins => :articles, :limit => 15) # SELECT `blogs`.* FROM `blogs` INNER JOIN `articles` ON articles.blog_id = blogs.id LIMIT 15
:include では blogs テーブルから15個のデータを取得して(id:1~15)、それに対応する articles テーブルのデータを取ってきます。それに対して :joins の場合は INNER JOIN した結果を15個取ってきます。この違いは怖い。。
もうちょっと実用的な例として :conditions で関連テーブルでの条件を付けてみます。その場合はこんな感じになりました(ちょっと見にくいですが)。:conditions で関連テーブルを指定すればどちらも同じ結果が取得できるようですね。ひと安心。
@blogs = Blog.all(:include => :articles, :conditions => {:articles => {:title => "title_1"}}, :limit => 15) # SELECT `blogs`.* FROM `blogs` INNER JOIN `articles` ON articles.blog_id = blogs.id WHERE (`articles`.`title` = 'title_1') LIMIT 15 @blogs = Blog.all(:joins => :articles, :conditions => {:articles => {:title => "title_1"}}, :limit => 15) # SELECT # DISTINCT `blogs`.id # FROM # `blogs` LEFT OUTER JOIN `articles` ON articles.blog_id = blogs.id # WHERE # (`articles`.`title` = 'title_1') LIMIT 15 # SELECT # `blogs`.`id` AS t0_r0, `blogs`.`name` AS t0_r1, `blogs`.`created_at` AS t0_r3, `blogs`.`updated_at` AS t0_r4, `articles`.`id` AS t1_r0, `articles`.`title` AS t1_r1, `articles`.`blog_id` AS t1_r2, `articles`.`created_at` AS t1_r4, `articles`.`updated_at` AS t1_r5 # FROM # `blogs` LEFT OUTER JOIN `articles` ON articles.blog_id = blogs.id # WHERE (`articles`.`title` = 'title_1') AND `blogs`.id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
何はともあれ、:include で発行される SQL は直感的じゃないと思うので、どのような SQL が発行されているのかには気を付けたいところです!
ところで INNER JOIN で先読みさせたいときはどうしたらいいんでしょうか。。