Rails SQLでデータを絞り込む

#はじめに
自分のポートフォリオ作成時に迷ったので書きました。
ユーザーを投稿の多い順番で並び替えたり出来るようになりました。

SQLの基礎はこれで勉強しました↓
[SQLの基本を覚える【初心者向け】-Qiita](https://qiita.com/chida09/items/d4b33a28b918958f267f)
[テーブル結合についての備忘録 その2-Qiita](https://qiita.com/mounntainn/items/2c5a568c98b7e9c38c6f)

Rails ActiveRecordのSQLはこれで勉強しました↓
[Rails における内部結合、外部結合まとめ-Qiita](https://qiita.com/yuyasat/items/c2ad37b5a24a58ee3d30)
[Rails ActiveRecord/SQL 小技集-Qiita](https://qiita.com/tomomomo1217/items/1ccd21bfb97730763255)

前提条件
deviseでUserモデルの作成済み
scaffoldでBlogモデルの作成済み
BlogとUserは１対多
bootstrapでちょっとデザインを付けてます。
seeds.rbでサンプルデータを作成しています。↓

```rb:seeds.rb

if Rails.env == "development"
  (1..10).each do |i|
    User.create!(email: "test#{i}@example.com", password: "password", password_confirmation: "password")
  end

  (1..5).each do |i|
    user = User.find(1)
    user.blogs.create!(title: "test#{i}", content: "test#{i}. test#{i}. test#{i}.")
  end

  (1..3).each do |i|
    user = User.find(2)
    user.blogs.create!(title: "test#{i}", content: "test#{i}. test#{i}. test#{i}.")
  end
  
  user = User.find(3)
  user.blogs.create!(title: "test1", content: "test1. test1. test1.")
end
```

#allメソッド
SQLのWHEREの条件なしのため、idの１番から順番に全件取り出す。

```erb:users/index.html.erb

<h1>ユーザー一覧</h1>
<div class="row">
  <% @users.each do |user| %>
    <div class='col-3'>
      <div class="card mb-5">
        <h1 class='text-center'>ID : <%= user.id %></h1>
        <div class="card-body">
          <p class="card-text"><%= user.email %></p>
          <p class="card-text">書いた記事の数<%= user.blogs.count %></p>
        </div>
      </div>
    </div>
  <% end %>
</div>
```

```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end
```
アクセスした時のログ↓

```sql

User Load (0.3ms)  SELECT "users".* FROM "users"
```

ページにアクセスするとこんな感じで表示されます。↓

<img width="1440" alt="スクリーンショット 2019-07-11 16.19.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348610/2afeec10-1908-8ef0-414f-4214f1b86638.png">

#orderメソッド

SQLのORDER BY節を作ります。
creted_atの降順(desc)で並び替えてます。


```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.order(created_at: :desc)
  end
end
```
アクセスした時のログ↓

```sql

User Load (1.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."created_at" DESC
```

ページにアクセスするとこんな感じで表示されます。↓

<img width="1440" alt="スクリーンショット 2019-07-11 20.54.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348610/739e229e-3122-c483-1132-f4fe2e4e5080.png">

#joinsメソッド
SQLのJOIN節（INNER JOIN)をつくる。
引数のモデルと内部結合出来るみたいです。

```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.order(created_at: :desc)
    @writer = User.joins(:blogs) #追加しました。
  end
end
```
アクセスした時のログ↓

```sql

User Load (1.5ms)  SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id"
```
```erb:users/index.html.erb

<h1>ユーザー一覧</h1>
<div class="row">
  <% @users.each do |user| %>
    <div class='col-3'>
      <div class="card mb-5">
        <h1 class='text-center'>ID : <%= user.id %></h1>
        <div class="card-body">
          <p class="card-text"><%= user.email %></p>
          <p class="card-text">書いた記事の数<%= user.blogs.count %></p>
        </div>
      </div>
    </div>
  <% end %>
</div>

<!-- -----------ここから下を追加しました。----------------- -->
<h1>ブログを書いたユーザー</h1>
<div class="row">
  <% @writer.each do |user| %>
    <div class='col-3'>
      <div class="card mb-5">
        <h1 class='text-center'>ID : <%= user.id %></h1>
        <div class="card-body">
          <p class="card-text"><%= user.email %></p>
          <p class="card-text">書いた記事の数<%= user.blogs.count %></p>
        </div>
      </div>
    </div>
  <% end %>
</div>

```

ページにアクセスするとこんな感じで表示されます。↓

<img width="1440" alt="スクリーンショット 2019-07-11 21.51.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348610/d17fef84-aaf7-37aa-da58-2219b12cdf07.png">

ユーザーが持っているブログの数だけユーザーが重複してます。

#distinctメソッド
distinctメソッドで重複をなくします。
SQLのSELECT節にDISTINCTを追加出来ます。

```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.order(created_at: :desc)
    @writer = User.joins(:blogs).distinct
  end
end
```
アクセスした時のログ↓

```sql

User Load (3.3ms)  SELECT DISTINCT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id"
```

#groupメソッド
また、groupメソッドでも同じ結果になります。
こっちは引数の同じ値同士をグループ化します。
SQLのGROUP BY節をつくります。

```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.order(created_at: :desc)
    @writer = User.joins(:blogs).group(:user_id)
  end
end
```
アクセスした時のログ↓

```sql

User Load (0.7ms)  SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" GROUP BY "user_id"
```


<img width="1440" alt="スクリーンショット 2019-07-11 22.09.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348610/1187b513-7a28-80fe-0c46-109b0280bd76.png">


これでブログを書いたユーザーの重複がなくなりました。

**書いた記事の数が現状たまたまIDが若いほど増えて行ってしまっていて並び変わったか分かりづらくなってしまったのでID = 1のユーザーの記事の数とID = 2のユーザーの記事の数を入れ変えました。すいません。**

#orderメソッドでSQLを使う

次にユーザーの並びを記事を書いた数の多い順番に変更します。
orderの引数をSQLで書きます。（この表現あってるかわかんないです）

```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.order(created_at: :desc)
    @writer = User.joins(:blogs).group(:user_id).order('count(user_id) desc')
  end
end
```
ログを見るとちゃんと`ORDER BY count(user_id) desc`と書かれています。（ただ警告が出てます。後で説明します。）

アクセスした時のログ↓

```sql

DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "count(user_id) desc". Non-attribute arguments will be disallowed in Rails 6.0. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql(). (called from <main> at (pry):4)
  User Load (0.8ms)  SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" GROUP BY "user_id" ORDER BY count(user_id) desc
```

<img width="1440" alt="スクリーンショット 2019-07-11 22.53.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348610/532d70a2-a366-a006-a2f6-36476b7df132.png">

ビューも反映されて　user_id = 1　と　user_id = 2　のユーザーの順番が入れ替わってます。

#警告について

参考リンク↓
[Arel.sqlを付けるだけじゃダメ!? Railsで"Dangerous query method …”の警告が出たときの対応方法](https://qiita.com/jnchito/items/5f2f00c93c0ba68e4d31)

今回のケースではorderのSQL文は入力される値でないため、SQLインジェクションの危険性はないと考え、Arel.sqlを単純につけました。

```rb:controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.order(created_at: :desc)
    @writer = User.joins(:blogs).group(:user_id).order(Arel.sql('count(user_id) desc'))
  end
end
```
アクセスした時のログ↓

```sql

User Load (2.1ms)  SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" GROUP BY "user_id" ORDER BY count(user_id) desc
```

警告がきえました。
Arel.sql自体はSQLインジェクションに対して安全を確保するものではなく、安全を確認しましたという宣言みたいなものみたいです。

#ほかにもいろいろ試してみる
##limitメソッド
SQLのLIMIT節をつくる。
数を制限したいときに便利。

コンソール↓（コンソールに変更しました）

```sql

[6] pry(main)> User.order(created_at: :desc).limit(5)
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."created_at" DESC LIMIT ?  [["LIMIT", 5]]
=> [#<User id: 10, email: "test10@example.com", created_at: "2019-07-11 13:08:06", updated_at: "2019-07-11 13:08:06">,
 #<User id: 9, email: "test9@example.com", created_at: "2019-07-11 13:08:06", updated_at: "2019-07-11 13:08:06">,
 #<User id: 8, email: "test8@example.com", created_at: "2019-07-11 13:08:06", updated_at: "2019-07-11 13:08:06">,
 #<User id: 7, email: "test7@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">,
 #<User id: 6, email: "test6@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">]
[7] pry(main)> 

```
10件あったデータを５件に絞ることが出来ました。

##findメソッド

SQLのWHEREでidのレコードを取得します。
見つからない場合は例外が発生します。

```sql

[7] pry(main)> User.find(1)
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, email: "test1@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">

```
複数のidを取得することも出来ます。

```sql
[8] pry(main)> User.find([1,2,3])
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?)  [["id", 1], ["id", 2], ["id", 3]]
=> [#<User id: 1, email: "test1@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">,
 #<User id: 2, email: "test2@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">,
 #<User id: 3, email: "test3@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">]
```

ちょっと応用でfindでブログを持ったユーザーの情報を取ったりも出来ます。

```sql

User.find(User.joins(:blogs).group(:user_id).pluck(:user_id))
   (0.2ms)  SELECT "user_id" FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" GROUP BY "user_id"
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?)  [["id", 1], ["id", 2], ["id", 3]]
=> [#<User id: 1, email: "test1@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">,
 #<User id: 2, email: "test2@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">,
 #<User id: 3, email: "test3@example.com", created_at: "2019-07-11 13:08:05", updated_at: "2019-07-11 13:08:05">]
```


#最後に
まだまだ、調べながら付け加えて行こうと思います。