【Rails】 豊富なサンプルコードでselectメソッドを理解する!

Rails

selectメソッドとは、取得したい列を指定することが出来るメソッドです。

selectメソッドとは、取得したい列を指定することが出来るメソッドです。

selectメソッドの基本構文
1
Model.all.select(:取得したい列)

selectメソッドを使用しない場合と使用した場合は、以下のように異なります。

コンソール | selectメソッドを使用しない場合 (Catsテーブルの全てのレコードを取得)
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">]>
コンソール | 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: "ミー">]>

Cat.allを実行した場合は、catsテーブルから全ての列のレコードを取得していますが、selectメソッドを使用する事でname列のレコードを指定して取得する事が出来ています。

また、後述しますがallメソッドは省略する事が出来ます。

selectメソッドの基礎知識

この章では、selectメソッドの基本的な使い方や発行されるSQLについて解説します。

selectメソッドの基本的な使い方
リンクをコピーしました

ActiveRecordでは、Model.findやModel.all等を実行するとデフォルトで全ての列を取得する「SELECT * FROM」を発行します。本来なら必要のない列まで無条件に取り出してしまい、メモリの無駄遣いになります。

これを解決するために、必要な列だけ取得する事ができるselectメソッドを使います。今回はownersテーブルとcatsテーブルを例にselectメソッドについて確認していきます。

サンプルコードのテーブル

OwnerモデルとCatモデルのアソシエーション
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

アソシエーションが分からない方は、【Rails】図解形式で理解する《アソシエーション解説書》を参考にしてください。

selectメソッドを使わない場合のSQL
リンクをコピーしました

selectメソッドを使わないで、catsテーブルから全てのレコードを取得する為にallメソッドを使って「Cat.all」を実行します。

コンソール | Catsテーブルの全てのレコードを取得
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は図の方では省略しています。)

catsテーブルの全ての列を取得する

そして、このコードで発行されているSQLにも注目してください。SQLでは、SELECT文で指定しているカラムによって取得する列が決まります。

下記のSELECT文は(cats.*)は、catsテーブルの列を全て取得するという意味です。

コンソール | Cat.allで発行されるSQL
1
SELECT `cats`.* FROM `cats`

ActiveRecordのデフォルトでは、SELECT文(cats.*)の様にSELECT文に「テーブル名.*」を指定し、テーブルから全ての列を取得します。

selectメソッドを使う場合のSQL
リンクをコピーしました

今回取得したいレコードは、catsテーブルのname列のみとします。そうすると、先ほどのSQL文では、name列を含んだ全ての列を取得してしまっています。つまり、不必要な列のレコードまで取得しているのです。

Cat.allのSQL文

その様な場合にselectメソッドを使う事によって、必要な列を指定する事が出来ます。取得したい列はnameになるので、selectメソッドの引数に:nameを指定します。

コンソール | 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列のみのレコードを全て取得することが出来ました。

name列だけをcatsテーブルから取得

そして、このコードで発行されているSQLにも注目してください。SQLのSELECT文が先ほどのcats.*からcats.nameに変更されていることが分かります。

コンソール | Cat.all.select(:name)で発行されるSQL
1
SELECT `cats`.`name` FROM `cats`

このSQLによって「catsテーブルの全ての列のレコードを取得する」から「catsテーブルのname列のレコードを取得する」という内容に変更されました。

また、allメソッドは下記の様に省略することが出来ます。 allメソッドを省略した場合も同じSQLを発行している事が確認出来ます。

コンソール | allメソッドを省略する
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メソッドで取得した列以外にアクセスする場合は、下記の様にエラーが起きてしまうので注意して下さい。

コンソール | 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メソッドで取得していない場合でも、例外を発生しませんので、更に注意が必要です。

コンソール | selectメソッドで取得した列以外のidメソッドにアクセスした場合
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も指定しましょう。

selectメソッドに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: "クロ"> # 返り値

selectメソッドの応用的な使い方

この章では、selectメソッドの応用的な使い方について解説します。

他のメソッドと併用して使用する場合
リンクをコピーしました

selectメソッドは、allメソッドだけではなく、findメソッドfind_byメソッドwhereメソッド等と併用して使う事が出来ます。また、重複のない一意のレコードを取り出すdistinctメソッドも使う事が出来ます。

それでは、サンプルコードを確認していきましょう。

findメソッドとfind_byメソッド
リンクをコピーしました

先ほどのallメソッドでは、全てのレコードのname列のみを取得していましたが、catsテーブルのid=1のname列のレコードが欲しい場合は、selectメソッドとfindメソッドで下記の様に定義する事が出来ます。

コンソール | id=1のname列のレコードを取得する
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列のレコードのみを取得する事が出来ています。

findメソッドとselectメソッド

発行されているSQLを確認すると、SELECT文ではcats.nameの様にcatsテーブルのname列を取得する指定になっています。

コンソール | Cat.select(:name).find(1)で発行されるSQL
1
SELECT  `cats`.`name` FROM `cats`  WHERE `cats`.`id` = 1 LIMIT 1

そして、findメソッドやfind_byメソッドは、ActiveRecord::Relationオブジェクトではなく、モデルのインスタンスを返すメソッドです。今回で言うと、Catモデルのインスタンスを返します。whereメソッドは、ActiveRecord::Relationオブジェクトを返します。

