更新日:
【Rails】 豊富なサンプルコードでselectメソッドを理解する!
selectメソッドとは、取得したい列を指定することが出来るメソッドです。
selectメソッドとは、取得したい列を指定することが出来るメソッドです。
1
Model.all.select(:取得したい列)
selectメソッドを使用しない場合と使用した場合は、以下のように異なります。
1
2
3
4
5
6
7
8
Cat.all
SELECT `cats`.* FROM `cats`
=> #<ActiveRecord::Relation [ #返り値
#<Cat id: 1, name: "クロ", owner_id: 2, created_at: "2019-11-25 08:53:31", updated_at: "2019-11-25 08:53:31">,
#<Cat id: 2, name: "モモ", owner_id: 1, created_at: "2019-11-25 08:53:43", updated_at: "2019-11-25 08:53:43">,
#<Cat id: 3, name: "ハナ", owner_id: 3, created_at: "2019-11-25 08:53:59", updated_at: "2019-11-25 08:53:59">,
#<Cat id: 4, name: "ミー", owner_id: 1, created_at: "2019-11-25 08:54:39", updated_at: "2019-11-25 08:54:39">]>
1
2
3
4
5
6
7
Cat.all.select(:name)
SELECT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "クロ">,
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">]>
Cat.all
を実行した場合は、catsテーブルから全ての列のレコードを取得していますが、selectメソッドを使用する事でname列のレコードを指定して取得する事が出来ています。
※後述しますが、allメソッドは省略する事が出来ます。
selectメソッドの基礎知識
この章では、selectメソッドの基本的な使い方や発行されるSQLについて解説します。
selectメソッドの基本的な使い方
ActiveRecordでは、Model.findやModel.all等を実行するとデフォルトで全ての列を取得する「SELECT * FROM」を発行します。本来なら必要のない列まで無条件に取り出してしまい、メモリの無駄遣いになります。
これを解決するために、必要な列だけ取得する事ができるselectメソッドを使います。今回はownersテーブルとcatsテーブルを例にselectメソッドについて確認していきます。
1
2
3
4
5
6
7
8
9
# owner.rb
class Owner < ActiveRecord::Base
has_many :cats
end
# cat.rb
class Cat < ActiveRecord::Base
belongs_to :owner
end
アソシエーションが分からない方は、「アソシエーションを図解形式で理解しよう!」を参考にしてください。
selectメソッドを使わない場合のSQL
selectメソッドを使わないで、catsテーブルから全てのレコードを取得する為にallメソッドを使って「Cat.all」を実行します。
1
2
3
4
5
6
7
8
Cat.all
SELECT `cats`.* FROM `cats`
=> #<ActiveRecord::Relation [ #返り値
#<Cat id: 1, name: "クロ", owner_id: 2, created_at: "2019-11-25 08:53:31", updated_at: "2019-11-25 08:53:31">,
#<Cat id: 2, name: "モモ", owner_id: 1, created_at: "2019-11-25 08:53:43", updated_at: "2019-11-25 08:53:43">,
#<Cat id: 3, name: "ハナ", owner_id: 3, created_at: "2019-11-25 08:53:59", updated_at: "2019-11-25 08:53:59">,
#<Cat id: 4, name: "ミー", owner_id: 1, created_at: "2019-11-25 08:54:39", updated_at: "2019-11-25 08:54:39">]>
実行後の返り値を見て分かる様に、catsテーブルのid,name,owner_idの列を含めたレコードを全て取得出来ている事が確認出来ます。(※created_at, updated_atは図の方では省略しています。)
そして、このコードで発行されているSQLにも注目してください。SQLでは、SELECT文で指定しているカラムによって取得する列が決まります。
下記のSELECT文は(cats.*
)は、catsテーブルの列を全て取得するという意味です。
1
SELECT `cats`.* FROM `cats`
ActiveRecordのデフォルトでは、SELECT文(cats.*
)の様にSELECT文に「テーブル名.*
」を指定し、テーブルから全ての列を取得します。
selectメソッドを使う場合のSQL
今回取得したいレコードは、catsテーブルのname列のみとします。そうすると、先ほどのSQL文では、name列を含んだ全ての列を取得してしまっています。つまり、不必要な列のレコードまで取得しているのです。
その様な場合にselectメソッドを使う事によって、必要な列を指定する事が出来ます。取得したい列はnameになるので、selectメソッドの引数に:nameを指定します。
1
2
3
4
5
6
7
Cat.all.select(:name)
SELECT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "クロ">,
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">]>
実行後の返り値からも分かる様に、selectメソッドで:nameを指定したことによって、name列のみのレコードを全て取得することが出来ました。
そして、このコードで発行されているSQLにも注目してください。SQLのSELECT文が先ほどのcats.*
からcats.name
に変更されていることが分かります。
1
SELECT `cats`.`name` FROM `cats`
このSQLによって「catsテーブルの全ての列のレコードを取得する」から「catsテーブルのname列のレコードを取得する」という内容に変更されました。
また、allメソッドは下記の様に省略することが出来ます。 allメソッドを省略した場合も同じSQLを発行している事が確認出来ます。
1
2
3
4
5
6
7
8
Cat.select(:name)
SELECT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "クロ">,
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">]>
また、selectメソッドはActiveRecord::Relationオブジェクトを返すメソッドという事を確認する事が出来ました。後述しますが、ActiveRecord::Relationオブジェクトに対してメソッドチェーンを使って他のメソッドを呼び出す事が出来ます。
selectメソッドの注意点
selectメソッドで取得した列以外にアクセスする場合は、下記の様にエラーが起きてしまうので注意して下さい。
1
2
3
4
5
6
7
8
9
10
11
cat_name = Cat.select(:name).find(1)
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`id` = 1 LIMIT 1
=> #<Cat id: nil, name: "クロ">
# selectメソッドで取得した列
cat_name.name
=> "クロ"
# 取得した列以外のアクセス
cat_name.owner_id
ActiveModel::MissingAttributeError: missing attribute: owner_id
idはselectメソッドで取得していない場合でも、例外を発生しませんので、更に注意が必要です。
1
2
3
4
5
6
7
cat_name = Cat.select(:name).find(1)
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`id` = 1 LIMIT 1
=> #<Cat id: nil, name: "クロ">
# idメソッドにアクセス
cat_name.id
=> nil
上記の様にidメソッドにアクセスするとnilが返ってきます。idが必要な場合は、下記の様にidも指定しましょう。
1
2
3
4
Cat.select(:id, :name).find(1)
SELECT `cats`.`id`, `cats`.`name` FROM `cats` WHERE `cats`.`id` = 1 LIMIT 1
=> #<Cat id: 1, name: "クロ"> # 返り値
Rails(O/Rマッパー)を使用していると、SQLの知識は要らないと思われがちですが、「無駄なクエリの発行、データの引き出しの間違え」は、SQLで確認できるのです!
他にもパフォーマンス・デバック・複雑な操作の際にもSQLの知識は必要になります。
SQLの知識がなく、「データベースの操作だけできれば良い」と考えているとプログラムの品質や信頼を失ってしまいます。
SQLについて不安がある方は、基礎から一通り学べる以下の参考書を利用すると良いでしょう。
発売から数年で不動の定番テキストとなった大人気SQL入門書に、最新のDBに対応した改訂版が登場!
selectメソッドの応用的な使い方
この章では、selectメソッドの応用的な使い方について解説します。
他のメソッドと併用して使用する場合
selectメソッドは、allメソッドだけではなく、findメソッドやfind_byメソッド、whereメソッド等と併用して使う事が出来ます。また、重複のない一意のレコードを取り出すdistinctメソッドも使う事が出来ます。
それでは、サンプルコードを確認していきましょう。
findメソッドとfind_byメソッド
先ほどのallメソッドでは、全てのレコードのname列のみを取得していましたが、catsテーブルのid=1のname列のレコードが欲しい場合は、selectメソッドとfindメソッドで下記の様に定義する事が出来ます。
1
2
3
4
Cat.select(:name).find(1)
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`id` = 1 LIMIT 1
=> #<Cat id: nil, name: "クロ"> # 返り値
実行後の返り値からも分かる様にid=1のname列のレコードのみを取得する事が出来ています。
発行されているSQLを確認すると、SELECT文ではcats.name
の様にcatsテーブルのname列を取得する指定になっています。
1
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`id` = 1 LIMIT 1
そして、findメソッドやfind_byメソッドは、ActiveRecord::Relationオブジェクトではなく、モデルのインスタンスを返すメソッドです。今回で言うと、Catモデルのインスタンスを返します。whereメソッドは、ActiveRecord::Relationオブジェクトを返します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# findメソッド
cat = Cat.find(1)
cat.class
=> Cat(id: integer, name: string, owner_id: integer, created_at: datetime, updated_at: datetime)
# find_byメソッド
cat = Cat.find_by(name: 'クロ')
cat.class
=> Cat(id: integer, name: string, owner_id: integer, created_at: datetime, updated_at: datetime)
# whereメソッド
cat = Cat.where(name: 'クロ')
cat.class
=> Cat::ActiveRecord_Relation
メソッドチェーンは、「ActiveRecord::Relationオブジェクトを返すメソッド」にしか使う事が出来ません。
その為、findメソッドに対してメソッドチェーンを使ってselectメソッドを呼び出すとエラーが起こってしまいます。
1
2
Cat.find(1).select(:name)
NoMethodError: private method `select' called for #<Cat:0x007fbf6f17aa30>
find_byメソッドを使用する場合も同様のことが起こるので、findメソッドとfind_byメソッドにselectメソッドを使う際は順番に気をつけてください。
1
2
3
4
Cat.select(:name).find_by(name: 'クロ')
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`name` = 'クロ' LIMIT 1
=> #<Cat id: nil, name: "クロ"> #返り値
メソッドチェーンについては、メソッドチェーンでfind_byメソッドを使う場合を参考にしてください。
whereメソッド
次に、owner_id=1のレコードの条件を抽出したレコードのname列を取得する場合は、下記の様にselectメソッドとwhereメソッドで定義することが出来ます。
1
2
3
4
5
6
Cat.select(:name).where(owner_id: 1)
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`owner_id` = 1
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ミー">]>
実行後の返り値からも分かる様にowner_id=1のname列のレコードのみを取得する事が出来ています。
発行されているSQLを確認すると、SELECT文ではcats.name
の様にcatsテーブルのname列を取得する指定になっています。whereメソッドで指定したowner_id=1もSQLのWHERE句以降で定義されている事が分かります。
1
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`owner_id` = 1
whereメソッドは、ActiveRecord::Relationオブジェクトを返すメソッドです。下記の様にwhereメソッドの返り値に対して、メソッドチェーンを使ってselectメソッドを呼び出す事が出来ます。
1
2
3
4
5
6
Cat.where(owner_id: 1).select(:name)
SELECT `cats`.`name` FROM `cats` WHERE `cats`.`owner_id` = 1
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ミー">]>
先ほどのコードと同じSQLが発行されている事を確認する事が出来ます。
distinctメソッド
selectメソッドは、重複のない一意のレコードを取得するdistinctメソッドも合わせて使う事が出来ます。
例えば、catsテーブルのid=5に新たなレコードを追加します。id=5のname列には、id=1と同じ名前(クロ)が入っています。しかし、owner_idの値がそれぞれ違い、id=1のクロは伊藤さんの飼い猫で、id=5のクロは高橋さんの飼い猫です。
catsテーブルに対してselectメソッドでname列のレコードを取得するには、下記の様に定義しました。
1
2
3
4
5
6
7
8
9
Cat.select(:name)
SELECT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "クロ">, # 重複
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">,
#<Cat id: nil, name: "クロ">]> # 重複
実行後の返り値を確認すると、「name: "クロ"」のレコードが2つ取得されていて、重複している状態です。
今回は、一意のname列の値が欲しいのでdistinctメソッドを下記の様に定義します。
1
2
3
4
5
6
7
8
Cat.select(:name).distinct
SELECT DISTINCT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "クロ">, # 重複していない
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">]>
実行後の返り値を確認すると、「name: "クロ"」のレコードが重複なく1つだけ取得する事が出来ています。
発行されるSQL文に注目してください。
1
SELECT DISTINCT `cats`.`name` FROM `cats`
SELECT文の後にDISTINCT文が追加されています。これは、重複した列を1行のみを出力する様にしてくれます。つまり、catsテーブルからname列の重複のない値を取得しているのです。
そして、下記の様にdistinct(false)で重複の有無に関わらず全てのname列を取得する事も出来ます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
query = Cat.select(:name).distinct
SELECT DISTINCT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [
#<Cat id: nil, name: "クロ">,
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">]>
query.distinct(false) # 重複の有無は関係なくname列を取得
SELECT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [
#<Cat id: nil, name: "クロ">,
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">,
#<Cat id: nil, name: "クロ">]> # 重複している値も取得されている
また、distinctメソッドもActiveRecord::Relationオブジェクトを返すメソッドです。下記の様にメソッドチェーンを使ってselectメソッドを呼び出す事が出来ます。
1
2
3
4
5
6
7
8
Cat.distinct.select(:name)
SELECT DISTINCT `cats`.`name` FROM `cats`
=> #<ActiveRecord::Relation [
#<Cat id: nil, name: "クロ">, # 重複していない
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">]>
先ほどのコードと同じSQLが発行されています。distinctメソッドについて詳しくは、distinctメソッドの使い方を参考にしてください。
複数のテーブルから取得する列を特定する
テーブルを結合した場合もselectメソッドによって、取得する列を指定する事が出来ます。
selectメソッドの引数は、select('テーブル名.列名')の様に文字列で指定します。
1
Owner.joins(:cats).select('owners.*, cats.name')
こちらのコードの詳しい解説は、selectメソッドで取得するレコードを変更する方法を参照してください。
selectメソッドとpluckメソッド
selectメソッドと同じSQLを発行するpluckメソッドがあります。下記のコードを比較すると、どちらも「catsテーブルのname列のレコードを取得するという同じSQL」を発行しています。
1
2
Cat.select(:name)
SELECT `cats`.`name` FROM `cats`
1
2
Cat.pluck(:name)
SELECT `cats`.`name` FROM `cats`
selectメソッドと同一のSQLを発行するpluckメソッドですが、2つのメソッドの異なる点を詳しく確認していきましょう。
selectメソッドとpluckメソッドの異なる点とは?
selectメソッドとpluckメソッドは、返り値が異なります。
selectメソッドの返り値は、ActiveRecord::Relationオブジェクトでしたが、pluckメソッドの返り値は、配列です。
1
2
3
4
5
6
7
8
Cat.select(:name)
=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, name: "クロ">,
#<Cat id: nil, name: "モモ">,
#<Cat id: nil, name: "ハナ">,
#<Cat id: nil, name: "ミー">,
#<Cat id: nil, name: "クロ">]>
1
2
3
Cat.pluck(:name)
=> ["クロ", "モモ", "ハナ", "ミー", "クロ"] # 返り値
selectメソッドで取得したデータを配列に整形して入れ直す場合は、pluckメソッドの方がコードをシンプルにすることが出来ます。上記のコードをselectメソッドにした場合は、下記の様に冗長になってしまいます。
1
2
3
Cat.select(:name).map { |cat| cat.name }
=> ["クロ", "モモ", "ハナ", "ミー", "クロ"] # 返り値
mapメソッドについては、mapメソッドの使い方を参考にして下さい。
pluckメソッドの注意点
pluckメソッドはコードをシンプルにしてくれる便利なメソッドになりますが、注意しなければいけない点が3点ほどあります。
- 取得したデータを使ってテーブルへの更新処理などを行う事が出来ない
- pluckメソッドにはメソッドチェーンを使ってメソッドを呼び出す事が出来ない
- pluckメソッドはその都度SQLを発行するので場合によってはN+1の様な状態も起きてしまう可能性がある
pluckメソッドは、selectメソッドの返り値(ActiveRecord::Relationオブジェクト)と違い配列が返ってきます。その為、取得したデータを使ってテーブルへの更新処理などを行う事が出来ないので注意が必要です。
更には、pluckメソッドにはメソッドチェーンを使ってメソッドを呼び出す事が出来ません。なぜなら返り値はActiveRecord::Relationオブジェクトではなく、配列だからです。pluckメソッドに対してメソッドチェーンを使った場合は、下記の様にエラーが起きます。
1
2
3
4
Cat.pluck(:name).where(owner_id: 1)
SELECT `cats`.`name` FROM `cats`
NoMethodError: undefined method `where' for ["クロ", "モモ", "ハナ", "ミー", "クロ"]:Array
また、pluckメソッドはその都度SQLを発行するので場合によってはN+1の様な状態も起きてしまう可能性があります。N+1については、【Rails】N+1問題を理解しながらincludesメソッドで解決してみよう!を参考にして下さい。
この記事のまとめ
- selectメソッドとは、取得したい列を指定することが出来るメソッドのこと
- 返り値は、ActiveRecord::Relationオブジェクトなのでselectメソッドに対して、メソッドチェーンを使う事が出来る
- selectメソッドで取得した列以外にアクセスする場合は、ActiveModel::MissingAttributeErrorのエラーが起きるので注意が必要!