SQL構文

# 概要
　Railsは、フレームワークの構文でSQLが分からなくても簡単にテーブル操作が行うことが可能。
簡単に動く反面、テーブル内部の処理について理解が曖昧だったため、N+1問題の対策に時間がかかりました。そのため、SQLについて学ぶことにしました。

# SQLを学ぶメリット
- Railsのフレームワークで簡単に操作できていたDBの内部でどのような動きをしているのか把握ができる
- RubyやRails以外の言語でも汎用的に活用が可能

# SQLを大量発行するデメリット
APサーバ（アプリケーションサーバ)とDBサーバ間のデータのやり取りに時間がかかるため。
SQL発行数が多いとその分サーバ間の往復が必要となる。


# 今回使用するテーブル
　私の作成途中のポートフォリオから必要部分のみ抜き取り

①地域(Area)は複数の観光地(Spot)を持つ
②観光地(Spot)とユーザ(User)は多対多の関係
　※ユーザが行きたい観光地を示すテーブルがWant

![スクリーンショット 2021-04-24 3.20.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1079862/9b31c018-4b75-f413-7b2f-fa2513609764.png)

# 目指す取得データ
- 観光地の全レコード情報(46箇所分)
- ユーザが行きたい観光地のid

# Controllerにてデータの取得
### 前処理
models/spot.rbに以下を追加。
`has_many :wanted_users, through: :wants, source: :user`
これにより、指定した観光地id(spot)に行きたいと思っているユーザを
`spot.wanted_users.length`
にて取得することが可能。

### Spot.allを用いてデータ取得

- 発行SQL数　2+2*46=94 (回)

```　ruby:controllers/spots_controller.rb
def index
  @spots = Spot.all
  @wanted_spot_ids = current_user.wanted_spots.pluck(:spot_id) if current_user
end
```

``` ruby:実行結果
  Rendering spots/index.html.erb within layouts/application
  Spot Load (34.9ms)  SELECT "spots".* FROM "spots"
  ↳ app/views/spots/index.html.erb:12
  Area Load (1.1ms)  SELECT "areas".* FROM "areas" WHERE "areas"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/shared/_all_spots_list.html.erb:8
  User Load (24.7ms)  SELECT "users".* FROM "users" INNER JOIN "wants" ON "users"."id" = "wants"."user_id" WHERE "wants"."spot_id" = $1  [["spot_id", 1]]
  ↳ app/views/shared/_all_spots_list.html.erb:26
  Rendered shared/_all_spots_list.html.erb (Duration: 240.1ms | Allocations: 16461)
  CACHE Area Load (0.0ms)  SELECT "areas".* FROM "areas" WHERE "areas"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/shared/_all_spots_list.html.erb:8
  User Load (0.5ms)  SELECT "users".* FROM "users" INNER JOIN "wants" ON "users"."id" = "wants"."user_id" WHERE "wants"."spot_id" = $1  [["spot_id", 2]]
  ↳ app/views/shared/_all_spots_list.html.erb:26
  Rendered shared/_all_spots_list.html.erb (Duration: 9.0ms | Allocations: 1843)
  CACHE Area Load (0.0ms)  SELECT "areas".* FROM "areas" WHERE "areas"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/shared/_all_spots_list.html.erb:8
︙	
```


### spot.preload(:area, :wanted_users) を用いてデータ取得

- 発行SQL数　4 (回)

``` ruby:controllers/spots_controller.rb
def index
  @spots = Spot.preload(:area, :wanted_users)
  @wanted_spot_ids = current_user.wanted_spots.pluck(:spot_id) if current_user
end
```

