SQL基礎!! 指摘ビフォーアフター（研修、レビュー、SELECT文）

# はじめに

実務未経験でWebエンジニアとして入社してからの2カ月で受けた社内研修についての振り返りです。

**SQL のデータ取得**（ **SELECT 文**）の答案へのレビューをいくつかピックアップして、SQL文のビフォーアフターを比較します。

<br>

## 記事の目的

同時期に実務未経験でWebエンジニアになって交流している方々（他社の）が、

**「他の人が初期にどのような注意を受けているか聞いてみたい」**

と言っていたため、私は自分が書いたコードをレビューしてもらった機会はまだ研修のみですが、どのような点をレビューで教えてもらったかについて、共有したいと思います。

振り返りを通して、私自身現在忘れてしまわずに身になっているか確認していきます。

## 想定する読者

想定する読者は、実務未経験の初学者の方です。（過去の自分を想定して書いてます）

## どんな社内研修？

社内研修は、Web開発全般についての基礎的な研修で、以下のカリキュラムでした。

1. PHPの基礎（PHP、オブジェクト指向）
2. データベースの基礎（SQL、MySQL）
3. Webアプリケーション基礎（HTML、CSS、CRUD、Cookie、セッション）

これらの内容についての課題を解いていき、 GitLab でマージリクエストして、 **社内のエンジニア** の方にレビューをいただく、という流れです。

※ 社内研修コードレビュー記事

https://qiita.com/ryamate/items/d87e7168111fc1ef4935

https://qiita.com/ryamate/items/d69220b13f5158acf052

<br><br>

SQLの課題は例えば下記のようなものでした。

>商品テーブルの内容を全て出力するSQLを作ってください

<br>

このようにSQLの基礎文法の理解を問う問題が20問ほどあり、OKもらえるまで修正します。

※ データ取得、データ操作、テーブル操作、トランザクションなどの問題がありました。

<br>

## 目次

今回は「データベースの基礎」の課題について、 **SQL のデータ取得**（ **SELECT 文** ）のレビュー内容に絞って、整理していきたいと思います（一番レビューで色々と指摘があったため）。

以下の8つのレビューをピックアップしました。

