joins オプションとinclude オプションの決定的な違い

railsのfindメソッドのincludeオプションとjoinsオプションの違いに付いてまとめてみた

  • DB構成
mysql> DESCRIBE users;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     |      | PRI | NULL    | auto_increment |
| name  | varchar(40) | YES  |     |         |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> DESCRIBE diaries;
+---------+---------+------+-----+---------+----------------+
| Field   | Type    | Null | Key | Default | Extra          |
+---------+---------+------+-----+---------+----------------+
| id      | int(11) |      | PRI | NULL    | auto_increment |
| user_id | int(11) |      | MUL | 0       |                |
| body    | text    | YES  |     |         |                |
+---------+---------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> DESCRIBE comments;
+----------+---------+------+-----+---------+----------------+
| Field    | Type    | Null | Key | Default | Extra          |
+----------+---------+------+-----+---------+----------------+
| id       | int(11) |      | PRI | NULL    | auto_increment |
| user_id  | int(11) |      | MUL | 0       |                |
| diary_id | int(11) |      | MUL | 0       |                |
| body     | text    | YES  |     |         |                |
+----------+---------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
  • リレーション
class User < ActiveRecord::Base
  has_many :diaries
  has_many :comments
end

class Diary < ActiveRecord::Base;
  belongs_to :user
  has_many :comments
end

class Comment < ActiveRecord::Base;
  belongs_to :user
  belongs_to :diary
end

joins

User.find(:all, :joins => "LEFT OUTER JOIN diaries ON diaries.user_id = users.id", :conditions => ["users.id = ?", "1"])
  • 発行される SQL
SELECT * FROM users LEFT OUTER JOIN diaries ON diaries.student_id = users.id WHERE users.id = 1
    • SELECT句には*が発行される
    • FROMの後にjoinsで指定した文字列が発行される
  • レスポンス
[#<User:0xb6f61e0c @attributes={"id"=>"2", "name"=>"太郎", "user_id"=>"1", "body"=>"joins オプションとinclude オプションの決定的な違い"}>]
    • Userモデルのオブジェクトの属性"@attributes"として全カラムが格納されて返ってくる
    • idカラムの値が上書きされている

include

User.find(:all, :include => [:diaries], :conditions => ["users.id = ?", "1"])
  • 発行される SQL
SELECT
  users.`id` AS t0_r0, users.`name` AS t0_r1, diaries.`id` AS t1_r0, diaries.`user_id` AS t1_r1, diaries.`body` AS t1_r2
FROM
  users
LEFT OUTER JOIN diaries ON diaries.user_id = users.id
WHERE users.id = 1
    • SELECT句での対象とincludeで指定されたテーブルの全カラムに対してエイリアスが貼られている
      • エイリアスの貼られ方
        • t*でテーブルの一意性を示している
        • r*でテーブル内のカラムの一意性を示している
    • エイリアスが貼られたカラムのみSELECT句へ格納されている
  • レスポンス
[#<User:0xb6f61e0c @attributes={"id"=>"1", "name"=>"太郎"}, @diarys=[#<Diary:0xb6f61768 @attributes={"id"=>"2", "user_id"=>"1", "body"=>"joins オプションとinclude オプションの決定的な違い"}>]>]
    • Userクラスのオブジェクトの属性"@attributes"には、usersテーブルのカラムが格納され、"@diarys"には、Diaryクラスのオブジェクトが格納され、その中には、diariesテーブルのカラムが格納されている

二つのオプションの決定的な違い

  • include
    • 参照をキャッシュして保存している
    • SQL問い合わせ後、関連先のオブジェクトとして準備してくれる
  • joins
    • 問い合わせて帰ってきたカラムが全てUserクラスのオブジェクトに入っている
    • つまり、単にFROM句とWHERE句の間に発行されるSQLを追加するだけ
    • しかも、idは後から取得した2に上書きされていることからもわかるとおり、問い合わせの結果同じカラム名が返ってくる場合は、エイリアスをselectオプションでしてしなければならない
  • 併用
    • includeとjoinsは併用が可能
    • 注意するべき点
    1. joinsはSELECT句を生成しない
    2. includeでエイリアスが貼られてSELECT句に格納されるのは、findの対象とincludeで指定されたテーブルのみ
    3. つまり、joinsとincludeを併用した場合、joinsで指定して、JOINしたテーブルのカラムはSELECTされない
  • 使い分け
    • 実務上、includeがとても便利である
    • joinsは、そのテーブルのカラムの情報自体は不要であるが、検索条件に追加したい場合などに指定すると良い
      • 例えば、
User.find(
  :all,
  :joins => "LEFT OUTER JOIN comments ON comments.user_id = users.id",
  :include => [:diaries],
  :conditions => ["users.id = ? AND comments.body LIKE ?", "1", "%Rails%"]
)
      • 発行される SQL
SELECT
  users.`id` AS t0_r0, users.`name` AS t0_r1, diaries.`id` AS t1_r0, diaries.`user_id` AS t1_r1, diaries.`body` AS t1_r2
FROM
  users
LEFT OUTER JOIN diaries ON diaries.user_id = users.id
LEFT OUTER JOIN comments ON comments.user_id = users.id
WHERE
  users.id = 1 AND
  comments.body LIKE '%Rails%'


最後までお読みいただきありがとうございました。