更新日:
【Rails】 left_joinsメソッドで定義する左外部結合とは?
left_joinsメソッドとは、関連するレコードが有る無しに関わらずレコードのセットを取得してくれるメソッドです。
1
モデル名.left_joins(:関連名)
left_joinsメソッドは、関連するテーブル同士を左外部結合します。引数には、アソシエーションで定義した関連名を渡します。エイリアスにleft_outer_joins
メソッドがあります。
left_joinsメソッドの使い方
この章では、left_joinsメソッドの使い方について解説します。
基本的な使い方
left_joins
メソッドは、結合したテーブルから指定したモデルのデータのみ取得します。下記の「飼い主を管理するownersテーブル」と「飼い猫を管理するcatsテーブル」を結合して確認します。
Ownerモデルを指定してleft_joins
メソッドを使うと、下記の様に結合したテーブルから左側のownersテーブルのデータを取得します。
1
2
3
4
5
6
7
8
9
Owner.left_joins(:cats)
SELECT `owners`.* FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` LIMIT 11
=> #<ActiveRecord::Relation [
#<Owner id: 1, name: "田中", created_at: "2019-12-25 02:43:01", updated_at: "2019-12-25 02:43:01">,
#<Owner id: 1, name: "田中", created_at: "2019-12-25 02:43:01", updated_at: "2019-12-25 02:43:01">,
#<Owner id: 2, name: "伊藤", created_at: "2019-12-25 02:43:15", updated_at: "2019-12-25 02:43:15">,
#<Owner id: 3, name: "高橋", created_at: "2019-12-25 02:43:20", updated_at: "2019-12-25 02:43:20">,
#<Owner id: 4, name: "加藤", created_at: "2019-12-25 02:43:23", updated_at: "2019-12-25 02:43:23">]>
また後述しますが、左側のテーブルを基準に結合することを左外部結合と言います。
左外部結合は、上記のピンク色の部分の「条件(cats.owner_id = owners.id
)が一致したデータ」と緑色の部分の「基準となる左のテーブル(owners)のデータ」を取得します。
left_joinsメソッドの引数
left_joins
メソッドの引数には、アソシエーションで定義した関連名を指定します。
先ほどのownersテーブルとcatsテーブルのアソシエーションは下記の通りです。(飼い主は沢山の猫を飼っているが、猫は1人の飼い主に属す関係)
1
2
3
4
5
6
7
8
9
# owner.rb
class Owner < ApplicationRecord
has_many :cats # 関連名
end
# cat.rb
class Cat < ApplicationRecord
belongs_to :owner # 関連名
end
catsテーブルを左側の基準にしてownersテーブルと結合する場合は、Cat.left_joins(:owner)
と定義しますが、下記の様に間違ってテーブル名:owners
を渡してしまうとActiveRecord::ConfigurationError
が発生します。
1
2
3
Cat.left_joins(:owners)
Traceback (most recent call last):
ActiveRecord::ConfigurationError (Can't join 'Cat' to association named 'owners'; perhaps you misspelled it?)
left_joins
メソッドの引数は、テーブル名ではなくアソシエーションの関連名を渡す事に注意してください。
条件を指定する方法
left_joins
メソッドでテーブルを左外部結合する際に、whereメソッドで条件指定する事が出来ます。where
メソッドのカラム名は、eft_joinsメソッドで指定したモデルのカラムを指します。
1
モデル名.left_joins(:関連名).where(カラム名: 値)
ownerモデルにleft_joins
メソッドでcatsテーブルと左外部結合する際に、飼い主の名前が高橋さんという条件where(name: '高橋')
を追加すると、下記の様に結合したテーブルからwhereメソッドで指定した条件を抽出している事が確認出来ます。
1
2
3
Owner.left_joins(:cats).where(name: '高橋')
SELECT `owners`.* FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` WHERE `owners`.`name` = '高橋' LIMIT 11
=> #<ActiveRecord::Relation [#<Owner id: 3, name: "高橋", created_at: "2019-12-25 02:43:20", updated_at: "2019-12-25 02:43:20">]>
また、catsテーブルの様な結合先のテーブルに対して条件を指定する場合は、下記の様にwhere(結合先のテーブル名: { カラム名: 値 })
で指定します。
1
モデル名.left_joins(:関連名).where(結合先のテーブル名: { カラム名: 値 })
それでは、猫を飼っていないオーナーを取得してみます。猫を飼っていないオーナーとは、結合先のcatsテーブルのowner_idカラムがnilになっているレコードが該当するので、下記の様にwhere
メソッドで条件を指定します。
1
2
3
4
5
Owner.left_joins(:cats).where(cats: { owner_id: nil })
SELECT `owners`.* FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` WHERE `cats`.`owner_id` IS NULL LIMIT 11
=> #<ActiveRecord::Relation [ # 返り値
#<Owner id: 4, name: "加藤", created_at: "2019-12-25 02:43:23", updated_at: "2019-12-25 02:43:23">]>
返り値を確認すると、猫を飼っていない加藤さんのデータを取得することが出来ています。また、Ownerモデルに対してleft_joins
メソッドを指定したので、ownerモデルのデータしか取得されていないことも確認できます。
joinsメソッドと同じ指定方法なので、詳しくはwhereメソッドのカラム指定方法2を参考にしてください。
結合先のデータを取得する方法
left_joins
メソッドは、デフォルトでは指定したモデルのデータしか取得しません。下記は、Ownerモデルがleft_joins
メソッドに指定されている為Ownerモデルのデータしか取得されません。
指定したモデルのデータだけではなく、結合先のデータを表示させるには、selectメソッドを併用します。下記の様に文字列を使ってselect('テーブル名.カラム名')
と指定する事で、結合先のデータを取得する事が出来ます。
1
モデル名.left_joins(:関連名).select('テーブル名.カラム名')
それでは、select
メソッドで「Ownerモデルの全てのデータと結合先のcatsテーブルのspeciesカラムのデータ」を取得していきます。
1
2
Owner.left_joins(:cats).select('owners.*, cats.species')
SELECT owners.*, cats.name FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` LIMIT 11
下記の様に、selectメソッドで指定したカラムのデータを取得する事が出来ています。試しに上から2番目の田中さんの飼い猫の種類を取得します。
1
2
3
4
Owner.left_joins(:cats).select('owners.*, cats.species').first.species
Owner Load (1.1ms) SELECT owners.*, cats.species FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` ORDER BY `owners`.`id` ASC LIMIT 1
=> "スコティッシュ・フォールド"
select
メソッドを使う事でデフォルトの指定モデルだけではなく結合先のデータも取得する事が出来ます。
SQLで理解する左外部結合
この章では、左外部結合についてSQLとテーブルを使って解説します。
左外部結合とは?
左外部結合とは、外部結合の結合方式の1つです。結合条件に従って複数のテーブルを1つのテーブルとして結合しますが、その際に「結合条件に一致するレコード」と「左側のテーブルにしかないレコード」を取得します。
ここからはleft_joins
メソッドの裏側で発行される左外部結合のSQL
について、サンプルコードとテーブルを使いながら詳しく解説していきます。
RailsはSQL
を特に意識する事なく使えますが、コードの裏側で発行されるSQLが分かると、メソッドについてより深く理解する事が出来ます。
左外部結合の特徴
左外部結合は、「左側のテーブルにしかないレコードを取得して結合する」という大きな特徴があります。
Ownerモデルを指定してleft_joins
メソッドを使うと、下記の様に加藤さんは猫を飼っていませんが、結合先のcatsテーブルの値が有る無しに関わらず結合します。
このコードで発行されるSQL文は、下記の通りです。2行目にあるLEFT OUTER JOIN
句に注目してください。
1
2
3
SELECT owners.* /* 取得するカラム */
FROM owners LEFT OUTER JOIN cats /* ownersテーブルを左側の基準にしてcatsテーブルと結合する */
ON cats.owner_id = owners.id LIMIT 11 /* テーブル結合条件 */
このLEFT OUTER JOIN
句の左側にあるownersテーブルの全てのデータを抽出して、これを基準にON
句以降の結合条件で右側のcatsテーブルをチェックして一致するレコードを結合させています。
ownersテーブルの全てのデータを基準にして結合条件に一致すれば、catsテーブルのレコードが結合されますが、上記の加藤さんの様に条件に一致せず結合出来なかったとしてもレコードにNULLが入って結合されます。
そして最後に、SELECT文で指定したカラムのデータを結合されたテーブルから取得します。今回はSELECT owners.*
となっているので、下記の様にownersテーブルの全てのカラムのデータを取得します。
また、加藤さんのcatsテーブルと結合されたレコードにNULLが入ったのは、ownersテーブルのデータが基準となって結合しているからです。もし、ownersとcatsを反対にしてcats LEFT OUTER JOIN owners
にした場合は、下記の様にcatsテーブルの全てのデータを抽出してownersテーブルに結合条件に一致するレコードをチェックしにいきます。
今回は、catsテーブルに条件に一致しないレコードは無かったので結合結果は下記の様になります。
catsテーブルのデータを基準にして結合条件をチェックしているので、加藤さんのレコードはもちろん存在しません。
内部結合と左外部結合の違い
左外部結合は、条件に一致したレコードに加えて左のテーブルにしかないデータも結合しますが、左のテーブルにしか無いデータが必要な場面とは、一体どの様な場面なのでしょうか?
先ほどのownersテーブルを基準にしてcatsテーブルと結合した時は、左のテーブル(owners)にしか無い加藤さんのデータも結合されてました。
左のテーブルにしか無いということは、加藤さんには飼い猫はいません。ownersテーブルを基準にcatsテーブルを左外部結合すると「猫を飼っているオーナー」と「猫を飼っていないオーナー」の2種類のオーナーを取得することが出来ます。
結合したcatsテーブルのowner_idがNULLの場合は、猫を飼っていないownerになるので、下記の様にselect
メソッドで結合先のcatsテーブルのidカラムのデータを取得します。そして、猫を飼っているか飼っていないかで処理を分ける事が出来ます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
owners = Owner.left_joins(:cats).select('owners.*, cats.owner_id')
SELECT owners.*, cats.owner_id FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` LIMIT 11
owners.each do |owner|
if owner.owner_id
p "#{owner.name}さんは猫を飼っています"
else
p "#{owner.name}さんは猫を飼っていません"
end
end
# 出力結果
"田中さんは猫を飼っています"
"田中さんは猫を飼っています"
"伊藤さんは猫を飼っています"
"高橋さんは猫を飼っています"
"加藤さんは猫を飼っていません"
これが内部結合になると、下記の様に結合条件に一致するレコードのみを結合します。
つまり、内部結合すると「猫を飼っているオーナー」のデータしか取得する事が出来ないのです。
猫を飼っていないオーナーのみを取得するには、下記の様にLEFT OUTER JOIN
句で左外部結合して、where
メソッドで結合先のcatsテーブルのowner_idカラムがnilのデータを抽出します。
1
2
3
4
5
Owner.left_joins(:cats).where(cats: { owner_id: nil })
SELECT `owners`.* FROM `owners` LEFT OUTER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id` WHERE `cats`.`owner_id` IS NULL LIMIT 11
=> #<ActiveRecord::Relation [ # 返り値
#<Owner id: 4, name: "加藤", created_at: "2019-12-25 02:43:23", updated_at: "2019-12-25 02:43:23">]>
返り値は、猫を飼っていない加藤さんのデータを取得する事が出来ています。
SQLの知識の必要性
Rails(O/Rマッパー)を使用していると、SQLの知識は要らないと思われがちですが、「無駄なクエリを発行していないか、データの引き出しを間違えていないか」は、裏側で発行されるSQLで確認できます。
他にもパフォーマンス・デバック・複雑な操作の際にもSQLの知識は必要になります。
SQLの知識がなく、「データベースの操作だけできれば良い」と考えているとプログラムの品質や信頼を失ってしまいます。
SQLについて不安がある方は、基礎から一通り学べる以下の参考書を利用すると良いでしょう。
発売から数年で不動の定番テキストとなった大人気SQL入門書に、最新のDBに対応した改訂版が登場!
この記事のまとめ
- 左外部結合とは、結合条件に一致するレコードに加えて左側の軸となるテーブルのレコードを結合する方法のこと
- Railsで左外部結合をするには、left_joinsメソッドで定義する
- 引数には、テーブル名ではなくアソシエーションで定義した関連名を渡す