【Rails5】Active Record のクエリ

Rails5 でよく使われる Active Record のクエリをまとめました。

find

主キーで検索します。

User.find(100) 
=> SELECT `users`.* FROM `users` WHERE `users`.`id` = 100 LIMIT 1

# 複数指定
User.find([100, 101])
=> SELECT `users`.* FROM `users`  WHERE `users`.`id` IN (100, 101)

データが1件も取得できなかった場合は ActiveRecord::RecordNotFound でエラーになります。

take

指定した数のレコードを取得します。

# デフォルトは1件
User.take
=> SELECT `users`.* FROM `users` LIMIT 1

User.take(5)
=> SELECT `users`.* FROM `users` LIMIT 5

1件もない場合に例外(ActiveRecord::RecordNotFound)を起こす場合は ! をつけます。

User.take!
=> SELECT `users`.* FROM `users` LIMIT 1

first / second / third… / last

first: 主キーの昇順でソートして一番最初のレコードを返します。
last: 主キーの降順でソートして一番最初のレコードを返します。

User.first
=> SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1

引数でとってくる数を指定できます。

User.first(3)
=> SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 3

1件もない場合に例外(ActiveRecord::RecordNotFound)を起こす場合は ! をつけます。

second, thirdなどで何番目のデータをとってくるか指定できます。

find_by

条件に合致したレコードを1件取得します。

# idで指定
User.find_by(id: 100)
=> SELECT `users`.* FROM `users` WHERE `users`.`id` = 100 LIMIT 1

# nameで指定
User.find_by(name: 'taro')
=> SELECT `users`.* FROM `users` WHERE `users`.`name` = 'taro' LIMIT 1

# 複数指定
User.find_by(name: 'taro', email: 'test@example.com')
=> SELECT `users`.* FROM `users` WHERE `users`.`name` = 'taro' AND `users`.`email` = 'test@example.com' LIMIT 1

1件もない場合に例外(ActiveRecord::RecordNotFound)を起こす場合は ! をつけます。

all / find_each / find_in_batches

すべてのレコードを取得します。

User.all
=> SELECT `users`.* FROM `users`

レコード数が多いとメモリが足りなくなるので、その場合は find_eachfind_in_batches でデータを何件かに分けて取得するようにします(バッチ処理)。

# デフォルトでは1000件ずつ取得
User.find_each do |user|
  p user.id
end

取得するデータ数を指定する場合は batch_size を使用します。

# 100件ずつ取得
User.find_each(batch_size: 100) do |user|
  p user.id
end

バッチ処理を開始・終了する主キーを指定する場合は startfinish を使用します。

# id が100以上のデータ
User.find_each(start: 100) do |user|
  p user.id
end

# id が100以上200以下のデータ
User.find_each(start: 100, finish: 200) do |user|
  p user.id
end

バッチ処理をする引数を配列で扱いたい場合は find_in_batches を使用します。

# ブロック引数の users は、user オブジェクトが1000個入った配列
User.find_in_batches do |users|
  p users
end

find_in_batches も同様に batch_sizestartfinish が使用できます。

where

指定した条件に合致するレコードを取得します。

User.where(name: 'taro')
=> SELECT `users`.* FROM `users` WHERE `users`.`name` = 'taro'

カラム名は文字列、ロケット記法でも指定できます。

User.where('name' => 'taro')
=> SELECT `users`.* FROM `users` WHERE `users`.`name` = 'taro'

同カラムで条件値を複数指定する場合は配列にします。

User.where(name: ['taro', 'ziro'])
=> SELECT `users`.* FROM `users`  WHERE `users`.`name` IN ('taro', 'ziro')

否定を指定する場合は where.not を使用します。

User.where.not(name: 'taro')
=> SELECT `users`.* FROM `users` WHERE (`users`.`name` != 'taro')

条件値が動的に変わる場合は ? か、名前を指定したい場合は :colume_name を使います。

name = "taro"
email = "test@example.com"
User.where("name = ?", name)
User.where("name = ? and email = ?", name, email)
name = "taro"
email = "test@example.com"
User.where("name = :name and email = :email", name: name, email: email)

複数の条件のうち、1つでもマッチしたものを取得する場合は where.or を使用します。

User.where(name: 'taro').or(User.where(email: 'test@example.com'))
=> SELECT `users`.* FROM `users` WHERE (`users`.`name` = 'taro' OR `users`.`email` = 'test@example.com')

条件に範囲を指定する場合は .. を使用します。

User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
=> SELECT `users`.* FROM `users`  WHERE (`users`.`created_at` BETWEEN '2015-01-06 15:00:00' AND '2015-01-07 15:00:00')

order

