更新日:
【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メソッドを使わない場合
まずは、catsテーブルのspecies列を取得する為に、catsテーブルに対応するCatモデルにselectメソッドを使います。
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メソッドを追加します。
SQLの知識について不安があるという方は、こちらの参考書が基礎から学ぶことができるので良いでしょう。
distinctメソッドを使った場合
それでは、distinctメソッドを使って重複しているデータを削除していきます。
catsテーブルからspecies列を取得する為にselectメソッドを使いましたが、このメソッドの返り値はActiveRecord::Relationオブジェクト
なので、下記の様にメソッドチェーンでdistinctメソッドを呼び出す事が出来ます。
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
を指定する事であいうえお順に取得しています。
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があいうえお順になってデータを取得しています。
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が発行されます。
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が発行されます。
1
2
3
4
5
Cat.distinct.pluck(:species)
SELECT DISTINCT `cats`.`species` FROM `cats`
# 返り値
=> ["ミックス", "スコティッシュ・フォールド", "アメリカン・ショートヘア", "マンチカン"]
このDISTINCT
によって、重複していたミックス
を除外して、返り値の配列に一意の猫の種類を格納する事が出来ました。上記の返り値を確認すると、ミックス
は重複していません。
この様に、pluckメソッドとdistinctメソッドは便利なメソッドですが、下記の様にpluckメソッドの後にメソッドチェーンでdistinctメソッドを使うとNoMethodErrorが発生します。
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テーブルから猫の種類を取得していた下記のコードを例にして確認していきます。
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)
と同じ意味になります。
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)
を実行すると、重複の有無に関わらず猫の種類を取得します。
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を渡すと、取得列を重複の有無に関わらず全ての取得する