【Rails】distinctメソッドでユニークなデータを取得する方法

Rails

distinctメソッドとは、データベースから取得した重複するレコードを削除してくれるメソッドです。

distinctメソッドの構文
1
モデル名.select(:取得列).distinct

distinctメソッドを使う事で、重複のない一意のレコードを取得することが出来ます。

※distinctメソッドのエイリアスにuniqメソッドがありますが、このメソッドはRails 5.0では廃止され、Rails5.1から削除されているので注意してください。

distinctメソッドの使い方

この章では、distinctメソッドの使い方について1つ1つ解説します。

distinctメソッドの使用例
リンクをコピーしました

下記のcatsテーブルには、猫の名前と種類が管理されています。猫の種類を一覧表示させたい場合に、このままではid=1とid=4のspecies列に同じ種類(ミックス)が格納されているので、重複して表示されます。

猫の種類が重複しているテーブルの例

これを解決するために、重複するデータを削除してくれるdistinctメソッドを使います。distinctメソッドを使うと、下記の様に一意の猫の種類を一覧表示することが出来ます。

distinctメソッドを使った猫の種類の一覧表示の例

それでは、この猫の種類一覧をコードで確認していきましょう。

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

まずは、catsテーブルのspecies列を取得する為に、catsテーブルに対応するCatモデルにselectメソッドを使います。

コンソール | catsテーブルのspecies列を取得する
1
2
3
4
5
6
7
8
9
cats_species = Cat.select(:species)
SELECT `cats`.`species` FROM `cats`

=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, species: "ミックス">,      # speciesの値が重複
#<Cat id: nil, species: "スコティッシュ・フォールド">, 
#<Cat id: nil, species: "アメリカン・ショートヘア">, 
#<Cat id: nil, species: "ミックス">,      # speciesの値が重複
#<Cat id: nil, species: "マンチカン">]>

Cat.select(:species)で発行されるSQLは、SELECT cats.species FROM catsで「catsテーブルからspecies列を取得する」という構文になります。

このSQLによって、全てのspecies列のデータを含むActiveRecord::Relationオブジェクトが返りますが、ミックスの値は重複している状態です。猫の種類一覧は、一意のspecies列のデータで表示させたいので、このコードにdistinctメソッドを追加します。

distinctメソッドを使った場合
リンクをコピーしました

それでは、distinctメソッドを使って重複しているデータを削除していきます。

catsテーブルからspecies列を取得する為にselectメソッドを使いましたが、このメソッドの返り値はActiveRecord::Relationオブジェクトなので、下記の様にメソッドチェーンでdistinctメソッドを呼び出す事が出来ます。

コンソール | catsテーブルの一意のspecies列を取得する
1
2
3
4
5
6
7
8
cats_species = Cat.select(:species).distinct
SELECT DISTINCT `cats`.`species` FROM `cats`

=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, species: "ミックス">,  # speciesの値が重複していない
#<Cat id: nil, species: "スコティッシュ・フォールド">, 
#<Cat id: nil, species: "アメリカン・ショートヘア">, 
#<Cat id: nil, species: "マンチカン">]>

Cat.select(:species).distinctで発行されるSQLは、SELECT DISTINCT cats.species FROM catsで、DISTINCTが追加されていますが、これは「catsテーブルから取得するspecies列の値が一致しているデータは除外して取得する」という構文になります。

このSQLによって、重複していないspecies列のデータを取得している事が返り値から確認出来ます。また、distinctメソッドもActiveRecord::Relationオブジェクトを返します。

下記の様にdistinctメソッドに対して、メソッドチェーンを使ってorderメソッドを呼び出す事も出来ます。下記はorderメソッドの引数にspeciesを指定する事であいうえお順に取得しています。

コンソール | species列の重複していないデータをあいうえお順で取得する
1
2
3
4
5
6
7
8
Cat.select(:species).distinct.order(:species)
SELECT DISTINCT `cats`.`species` FROM `cats`   ORDER BY `cats`.`species` ASC

=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, species: "アメリカン・ショートヘア">, 
#<Cat id: nil, species: "スコティッシュ・フォールド">, 
#<Cat id: nil, species: "マンチカン">, 
#<Cat id: nil, species: "ミックス">]>

返り値を確認すると、speciesがあいうえお順になってデータを取得しています。

Pikawakaマークdistinctメソッドを使用した場合

  1. 発行されるSQL文にDISTINCT句が追加されて、取得する列の値が一致しているデータを除外して取得します。
  2. 返り値は、ActiveRecord::Relationオブジェクトです。
  3. メソッドチェーンを使ってorderメソッドなど他のメソッドを呼び出す事が出来ます。

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

distinctメソッドは、取得列を配列で返すpluckメソッドも併用する事が出来ます。

pluckメソッドはselectメソッドと同じSQLを発行しますが、pluckメソッドの返り値がArray(配列)で、selectメソッドの返り値がActiveRecord::Relationオブジェクトという違いがあります。詳しくは、selectメソッドとpluckメソッドの異なる点とは?を参考にして下さい。

それでは、猫の種類をpluckメソッドだけで取得した場合とdistinctメソッドを併用した場合を確認していきます。

pluckメソッドだけを使った場合
リンクをコピーしました

猫の種類のデータを取得するために、下記の様にpluckメソッドの引数に取得する列名を渡してCat.pluck(:species)実行すると、Cat.select(:species)と同じSELECT cats.species FROM catsのSQLが発行されます。

コンソール|pluckメソッドを使って猫の種類の値を配列で取得する
1
2
3
4
5
Cat.pluck(:species)
SELECT `cats`.`species` FROM `cats`