並び順を指定します。

# created_atの昇順
User.order(:created_at)
=> SELECT `users`.* FROM `users` ORDER BY `users`.`created_at` ASC

# 文字列でも指定可能
User.order("created_at")

# 昇順
User.order(created_at: :asc)
User.order("created_at asc")

# 降順
User.order(created_at: :desc)
User.order("created_at desc")

# 複数指定
User.order(name: :asc, created_at: :desc)
User.order("name asc, created_at desc")

select

指定したカラム名を取得します。

User.select(:name)
=> SELECT `users`.`name` FROM `users`

同カラムでユニークなレコードのみ取得したい場合は distinct を使用します。

User.select(:name).distinct

limit / offset

limit は取得件数、offset は何番目のデータから取得するかを指定します。配列のインデックスと同じで、1件目は「0」を指定します。

# 5件取得
User.limit(5)
=> SELECT  `users`.* FROM `users`  LIMIT 5

# 11番目から5件取得
User.limit(5).offset(10)
=> SELECT `users`.* FROM `users` LIMIT 5 OFFSET 10

find_or_create_by

あれば取得し、なければ新規作成します。

User.find_or_create_by(name: 'taro')

新規作成する際に他のカラムに値を設定したい場合は create_with もしくはブロックを使用します。

User.create_with(email: 'taro@example.com').find_or_create_by(name: 'taro')

# ブロックの場合
User.find_or_create_by(name: 'taro') do |user|
  user.email = 'taro@example.com'
end

新規作成する際にバリデーションでエラーになった場合に例外を発生させるには ! をつけます。

find_or_initialize_by

あれば取得し、なければ初期化します。

User.find_or_initialize_by(name: 'taro')

pluck

特定のカラムのリストを配列で返します。

User.pluck(:id)
=> SELECT `users`.`id` FROM `users`

# 複数指定
User.pluck(:id, :name)=> SELECT `users`.`id`, `users`.`name` FROM `users`

ids

idのリストを配列で返します。

User.ids
=> SELECT `users`.`id` FROM `users`

exists?

レコードが存在するかどうかを true/false で返します。

User.exists?(1) # デフォルトは id
User.exists?(name: 'taro')
User.where(name: 'taro').exists?

count

レコード数を返します。

User.count
=> SELECT COUNT(*) FROM `users`

# 条件句つき
User.where(name: 'taro').count
=> SELECT COUNT(*) FROM `users` WHERE `users`.`name` = 'taro'

average

特定カラムの平均値を取得します。

Item.average(:price)
=> SELECT AVG(`items`.`price`) AS avg_id FROM `items`

minimum

特定カラムの最小値を取得します。

Item.minimum(:price)
=> SELECT MIN(`items`.`price`) AS min_id FROM `items`

maximum

特定カラムの最大値を取得します。

Item.maximum(:price)
=> SELECT MAX(`items`.`price`) AS max_id FROM `items`

sum

特定カラムの合計値を取得します。

Item.sum(:price)
=> SELECT SUM(`items`.`price`) AS sum_id FROM `items`

readonly

読み込み専用として取得します。

user = User.readonly.first
user.name = "ziro"
user.save # エラー

group

特定のカラムをグルーピングします。

他のカラムの件数を取得する場合

Item.group(:name).count
=> SELECT COUNT(*) AS count_all, name AS name FROM `items` GROUP BY name

他のカラムの平均値を出す場合

Item.group(:name).average(:price)
=> SELECT AVG(`items`.`price`) AS average_price, name AS name FROM `items` GROUP BY name

他のカラムの合計値を出す場合

Item.group(:name).sum(:price)
=> SELECT SUM(`items`.`price`) AS sum_price, name AS name FROM `items` GROUP BY name

グルーピングする時に条件を追加する場合は having を使用します。

Item.group(:name).having(name: "apple").average(:price)
=> SELECT AVG(`items`.`price`) AS average_price, name AS name FROM `items` GROUP BY name HAVING `items`.`name` = 'apple'

find_by_sql

検索に生のSQLを使います。返り値は配列です。

User.find_by_sql('select * from users where id = 1')

select_all

検索に生のSQLを使います。返り値はハッシュです。

User.connection.select_all('select * from users where id = 1')

to_sql

ActiveRecord_Relation のメソッドで、発行したSQLの内容を確認することができます。

User.where(1).to_sql
=> "SELECT `users`.* FROM `users`  WHERE (1)"

explain

ActiveRecord_Relation のメソッドで、発行したSQLの内容を詳細に確認することができます。

User.where(1).explain
=> EXPLAIN for: SELECT `users`.* FROM `users`  WHERE (1)
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL |  106 | NULL  |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)