はじめに
主なクエリメソッドをまとめました。
クエリメソッド
下記2つのメソッド(find
,find_by
)はクエリメソッドではありませんが、頭の整理のため載せておきます。
find
主キーを一つだけ指定
[1] pry(main)> Book.find(1)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`id` = 35 LIMIT 1
主キーを複数指定
[2] pry(main)> Book.find([2,4,7])
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`id` IN (2, 4, 7)
find
メソッドは、引数に配列を渡すことで、それぞれの主キーに合致するレコード全てを取り出すことができます。
find_by
任意の列をキーに指定
[3] pry(main)> Book.find_by(title: "メタプログラミングRuby")
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`title` = 'メタプログラミングRuby' LIMIT 1
ヒットした最初の1件を取得します。
複数の任意の列キーを指定
[4] pry(main)> Book.find_by(title: "メタプログラミングRuby", price: 3240)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`title` = 'メタプログラミングRuby' AND `books`.`price` = 3240 LIMIT 1
次からがクエリメソッドです。
クエリメソッドはfind
やfind_by
と違って、その場ではデータベースにアクセスしません。
結果が必要になった時に初めて、データベースに問い合わせます(遅延ロード)。
where
条件句を設定
[5] pry(main)> Book.where(price: 3218)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`price` = 3218
境界値を設定
[6] pry(main)> Book.where(price: 3218..4104)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`price` BETWEEN 3218 AND 4104
BETWEEN演算子を指定しています。
配列で指定
[7] pry(main)> Book.where(price: [3218, 4104])
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`price` IN (3218, 4104)
プレイスホルダーで指定(名前なしパラメーター)
[7] pry(main)> Book.where('title = ? and price >= ?', params[:title], params[:price])
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE (title = 'プログラミング言語Ruby' and price >= 4104)
名前なしパラメーターで指定する時の、メリットは、記述がシンプルであることです。
デメリットには、パラメータと値の対応関係が分かりにくかったり、パラメーターの増減や順番の変化に影響を受けやすいということがあります。
プレイスホルダーで指定(名前付きパラメーター)
[8] pry(main)> Book.where('title = :title and price >= :price', title: params[:title], price: params[:price])
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE (title = 'プログラミング言語Ruby' and price >= 4104)
名前付きパラメーターで指定する時の、メリットは、パラメータと値の対応が分かりやすいことです。
また、デメリットは、記述がやや冗長であることです。
not
否定条件句を設定
[9] pry(main)> Book.where.not(price: params[:price])
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE `books`.`price` != 4104
SQLでは指定の条件を「!=」演算子で反転しています。
or
論理和で結合
[10] pry(main)> Book.where(title: '基礎 Ruby on Rails').or(Book.where('price > 3000'))
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE (`books`.`title` = '基礎 Ruby on Rails' OR (price > 3000))
結合対象の両者は、同じモデルで、where条件式だけが異なるActiveRecord::Relation
オブジェクトでなければなりません。
order
ソート式で指定
[11] pry(main)> Book.where('price >= 2000').order(price: :desc)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE (price >= 2000) ORDER BY `books`.`price` DESC
取得したデータを特定のキーで並べ替えます。
reorder
ソート式を上書きして指定
[12] pry(main)> Book.where('price >= 2000').order(price: :asc).reorder(price: :desc)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` WHERE (price >= 2000) ORDER BY `books`.`price` DESC
前のorder
メソッドを無視してreorder
でのソート順となります。
前のソート式を打ち消し指定
[13] pry(main)> Book.order(:price).reorder(nil)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books`
select
取得列を明示的に指定
[14] pry(main)> Book.where('price >= 2000').select(:id, :title, :price)
これにより次のSQLが発行されます。
SELECT `books`.`id`, `books`.`title`, `books`.`price` FROM `books` WHERE (price >= 2000)
注意点として、select
メソッドで取得列を限定した場合、取得していない列(プロパティ)にアクセスしようとすると、ActiveRecord::MissingAttributeErrorが発生することがあげられます。
distinct
重複のないレコードを指定
[15] pry(main)> Book.select(:id, :title, :price).distinct.order(:price)
これにより次のSQLが発行されます。
SELECT DISTINCT `books`.`id`, `books`.`title`, `books`.`price` FROM `books` ORDER BY `books`.`price` ASC
結果セットから重複した行を除去します。
distinct(false)
をプラスで指定すると、一度追加したDISTINCT句を破棄することができます。
limit, offset
特定範囲のレコードだけを取得
[16] pry(main)> Book.order(price: :desc).limit(3).offset(6)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` ORDER BY `books`.`price` DESC LIMIT 3 OFFSET 6
limit
で取得件数を指定し、offset
で取得開始位置を指定します。
上記の例では、「6件目から最大3件取得」と意味します。
first, last
先頭/末尾のレコードを取得
[17] pry(main)> Book.order(price: :asc).first
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` ORDER BY `books`.`price` ASC LIMIT 1
first
, last
メソッドはクエリメソッドではないため、遅延ロードの対象外です。
メソッドチェーンの途中で記述することができないことに注意します。
group
特定のキー指定でグループ化
[18] pry(main)> Book.select(:id, :title, :price).group(:title)
これにより次のSQLが発行されます。
SELECT `books`.`id`, `books`.`title`, `books`.`price` FROM `books` GROUP BY `books`.`title`
データを集計する際などに利用します。
having
更にデータを絞り込んで取得
[19] pry(main)> Book.select(:id, :title, :price).group(:title).having('price >= 3000')
これにより次のSQLが発行されます。
SELECT `books`.`id`, `books`.`title`, `books`.`price` FROM `books` GROUP BY `books`.`title` HAVING (price >= 3000)
having
メソッドを利用することで、集計した結果をもとに、更にデータを絞り込むことが可能です。
unscope
条件式を除去
[20] pry(main)> Book.where(title: "プログラミング言語Ruby").order(:price).select(:id, :title, :price).unscope(:where, :select)
これにより次のSQLが発行されます。
SELECT `books`.* FROM `books` ORDER BY `books`.`price` ASC
クエリメソッドは、条件式を積み重ねて、最終的に結果が必要になってはじめてクエリが実行される(遅延ロード)ため、実行する前であれば、一度追加した条件式を取り消すことができます。
none
空の結果セットを取得
[21] pry(main)> Book.none
これにより次のように空の結果セットを取得できます。
=> []
none
メソッドを利用すると、戻り値はRelatioin(NullRelation)オブジェクトとなります。この場合、中身が空なだけでそれ自体は結果セットなので、eachメソッドを利用した際にもエラーになることはありません。
まとめ
プログラミングはつまるところ、データの流れの制御だと認識しておりますので、その根幹をなすデータベースとのやりとりをするモデルを理解することが、アプリケーションへの絶対的な理解に繋がりると思います。
今回はその一要素であるクエリメソッドをまとめることにしました。
少しでも役に立ったという方は、いいね、お願いします(^^)
Rails関連記事
正規表現まとめ(基礎)[Ruby編]
配列で利用できる主なメソッドをまとめてみた[Ruby編]
Mysql2::Error: Duplicate entry for key.. エラーを撃退した話(validationの設定)
Rails5でJqueryを利用しようとして少しハマった件(Uncaught ReferenceError: $ is not defined)
change_columnでの設定はrollbackできない話(This migration uses change_column, which is not automatically reversible.)[Rails: migration]
sessionに保存されたHashを別アクションで利用しようとした際にデータ型の変更によりハマった件[Rails]
Railsでfacebookログイン認証機能を実装してみた + エラーの原因はバリデーションだった
csvファイルを作成し、初期データを挿入しよう[Rails]
ArgumentError in ********Controller#send [Rails]
Render and/or redirect were called multiple times in this action...の罠[Rails]