コンソール | 各メソッドのclassの返り値を確認
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メソッドを呼び出すとエラーが起こってしまいます。

コンソール | findメソッド対してメソッドチェーンを使った場合
1
2
Cat.find(1).select(:name)
NoMethodError: private method `select' called for #<Cat:0x007fbf6f17aa30>

find_byメソッドを使用する場合も同様のことが起こるので、findメソッドとfind_byメソッドにselectメソッドを使う際は順番に気をつけてください。

find_byメソッドを正しく使った場合
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メソッドで定義することが出来ます。

コンソール | owner_id=1のレコードの条件を抽出したレコードのname列を取得する
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列のレコードのみを取得する事が出来ています。

owner_id=1のname列のレコードのみを取得する事が出来る

発行されているSQLを確認すると、SELECT文ではcats.nameの様にcatsテーブルのname列を取得する指定になっています。whereメソッドで指定したowner_id=1もSQLのWHERE句以降で定義されている事が分かります。

コンソール | Cat.where(owner_id: 1).select(:name)で発行されるSQL
1
SELECT `cats`.`name` FROM `cats`  WHERE `cats`.`owner_id` = 1

whereメソッドは、ActiveRecord::Relationオブジェクトを返すメソッドです。下記の様にwhereメソッドの返り値に対して、メソッドチェーンを使ってselectメソッドを呼び出す事が出来ます。

コンソール | owner_id=1のレコードの条件を抽出したレコードのname列を取得する
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テーブルに1レコードを追加

catsテーブルに対してselectメソッドでname列のレコードを取得するには、下記の様に定義しました。

catsテーブルの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メソッドを下記の様に定義します。

name列の値を一意で取得する
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文に注目してください。

SQL | Cat.select(:name).distinctで発行されるSQL
1
SELECT DISTINCT `cats`.`name` FROM `cats`

SELECT文の後にDISTINCT文が追加されています。これは、重複した列を1行のみを出力する様にしてくれます。つまり、catsテーブルからname列の重複のない値を取得しているのです。

そして、下記の様にdistinct(false)で重複の有無に関わらず全ての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メソッドを呼び出す事が出来ます。

name列の値を一意で取得する
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('テーブル名.列名')の様に文字列で指定します。

コンソール | 内部結合したテーブルの取得列をselectメソッドで指定する
1
Owner.joins(:cats).select('owners.*, cats.name')

こちらのコードの詳しい解説は、selectメソッドで取得するレコードを変更する方法を参照してください。

selectメソッドとpluckメソッド
リンクをコピーしました

selectメソッドと同じSQLを発行するpluckメソッドがあります。下記のコードを比較すると、どちらも「catsテーブルのname列のレコードを取得するという同じSQL」を発行しています。

selectメソッドを使った場合のSQL
1
2
Cat.select(:name)
SELECT `cats`.`name` FROM `cats`
pluckメソッドを使った場合のSQL
1
2
Cat.pluck(:name)
SELECT `cats`.`name` FROM `cats`

selectメソッドと同一のSQLを発行するpluckメソッドですが、2つのメソッドの異なる点を詳しく確認していきましょう。

selectメソッドとpluckメソッドの異なる点とは?
リンクをコピーしました

selectメソッドとpluckメソッドは、返り値が異なります。

selectメソッドの返り値は、ActiveRecord::Relationオブジェクトでしたが、pluckメソッドの返り値は、配列です。

コンソール | selectメソッドの返り値
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: "クロ">]>
コンソール | pluckメソッドの返り値
1
2
3
Cat.pluck(:name)

=> ["クロ", "モモ", "ハナ", "ミー", "クロ"] # 返り値

selectメソッドで取得したデータを配列に整形して入れ直す場合は、pluckメソッドの方がコードをシンプルにすることが出来ます。上記のコードをselectメソッドにした場合は、下記の様に冗長になってしまいます。

コンソール | pluckメソッドを使わないでselectメソッドを使った場合
1
2
3
Cat.select(:name).map { |cat| cat.name }

=> ["クロ", "モモ", "ハナ", "ミー", "クロ"] # 返り値

mapメソッドについては、mapメソッドの使い方を参考にして下さい。

pluckメソッドの注意点
リンクをコピーしました

pluckメソッドはコードをシンプルにしてくれる便利なメソッドになりますが、注意しなければいけない点が3点ほどあります。

  1. 取得したデータを使ってテーブルへの更新処理などを行う事が出来ない
  2. pluckメソッドにはメソッドチェーンを使ってメソッドを呼び出す事が出来ない
  3. pluckメソッドはその都度SQLを発行するので場合によってはN+1の様な状態も起きてしまう可能性がある

pluckメソッドは、selectメソッドの返り値(ActiveRecord::Relationオブジェクト)と違い配列が返ってきます。その為、取得したデータを使ってテーブルへの更新処理などを行う事が出来ないので注意が必要です。

更には、pluckメソッドにはメソッドチェーンを使ってメソッドを呼び出す事が出来ません。なぜなら返り値はActiveRecord::Relationオブジェクトではなく、配列だからです。pluckメソッドに対してメソッドチェーンを使った場合は、下記の様にエラーが起きます。

コンソール | 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のエラーが起きるので注意して下さい。