``` ruby:実行結果
Started GET "/spots" for ::1 at 2021-04-24 18:49:27 +0900
Processing by SpotsController#index as HTML
  Rendering spots/index.html.erb within layouts/application
  Spot Load (27.2ms)  SELECT "spots".* FROM "spots"
  ↳ app/views/spots/index.html.erb:12
  Area Load (24.1ms)  SELECT "areas".* FROM "areas" WHERE "areas"."id" IN ($1, $2)  [["id", 1], ["id", 2]]
  ↳ app/views/spots/index.html.erb:12
  Want Load (6.5ms)  SELECT "wants".* FROM "wants" WHERE "wants"."spot_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46)  [["spot_id", 1], ["spot_id", 2], ["spot_id", 3], ["spot_id", 4], ["spot_id", 5], ["spot_id", 6], ["spot_id", 7], ["spot_id", 8], ["spot_id", 9], ["spot_id", 10], ["spot_id", 11], ["spot_id", 12], ["spot_id", 13], ["spot_id", 14], ["spot_id", 15], ["spot_id", 16], ["spot_id", 17], ["spot_id", 18], ["spot_id", 19], ["spot_id", 20], ["spot_id", 21], ["spot_id", 22], ["spot_id", 23], ["spot_id", 24], ["spot_id", 25], ["spot_id", 26], ["spot_id", 27], ["spot_id", 28], ["spot_id", 29], ["spot_id", 30], ["spot_id", 31], ["spot_id", 32], ["spot_id", 33], ["spot_id", 34], ["spot_id", 35], ["spot_id", 36], ["spot_id", 37], ["spot_id", 38], ["spot_id", 39], ["spot_id", 40], ["spot_id", 41], ["spot_id", 42], ["spot_id", 43], ["spot_id", 44], ["spot_id", 45], ["spot_id", 46]]
  ↳ app/views/spots/index.html.erb:12
  User Load (4.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2)  [["id", 1], ["id", 2]]
  ↳ app/views/spots/index.html.erb:12
︙
```


Railsでよく見るSQL文について解説していきたいと思います。

## 1. SELECT

データベースを利用する時、核となる処理が「検索」
（データが保存されているテーブルから必要なデータを取り出す）

① SELECT (カラム名)　・・・　テーブルから検索するカラムの指定
② FROM (テーブル名）　・・・　データの取得元テーブルを指定

（例） SELECT "spots".* FROM "spots"
（意味）spotsテーブル内の全レコードを検索


## 2. WHERE


①WHERE (条件)　・・・　取得テーブルの検索条件を指定、指定した条件に該当するデータを全て返す
②LIMIT (範囲) ・・・　取得するデータの行数を指定する

（例）SELECT "areas".* FROM "areas" WHERE "areas"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
（意味）areasテーブルのid = 1のレコードを1行ずつ取得

## 3. INNER JOIN 

①(テーブル名1) INNER JOIN (テーブル名2) ON (条件) 
　条件に合う値をテーブル2から取得し、テーブル1と結合。一つのテーブルにする。
　[参考：SQL素人でも分かるテーブル結合(inner joinとouter join)]("https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59")

（例）SELECT "users".* FROM "users" INNER JOIN "wants" ON "users"."id" = "wants"."user_id" WHERE "wants"."spot_id" = $1  [["spot_id", 1]]
（意味）
user.id == want.user_id となるレコードを探し、usersテーブルとwantsテーブルを結合する。
その中から、spot_id == 1 であるwantレコードを取得する

## 4.IN
　複数の値が返されるような場合は、後ろに列挙する全ての値のうち、どれか一つと一致するレコードを抽出することができる。

（例）SELECT "wants".* FROM "wants" WHERE "wants"."spot_id" IN ($1, $2, ..., $45, $46)  [["spot_id", 1], ["spot_id", 2], ...,["spot_id", 45], ["spot_id", 46]]
（意味）wantsテーブルのspot_id = 1, 2, ..., 45, 46に当てはまるデータを全て取得

## まとめ
今回、4のINを使用することにより、

- Spot.all
Spotテーブル内のレコード1行1行をそれぞれSQLを発行して取得

- Spot.preload(:area, :wanted_users)
Spotテーブル内の全レコードを1件のSQLで取得

プログラミングだけでなく、SQLにも同じ結果を出すにも解法が複数あることが分かった。
自分の行った解法が最適だと考えるのではなく、見方を変えて改善していけるようにこれからの課題にも取り組んでいきたい。


# その他参考資料
[SQL大量発行処理をいかにして高速化するか]("https://www.slideshare.net/ShogoWakayama/sql-79530929")

SQL実践入門　高速でわかりやすいクエリの書き方