更新日:
【Rails】 whereメソッドを使って欲しいデータの取得をしよう!
whereメソッドとは、テーブル内の条件に一致したレコードを配列の形で取得することができるメソッドです。
1
モデル名.where("条件")
具体的には下記のように記述します。
1
2
3
4
5
6
7
8
9
# ageカラムに20が入っているレコードを全て取得
@users = User.where(age: 20)
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`age` = 20
=> [#<User:0x007fd316ec6710
id: 1,
name: "ぴかこ",
age: 20,
height: 170,
weight: 60,>]
特定のレコードだけ取得したり、検索機能を実装したりするときに使うメソッドです。
whereメソッドの基本的な使い方
それではどういう時にどのようにwhereメソッドを使うかを確認していきましょう。
whereメソッドの記述方法
whereメソッドはシンボル指定と文字列指定の2つの方法があります。
順番にそれぞれの指定方法を確認していきましょう。
シンボル指定の方法
シンボル指定の場合は下記のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
モデル名.where(カラム名: 条件)
# nameカラムから「ぴっか」という文字が入っているレコードを全て取得
User.where(name: "ぴっか")
# 発行されるSQL
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'ぴっか'
=> [#<User:0x007fd316cdeee8
id: 3,
name: "ぴっか",
age: 30,
height: 155,
weight: 46>]
文字列指定の方法
文字列指定の場合は下記のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
モデル名.where("カラム名 = 条件")
# nameカラムから「ぴっか」という文字が入っているレコードを全て取得
User.where("name = 'ぴっか'")
# 発行されるSQL
User Load (17.2ms) SELECT `users`.* FROM `users` WHERE (name = 'ぴっか')
=> [#<User:0x007fd313623f70
id: 3,
name: "ぴっか",
age: 30,
height: 155,
weight: 46>]
このように書き方としては2つの方法がありますが、実際は1つの条件で検索をするときにはwhere(カラム名: "条件")
のようにシンボル指定で定義します。
これはSQLインジェクションというセキュリティ上の脆弱性を防ぐためです。
SQLインジェクションとは
SQLを呼び出す際にセキュリティ上の不備を利用して、データベースのデータを不正に操作する攻撃方法のことを言います。
例えば下記のようなコードがあったとします。
1
2
id = params[:id]
User.where("id = #{id}")
もしユーザーidが1
のユーザーのときは下記のようなSQLが発行されデータを取得します。
1
SELECT `users`.* FROM `users` WHERE (id = 1)
そしてユーザーidが1
のユーザーのレコードを取得できます。
もしこの1
の部分に1 OR 1 = 1
という値が入ってしまうとどうなるでしょうか?
1
2
id = '1 OR 1 = 1'
User.where("id = #{id}")
すると下記のようなSQLが発行されてしまいます。
1
SELECT `users`.* FROM `users` WHERE (id = 1 OR 1 = 1)
上のコードが何を意味しているかというと、この後解説をするOR
によってid = 1
と1 = 1
の2つの式を判定します。
SQLではWHEREにtrue
が渡ると全てのレコードを取得します。
この時1 = 1
の式がtrue
と判断されるため、全てのレコードが取得できてしまいます。
これだと全てのユーザーのデータが取得されてしまうため、セキュリティ上困りますよね。
ですがハッシュ形式で記述しておけばこのようなことは起こりませんので不正な値を取得されないというわけです。
1
2
3
4
User.where(id: id)
# 発行されるSQL
SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
Rails(O/Rマッパー)を使用していると、SQLの知識は要らないと思われがちですが、「無駄なクエリの発行、データの引き出しの間違え」は、SQLで確認できるのです!
他にもパフォーマンス・デバック・複雑な操作の際にもSQLの知識は必要になります。
SQLの知識がなく、「データベースの操作だけできれば良い」と考えているとプログラムの品質や信頼を失ってしまいます。
SQLについて不安がある方は、基礎から一通り学べる以下の参考書を利用すると良いでしょう。
発売から数年で不動の定番テキストとなった大人気SQL入門書に、最新のDBに対応した改訂版が登場!
プレースホルダの記述の仕方
SQLインジェクションを防ぐ書き方はもう一つあります。
それがプレースホルダーを使った書き方です。
1
2
3
4
モデル名.where("name = ?", "pikawaka")
# 上のコードは下記のコードと同じ
モデル名.where("name = 'pikawaka'")
モデル名.where(name: "pikawaka")
?
はプレースホルダーと呼ばれ、第二引数で指定した値が置き換えられます。
上の例のpikawaka
の部分には変数が入ることが多いです。
?
を使うときにはwhere(カラム名: "条件")
のような書き方はできません。
プレースホルダーを使った書き方は、この後説明する比較して条件を定義するときやand
やor
を使うときにも使います。
whereメソッドの様々な検索方法・複雑な指定方法
whereメソッドには様々な検索方法があります。
順番に確認していきましょう。
比較して条件を定義
whereメソッドの条件には下記のように比較をして定義することができます。
このときもプレースホルダーを使いましょう。
1
2
3
4
5
User.where("id > ?", 5)
# 発行されるSQL
User Load (36.8ms) SELECT `users`.* FROM `users` WHERE (id > 5)
# idが5以上のレコードが全て取得できる
複数カラム指定でのAND検索方法
下記のように記述するとどちらの条件にも当てはまるレコードを取得することができます。
シンボルの場合
1
2
3
4
5
6
7
8
9
10
User.where(name: "ぴっか", age: 25)
# 発行されるSQL
User Load (2.3ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'ぴっか' AND `users`.`age` = 25
=> [#<User:0x007fd316e2ecf8
id: 3,
name: "ぴっか",
age: 25,
height: nil,
weight: 22>]
このようにnameカラムに「ぴっか」、ageカラムに「25」が存在するレコードを取得することができます。
どちらか一方でも当てはまらないレコードは取得しません。
文字列の場合
文字列でも指定することが出来ます。
その際はプレースホルダーの?
を使用します。
下のコードはnameカラムにぴっか
があり、さらにageカラムに25
があるレコードを全て取得します。
1
2
3
4
5
6
7
8
9
10
User.where("name = ? and age = ?", "ぴっか", 25)
# 発行されるSQL
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE (name = 'ぴっか' and age = 25)
=> [#<User:0x007fd316e2ecf8
id: 3,
name: "ぴっか",
age: 25,
height: 167,
weight: 52>]
複数カラムを指定するときのSQLを見るとAND句が使われていることがわかります。
複数カラム指定でのOR検索方法
条件にor
を使うとどちらかの条件に当てはまるレコードを取得することができます。
シンボルの場合
シンボルで指定する場合は下記のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
User.where(name: "ぴっか").or(User.where(age: 22))
# 発行されるSQL
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE (`users`.`name` = 'ぴっか' OR `users`.`age` = 22)
=> [#<User:0x007fd318858b60
id: 1,
name: "ぴかこ",
age: 22,
height: 170,
weight: 60,>,
#<User:0x007fd318858a20
id: 3,
name: "ぴっか",
age: 25,
height: 167,
weight: 52>]
このように上のコードはnameカラムにぴっか
があるかageカラムに22
があるレコードを全て取得します。
つまりどちらか1つでも当てはまるレコードを全て取得してくれます。
文字列の場合
文字列の場合はプレースホルダーを使い下記のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
User.where("name = ? or age = ?", "ぴっか", 22)
# 発行されるSQL
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE (name = 'ぴっか' or age = 22)
=> [#<User:0x007fd318858b60
id: 1,
name: "ぴかこ",
age: 22,
height: 170,
weight: 60,>,
#<User:0x007fd318858a20
id: 3,
name: "ぴっか",
age: 25,
height: 167,
weight: 52>]
not検索
not検索は条件を満したレコード以外を取得する時に使います。
シンボルの場合
シンボルの場合は下記のように定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
モデル名.where.not("条件")
# 例
User.where.not(name: "ぴっか")
# 発行されるSQL
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`name` != 'ぴっか'
=> [#<User:0x007fd316ca46f8
id: 1,
name: "ぴかこ",
age: 25,
height: 170,
weight: 60>,
#<User:0x007fd316ca4590
id: 2,
name: "ぴかわか",
age: 67,
height: 156,
weight: 60>,
# ...略
上のように記述するとnameカラムの「ぴっか」という文字が入っているレコード以外のレコードを全て取得します。
複数指定する場合は下記のように記述します。
1
2
3
User.where.not(name: "ぴっか", height: 160)
# 下のコードと同じ意味
User.where.not(name: "ぴっか").where.not(hegiht: 160)
上のように記述するとnameカラムに「ぴっか」という文字が入っているレコードと、heightカラムが160以外のレコードを全て取得します。
どちらか一方が入っていればそのレコードは取得されません。
文字列の場合
文字列で指定する場合は下記のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
モデル名.where("NOT(条件)")
# 例
User.where("NOT(name= 'ぴっか')")
# 発行されるSQL
User Load (2.5ms) SELECT `users`.* FROM `users` WHERE (NOT(name= 'ぴっか'))
=> [#<User:0x007fd316ca46f8
id: 1,
name: "ぴかこ",
age: 25,
height: 170,
weight: 60>,
#<User:0x007fd316ca4590
id: 2,
name: "ぴかわか",
age: 67,
height: 156,
weight: 60>,
# ...略
NOT NULL
SQLではカラムの値がNULL(rubyではnil)以外を取得するにはIS NOT NULL
と記述します。
whereメソッドの条件にこれを記述するとカラムの値がNULL以外のレコードを全て取得することができます。
シンボルの場合
シンボルの場合は下記のように定義します。
1
2
3
4
User.where.not(name: nil)
# 発行されるSQL
SELECT `users`.* FROM `users` WHERE `users`.`name` IS NOT NULL
文字列の場合
文字列の場合は下記のように定義します。
1
2
3
4
5
# nameカラムがNULL以外のレコードを全て取得
User.where("name IS NOT NULL")
# 発行されるSQL
SELECT `users`.* FROM `users` WHERE (name IS NOT NULL)
このように記述するとnameカラムの値が存在するレコードだけを取得することができます。
ただしこの記述よりシンボルを使って書くのが一般的です。
複数の条件(in句)
whereメソッドの条件に配列を使うと複数の条件を定義することができます。
シンボルの場合
シンボルの場合は下記のように定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
User.where(id: [1,2])
# 発行されるSQL
User Load (42.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 2)
=> [#<User:0x007fd3180a9f18
id: 1,
name: "ぴかこ",
age: 25,
height: 170,
weight: 60>,
#<User:0x007fd3180a9dd8
id: 2,
name: "ぴかわか",
age: 67,
height: 156,
weight: 60>]
上のように記述するとidカラムが1と2のレコードが取得できます。
このように配列で指定するとSQLのIN句
が使われているのが確認できます。
文字列の場合
文字列の場合は下記のように定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
User.where("id IN (1, 2)")
# 発行されるSQL
User Load (4.3ms) SELECT `users`.* FROM `users` WHERE (id IN (1, 2))
=> [#<User:0x007fd3180a9f18
id: 1,
name: "ぴかこ",
age: 25,
height: 170,
weight: 60>,
#<User:0x007fd3180a9dd8
id: 2,
name: "ぴかわか",
age: 67,
height: 156,
weight: 60>]
あいまい検索 LIKE
LIKE句は、あいまいな文字列の検索をするときに使います。
like検索
あいまい検索は下記のように記述します。
1
2
3
4
5
6
モデルクラス.where("カラム名 LIKE?", "検索したい文字列")
# 例 nameカラムに「あ」という文字が入っているレコードを全て取得
User.where("name LIKE?", "%あ%")
# 発行されるSQL
User Load (20.6ms) SELECT `users`.* FROM `users` WHERE (name LIKE'%あ%')
上の検索したい文字列
には%
と_
を文字列と組み合わせた値として記述します。
それぞれ下記の意味を持ちます。
記号 | 意味 |
---|---|
% | 空白文字を含む任意の複数文字列 |
_ | 任意の1文字 |
これだけだとわかりづらいので例を見てみましょう。
%
は任意の複数文字なのであ%
と書くとあ
以降はどんな文字も複数含むので先頭にあ
がついた文字列は全て含まれることになります。
(例)あ% = ありがとうございます
%あ
と書くと末尾があ
で終わる全ての文字列が含まれます。
(例)%は = こんにちは
%
が複数文字に対し、_
は任意の1文字を示します。
あ_
とするとあ
から始まる2文字の単語になります。
(例)あ_ = あめ、あお など
_あ
とするとあ
で終わる2文字の単語になります。
(例)_く = かく、まく など
like句は検索機能を実装したい時などに使います。
1
users = User.where("name LIKE?", "%あ%")
上の例の場合、検索したい文字列を%あ%
と指定しました。
ここでは前後に%
をつけたので文字列の中にあ
が含まれればどんな文字列も当てはまることになります。
ですのでusersテーブルのnameカラムの中のあ
が含まれるレコード全てが取得できるというわけです。
not like検索
上の例は当てはまる場合のレコードを取得しましたが、逆にあてはまらない場合のレコードを取得する際にはnot like検索
を使用します。
1
2
3
4
5
# 例 nameカラムに「あ」という文字が入っていないレコードを全て取得
User.where("name not LIKE?", "%あ%")
# 発行されるSQL
User Load (2.4ms) SELECT `users`.* FROM `users` WHERE (name not LIKE'%あ%')
上のように記述するとnameカラムに「あ」という文字が入っていない全てのレコードを取得することができます。
joinsメソッドで関連するモデル間の検索
アソシエーションを組んでいると関連するモデル間の検索を行うことが出来ます。
その際に使用するメソッドがjoinsメソッド
です。
1
2
3
モデル名.joins(:関連名).where(カラム名: 値)
# 例
Owner.joins(:cats).where(name: "田中")
詳しい定義方法や説明はjoinsメソッド
の記事のwhereメソッドで条件を追加してみようを参照してください。
where(条件).firstをfind_byメソッドに書き換え
whereメソッドは条件に一致した全てのレコードを取得するメソッドでした。
条件には一致するけど全てを取得したくない場合はどうすればよいでしょうか?
where(条件).firstの場合
とりあえず条件に一致するレコードを1つ取得したい場合はfirstメソッド
を使用します。
firstメソッド
は最初のレコードを1つ取得するメソッドです。
1
2
3
4
5
6
モデル名.where(条件).first
# nameカラムに「あ」という文字が入っている最初のレコードを取得
User.where("name LIKE?", "%あ%").first
# 発行されるSQL
User Load (1.0ms) SELECT `users`.* FROM `users` WHERE (name LIKE'%あ%') ORDER BY `users`.`id` ASC LIMIT 1
find_byの場合
find_byメソッド
は条件に一致する最初のレコード1件を取得するメソッドです。
1
2
3
4
5
6
モデル名.where(条件).first
# nameカラムに「あ」という文字が入っている最初のレコードを取得
User.find_by("name like '%あ%'")
# 発行されるSQL
User Load (0.9ms) SELECT `users`.* FROM `users` WHERE (name like '%あ%') LIMIT 1
この2つをどう使い分けるかは世界で一番詳しいfind_byメソッドのいろいろな使い方の記事を参照してください。
検索機能を実装してみよう
whereメソッドを使うと検索機能を実装することができます。
実際のコードの例を紹介します。
検索するためのアクションの定義
この例では検索を行うためにsearchアクション
を定義しました。
検索フォームは下記のように記述したとします。
1
2
3
4
<%= form_with(url: searchアクションが動くパス, local: true) do |form| %>
<%= form.text_field :keyword %>
<%= form.submit "検索" %>
<% end %>
コントローラーのsearchアクションは下記のように記述します。
1
2
3
4
5
6
class UsersController < ApplicationController
def search
keyword = params[:keyword]
@users = User.where('name LIKE?', "%#{keyword}%")
end
end
今回は検索フォームにform.text_field :keyword
と記述したので、このフォームに入力した値はコントローラーでparams[:keyword]
と書くことで取得することができます。
コントローラーでは取得した値をkeyword
という変数に代入しました。
それがwhereメソッドのLIKE句の値に入り、そのワードが含まれたレコードを取得してくれるというわけです。
paramsについてはparamsについて徹底解説!の記事も参考にしてみてください。
この記事のまとめ
- whereメソッドは、テーブル内の条件に一致したレコードを配列の形で取得することができるメソッド
- findメソッドとは異なり、複数のレコードを取得することが出来る
- 検索機能を実装したりするときに使用する