1. [「SQLコーディングスタイルを決めましょう」](#1-sqlコーディングスタイルを決めましょう)

1. [「複数取得できる場合は、かならず `ORDER BY` を記載する癖をつけてください」](#2-複数取得できる場合はかならず-order-by-を記載する癖をつけてください)

1. [「`IN`を使って、条件を1行で書いてください」](#3-inを使って条件を1行で書いてください)

1. [「 カウント関数の中に、条件書かずに、`WHERE`句に条件を記載してください」](#4--カウント関数の中に条件書かずにwhere句に条件を記載してください)

1. [「テーブル結合は基本的に `JOIN ON` を用いてください」](#5-テーブル結合は基本的に-join-on-を用いてください)

1. [「`AS` で別名つけてください」](#6-as-で別名つけてください)

1. [「結合に関連しない条件は、`JOIN`したあとに記載してください」](#7--結合に関連しない条件はjoinしたあとに記載してください)

1. [「`GROUP BY` を使った SQL に変えてください」](#8-group-by-を使った-sql-に変えてください)

※ 「答案ビフォー」→「レビュー」→「答案アフター」→「メモ」という構成です。
※ ビフォーとアフターとで、取得しているデータは一緒です。

# 1. 「SQLコーディングスタイルを決めましょう」

## レビュー

SQLのコーディングルールがなくて困りますね。これで統一しましょう。

https://yakinikunotare.boo.jp/orebase2/db/sql/coding_style

実務では現場のコーディングスタイルをまずは確認してください。

## メモ

- レビューというか、レビューをする前の意識合わせです。インターネットで検索した限りでは SQL のコーディングスタイルは、どの書き方が主流かわからず、人それぞれな印象でしたが、現場ではどうなんでしょう。

<br>

# 2. 「複数取得できる場合は、かならず `ORDER BY` を記載する癖をつけてください」

## ビフォー

```sql
SELECT
    product_id,
    product_name,
    color,
    price,
    delete_flag,
    created_at,
    updated_at
FROM
    products
;

```

## レビュー

複数取得できる場合は、かならず `ORDER BY` を記載してください。そうする癖をつけておくようにしてください。

## アフター

```sql
SELECT
    product_id,
    product_name,
    color,
    price,
    delete_flag,
    created_at,
    updated_at
FROM
    products
ORDER BY
    product_id ASC
;

```

## メモ

- 「なぜ、かならず `ORDER BY` を記載すべきか」の理由について自分でも考えてみると、「並び順を何も指定しないと、取得される表示順についてどのように表示されるか保証がない。実務では指定するため、指定する癖をつけよう」ということだと認識しています。

<br>

# 3. 「`IN`を使って、条件を1行で書いてください」

## ビフォー

```sql
SELECT
    product_id,
    product_name,
    color,
    price,
    delete_flag,
    created_at,
    updated_at
FROM
    products
WHERE
    color = '赤'
    OR
    color = '青'
ORDER BY
    product_id ASC
;

```

## レビュー

`IN`を使って、条件を1行で書いてください。

## アフター

```sql
SELECT
    product_id,
    product_name,
    color,
    price,
    delete_flag,
    created_at,
    updated_at
FROM
    products
WHERE
    color
    IN
    (
        '赤',
        '青'
    )
ORDER BY
    product_id ASC
;

```

（コーディングスタイルの関係で1行では書けていませんが…）

## メモ

- 「なぜ、`IN`を使う方がいいか」の理由について考えると、「さらに `’黒’,'白’` …と要素が増えていっても、簡潔に書けるよ」ということだと考えています。

<br>

# 4. 「 カウント関数の中に、条件書かずに、`WHERE`句に条件を記載してください」

## ビフォー

```sql
SELECT
    COUNT(
        gender = '男'
        OR
        NULL
    ) AS '男性の人数'
FROM
    members
WHERE
    delete_flag = 0
;

```

## レビュー

カウント関数の中に、条件書かずに、`WHERE`句に条件を記載してください。単純なcountを使うSQLは、`count(*)` が一般的です。

## アフター

```sql
SELECT
    COUNT(*) AS '男性の人数'
FROM
    members
WHERE
    delete_flag = 0
    AND
    gender = '男'
;

```

※ `OR NULL` は変更後の書き方では不要になったため削除しました。

## メモ

- 「なぜ、カウント関数の中に、条件書かずに、`WHERE`句に条件を記載するのか」の理由について軽く考えましたが、今のところよくわかっていません。SQL文の書きやすさ、読みやすさはありそうですね。ひとまず「一般的」な書き方で書いていきます。

<br>

# 5. 「テーブル結合は基本的に `JOIN ON` を用いてください」

## ビフォー

```sql
SELECT
    SUM(sales.quantity * products.price)
FROM
    sales, products
WHERE
    sales.product_id = products.product_id
;

```

## レビュー

[SQL/コーディングスタイル](http://yakinikunotare.boo.jp/orebase2/db/sql/coding_style) にて、

> JOIN ON句
> 
> 
> 結合は基本的に `JOIN ON` を用いて `FROM` に複数 + `WHERE` 句で結合条件という記述をしない
> 

と記載のとおり、修正してください。

## アフター

```sql
SELECT
    SUM(sales.quantity * products.price)
FROM
    sales
INNER JOIN
    products
    ON
        sales.product_id = products.product_id
;

```

## メモ

- 「なぜ、`FROM` に複数 + `WHERE` 句で結合条件という記述をしないで、結合は基本的に `JOIN ON` を用いるのか」の理由について調べてみると、「ビフォーのような`FROM` に複数 + `WHERE` 句で書く方法は 単純結合 と呼ばれて、`sales` テーブルと `products` テーブルのすべての組み合わせのレコードが返されて、データ数が多くなってしまうため」ということになると考えます。

<br>

# 6. 「`AS` で別名つけてください」

## ビフォー

```sql
SELECT
    SUM(sales.quantity * products.price)
FROM
    sales
INNER JOIN
    products
    ON
        sales.product_id = products.product_id
;

```

## レビュー

`AS` で別名つけてください。

## アフター

```sql
SELECT
    SUM(s.quantity * p.price) AS '売り上げの合計'
FROM
    sales AS s
INNER JOIN
    products AS p
    ON
        s.product_id = p.product_id
;

```

## メモ

- 「なぜ、`AS` で別名つけるのか」の理由については、「カラムやテーブルに別名を付けることで読みやすくなったり、簡潔になるため」だと考えています。

<br>

# 7. 「 結合に関連しない条件は、`JOIN`したあとに記載してください」

## ビフォー

```sql
SELECT
    SUM(s.quantity * p.price) AS '非会員の売り上げの合計'
FROM
    sales AS s
INNER JOIN
    products AS p
    ON
        s.product_id = p.product_id
        AND
        s.member_id IS NULL
;

```

## レビュー

`INNER JOIN` の `ON` 句に、salesの条件があると読みにくいので、 sales の条件（`s.member_id IS NULL`）は `JOIN` したあとに記載してください。

## アフター

```sql
SELECT
    SUM(s.quantity * p.price) AS '非会員の売り上げの合計'
FROM
    sales AS s
INNER JOIN
    products AS p
    ON
        s.product_id = p.product_id
WHERE
    s.member_id IS NULL
;

```

## メモ

- 理由については、sales テーブルと products テーブルの結合条件として、「salesの条件があると読みにくい」ということでした。
- 実行コストは変わらないのかなどはわかっていません。

<br>

# 8. 「`GROUP BY` を使った SQL に変えてください」

## ビフォー

```sql
SELECT
    SUM(
        CASE WHEN m.gender = '男'
        THEN s.quantity * p.price
        ELSE 0 END
    ) AS '男性会員の売り上げの合計'
    ,
    SUM(
        CASE WHEN m.gender = '女'
        THEN s.quantity * p.price
        ELSE 0 END
    ) AS '女性会員の売り上げの合計'
FROM
    sales AS s
INNER JOIN
    products AS p
    ON
        s.product_id = p.product_id
INNER JOIN
    members AS m
    ON
        s.member_id = m.member_id
;

```

## レビュー

`GROUP BY` を使ったSQLに変えてください。

## アフター

```sql
SELECT
    m.gender,
    SUM(s.quantity * p.price) AS '売り上げの合計'
FROM
    sales AS s
INNER JOIN
    products AS p
    ON
        s.product_id = p.product_id
INNER JOIN
    members AS m
    ON
        s.member_id = m.member_id
GROUP BY
    m.gender
;

```

## メモ

- 理由について考えると、CASE式より明らかにスッキリしましたし、種類ごとに集計したいときは `GROUP BY` で良いと考えています。
- 取得した結果は同じでも、SQL文の可読性や、実行コストも考慮できるようになっていきたいです。

<br>

# おわりに

未だ実務で SQL には触れていないこともあり、研修当時からあんまり理解度は変わってない気がする、という危機感があったので、それだけでも今回Qiita記事にまとめ直して、振り返った甲斐がありました…

あと、レビューでそのように書いた方が良いと言っていただく際に理由は添えられていなかったので、理由も理解する必要があると思い、記事にまとめ直す際にメモを書き足してみました。

この記事に対してのレビューもお待ちしております！

<br>

ありがとうございました！