クエリメソッドをまとめてみた[Rails]

# **はじめに**
主なクエリメソッドをまとめました。

# **クエリメソッド**

下記２つのメソッド（`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
```
ヒットした**最初の１件**を取得します。

#### **複数の任意の列キーを指定**

```
[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演算子を指定しています。

#### **配列で指定**

```
[７] pry(main)> Book.where(price: [3218, 4104])
```
これにより次のSQLが発行されます。

```
SELECT `books`.* FROM `books` WHERE `books`.`price` IN (3218, 4104）
```

#### **プレイスホルダーで指定（名前なしパラメーター）**

```
[７] 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**

#### **ソート式を上書きして指定**

```
[1２] 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`で取得開始位置を指定します。
上記の例では、「６件目から最大３件取得」と意味します。


### **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編]](https://qiita.com/takuyanin/items/2918ffb3151dcfa88565)
[配列で利用できる主なメソッドをまとめてみた[Ruby編]](https://qiita.com/takuyanin/items/3ca4cd7067b7b9e7e9b6)
[Mysql2::Error: Duplicate entry for key.. エラーを撃退した話(validationの設定)](https://qiita.com/takuyanin/items/b28d5111447d4dedc39d)
[Rails5でJqueryを利用しようとして少しハマった件（Uncaught ReferenceError: $ is not defined）](https://qiita.com/takuyanin/items/24444ec3005098471f3b)
[change_columnでの設定はrollbackできない話(This migration uses change_column, which is not automatically reversible.)[Rails: migration]](https://qiita.com/takuyanin/items/6d51ffb4078a417fed35)
[sessionに保存されたHashを別アクションで利用しようとした際にデータ型の変更によりハマった件[Rails]](https://qiita.com/takuyanin/items/e794e8bdb46287a2a269)
[Railsでfacebookログイン認証機能を実装してみた + エラーの原因はバリデーションだった](https://qiita.com/takuyanin/items/5ebd5d9ac8a0baaa1a8e)
[csvファイルを作成し、初期データを挿入しよう[Rails]](https://qiita.com/takuyanin/items/fd183288d54d2b601df9)
[ArgumentError in ********Controller#send [Rails]](https://qiita.com/takuyanin/items/a14865fda34ff48f5e5a)
[Render and/or redirect were called multiple times in this action...の罠[Rails]](https://qiita.com/takuyanin/items/8aa7a3978a2bb344da59)