# 返り値
=> ["ミックス", "スコティッシュ・フォールド", "アメリカン・ショートヘア", "ミックス", "マンチカン"]

発行されるSQLはselectメソッドと同じですが、返り値は配列で返っています。そして、配列の値には、猫の種類のミックスが重複して格納されています。

配列の値を一意の猫の種類にしたい場合に、pluckメソッドだけではなくdistinctメソッドを併用します。

distinctメソッドを併用した場合
リンクをコピーしました

猫の種類を一意で取得するために、Cat.pluck(:species)にdistinctメソッドを追加して下記の様にCat.distinct.pluck(:species)を実行すると、SELECT DISTINCT cats.species FROM catsのSQLが発行されます。

コンソール|pluckメソッドとdistinctメソッドを使って一意の猫の種類の値を配列で取得する
1
2
3
4
5
Cat.distinct.pluck(:species)
SELECT DISTINCT `cats`.`species` FROM `cats`

# 返り値
=> ["ミックス", "スコティッシュ・フォールド", "アメリカン・ショートヘア", "マンチカン"]

このDISTINCTによって、重複していたミックスを除外して、返り値の配列に一意の猫の種類を格納する事が出来ました。上記の返り値を確認すると、ミックスは重複していません。

この様に、pluckメソッドとdistinctメソッドは便利なメソッドですが、下記の様にpluckメソッドの後にメソッドチェーンでdistinctメソッドを使うとNoMethodErrorが発生します。

コンソール|pluckメソッドの後にメソッドチェーンでdistinctメソッドを呼び出すとエラーになる
1
2
Cat.pluck(:species).distinct
NoMethodError: undefined method `distinct' for #<Array:0x007ff869c9b5d0>

pluckメソッドの返り値は、配列なのでメソッドチェーンを使う事が出来ません。メソッドチェーンを使う事が出来るのは、distinctメソッドの返り値でもあるActiveRecord::Relationオブジェクトです。

他にもpluckメソッドを使う際のいくつかの注意点があります。詳しくは、pluckメソッドの注意点を参考にして下さい。

distinctメソッドの引数
リンクをコピーしました

distinctメソッドの引数は、デフォルトでdistinct(true)の様にtrueが渡されます。これによって、取得列の重複するデータを除外して取得出来ます。

そして、distinctメソッドの引数にdistinct(false)の様にfalseを渡すと、取得列を重複の有無に関わらず全て取得します。これは、distinctメソッドで重複データを除外したデータに対しても有効です。

catsテーブルから猫の種類を取得していた下記のコードを例にして確認していきます。

コンソール | catsテーブルのspecies列を取得する
1
2
3
4
5
6
7
8
9
cats_species = Cat.select(:species)
SELECT `cats`.`species` FROM `cats`

=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, species: "ミックス">,      # speciesの値が重複
#<Cat id: nil, species: "スコティッシュ・フォールド">, 
#<Cat id: nil, species: "アメリカン・ショートヘア">, 
#<Cat id: nil, species: "ミックス">,      # speciesの値が重複
#<Cat id: nil, species: "マンチカン">]>

distinct(true).distinct(false)
リンクをコピーしました

先ほどのCat.select(:species)では、猫の種類を重複して取得してしまうので、distinctメソッドを使って一意の値を取得します。デフォルトでは引数にtrueが渡されるので、メソッド呼び出し時にCat.select(:species).distinctでもCat.select(:species).distinct(true)と同じ意味になります。

コンソール | catsテーブルの一意のspecies列を取得する
1
2
3
4
5
6
7
8
9
10
cats_species = Cat.select(:species).distinct
# 上記と同じ意味
cats_species = Cat.select(:species).distinct(true)
SELECT DISTINCT `cats`.`species` FROM `cats`

=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, species: "ミックス">, 
#<Cat id: nil, species: "スコティッシュ・フォールド">, 
#<Cat id: nil, species: "アメリカン・ショートヘア">, 
#<Cat id: nil, species: "マンチカン">]>

cats_speciesには、重複が除外された猫の種類の値が格納されています。このcats_speciesに対してdistinct(false)を使ってcats_species.distinct(false)を実行すると、重複の有無に関わらず猫の種類を取得します。

コンソール|distinct(false)で重複を除外したデータに対して、重複有無に関わらず全ての猫の種類を取得する
1
2
3
4
5
6
7
8
9
cats_species.distinct(false)
SELECT `cats`.`species` FROM `cats`

=> #<ActiveRecord::Relation [ # 返り値
#<Cat id: nil, species: "ミックス">,       # speciesの値が重複
#<Cat id: nil, species: "スコティッシュ・フォールド">, 
#<Cat id: nil, species: "アメリカン・ショートヘア">, 
#<Cat id: nil, species: "ミックス">,       # speciesの値が重複
#<Cat id: nil, species: "マンチカン">]>

上記のコードを確認すると、cats_speciesには重複した猫の種類を除外した値が格納されていましたが、distinct(false)を使う事でcatsテーブルのspecies列の全ての値を取得するというSQLが発行されている事が確認出来ます。

また、指定列のレコードの種類ごとにデータをまとめるgroupメソッドというdistinctメソッドと似ているメソッドがあります。distinctメソッドは、重複を除外してデータを取得したい場合に使用し、groupメソッドは、データをまとめて更に処理をする場合に使用する様にしましょう。詳しくは 【Rails】groupメソッドの使い方を図解形式で仕組みを徹底解説!を参考にして下さい。

まとめ
  • データベースから取得したデータから重複するレコードを削除するメソッド
  • 返り値は、ActiveRecord::Relationオブジェクトなのでメソッドチェーンを使える
  • 引数にfalseを渡すと、取得列を重複の有無に関わらず全ての取得する