非エンジニアのSQL入門

最近、社内でエンジニア以外の方へSQLを教える機会があり、そこだけの話にするのはもったいないと思ったのでqiitaにまとめてみました。

# はじめに

データドリブンと言われるようになって久しい今日この頃、営業や企画など非エンジニアの方もSQLを駆使してデータを取得している場面をよく目にするようになりました。
ただ、SQLとかデータベースとかハードルが高いよ！と感じている方も多いと思います。
しかしデータを参照するためのSQLだけであればすごく簡単に習得できるので是非チャレンジしてみてください。

なお、この記事ではデータ参照のみを扱います。また参照の中でも使用頻度が高いと思われるもののみ扱います

# 前提条件

* SQLを実行してデータを取得できる環境があり、アクセスできること
    * そもそもデータベースを使っていない場合は対象外です
    * データドリブンを掲げているのであれば参照専用の環境はあるはず
* SQLを毎回書いてデータを取得するのはアドホックなデータを取得したい時です
    * 定型データの抽出はミスなく簡単に取得できるようにシステム化してもらいましょう

# データについて

データは「テーブル」と呼ばれる箱に入っています。
テーブルは「カラム」と呼ばれるデータ項目を持っています。
Excelなどの表を想像するとよいかもしれません。

## テーブルの具体例
文章だけではわかりづらいので具体例を挙げます。
ユーザー情報、試験情報、ユーザーが受験した試験情報の3つのテーブルを使います。

### ユーザー情報を持っているテーブル「users」
ユーザー情報は「id(一意に識別するための番号)」、「name」、「email」を持っているものとする

|id|name|email|
|--:|:--|:--|
| 1|伊藤一郎|ito1@example.com|
| 2|伊藤次郎|ito2@example.com|
| 3|鈴木太郎|suzuki@example.com|
| 4|佐藤花子|sato@example.com|

### 試験情報を持っているテーブル「exams」
試験情報は「id(一意に識別するための番号)」、「name」を持っているものとする

|id|name|
|--:|:--|
| 1|国語|
| 2|数学|
| 3|物理|
| 4|化学|


### ユーザーが受験した試験情報を持っているテーブル「user_exams」
ユーザーが受験した試験情報は「exam_id(受験した試験のid)」、「user_id(受験したユーザーのid)」、「point」を持っているものとする

|exam_id|user_id|point|
|--:|--:|--:|
|1|1|100|
|1|2|90|
|1|3|65|
|1|4|70|
|2|1|100|
|2|2|60|
|2|3|95|
|2|4|80|
|3|1|100|
|3|3|60|
|4|1|100|
|4|2|55|
|4|4|80|

このテーブルは急に数字だらけになってしまいわかりづらいと思うので、このテーブルから読み取れることをいくつか列挙しておきます。
下記のことがテーブルから読み取れることを確認してください。

* 国語、数学の試験は4人全員が受けている
* 物理は伊藤一郎と鈴木太郎の2人が受けている
* 伊藤一郎は４科目受けており、全部100点

# データ抽出のSQLについて
ここからデータ抽出のSQLについて書いていきます。

## データ抽出SQLの基本形
１テーブルからデータを取得するSQLは下記の構文で記載できます。

```sql
select
  [参照したいカラム1, 参照したいカラム２, ...]
from
  [テーブル名]
where
  [抽出条件]
```

[参照したいカラム]には、参照したいデータのカラム名記載します。
複数ある場合はカンマ区切りで書きます。

[テーブル名]には参照したいカラムを持っているテーブル名を記載します。

[抽出条件]には取得したいデータの条件を記載します。抽出条件は具体例を説明する時に一緒に説明します。

これまた具体例がないとわかりづらいと思うので具体例を挙げます。

### ユーザー情報を全部取得

```sql
select
  id, name, email
from
  users
```

全件取得の場合は抽出条件は省略できるので`where`は書いていません。

* 取得結果

|id|name|email|
|--:|:--|:--|
| 1|伊藤一郎|ito1@example.com|
| 2|伊藤次郎|ito2@example.com|
| 3|鈴木太郎|suzuki@example.com|
| 4|佐藤花子|sato@example.com|

今回は明示的にカラムを指定しましたが、fromに指定したテーブルの全カラムが欲しい場合は`*`で指定することもできます。

```sql
select
  *
from
  users
```

こちらでも1つ前のSQLと同じ結果が得られます。

逆にemailとnameだけ欲しいという場合は下記のように書きます。

```sql
select
  email, name
from
  users
```

* 取得結果

|email|name|
|:--|:--|
|ito1@example.com|伊藤一郎|
|ito2@example.com|伊藤次郎|
|suzuki@example.com|鈴木太郎|
|sato@example.com|佐藤花子|

※指定したカラムの順序も結果に反映されます。

### 特定のユーザー情報を取得

鈴木一郎のユーザー情報を取得する場合

```sql
select
  id, name, email
from
  users
where
  name = '伊藤一郎'
```

このように任意のカラムが特定の値のデータが欲しい場合は`where`に`[カラム名] = [値]`で指定できます。
今回は鈴木一郎の情報だけ必要なので、`where`に`name = '鈴木一郎'`と書いています。
ちなみに値は`'`で囲ってください。

* 取得結果

|id|name|email|
|--:|:--|:--|
| 1|伊藤一郎|ito1@example.com|

### 特定のユーザー情報を取得(複数)

先ほどの例では値を1つ指定していましたが、複数指定することもできます。
伊藤一郎と鈴木太郎のユーザー情報を取得する場合

```sql
select
  id, name, email
from
  users
where
  name in ('伊藤一郎', '鈴木太郎')
```

複数指定する場合は`[カラム名] in ([値1], [値2], ...)`で指定できます。
inの中のいずれかに一致するデータを取得できます。

* 取得結果

|email|name|
|:--|:--|
|ito1@example.com|伊藤一郎|
|suzuki@example.com|鈴木太郎|

### 部分一致しているユーザー情報を取得する

伊藤という苗字のユーザー情報を取得する場合

```sql
select
  id, name, email
from
  users
where
  name like '伊藤%'
```

`[カラム名] like [値]`で部分一致で検索することができます。
likeを使用すると、値の中に`%`(ワイルドカード)を使用することができるようになります。
例の場合は最後に`%`をつけているので'伊藤'で始まるユーザー情報が取得できます。
また最後が'一郎'で終わるユーザー情報を検索する場合は`%一郎`のように前に`%`をつけることで検索できます。
また'藤'という漢字を含むユーザーを検索する場合は`%藤%`のように前後に`%`をつけることで検索できます。

※※※注意※※※
likeを使った部分一致検索はとてもパフォーマンスが悪いためデータ量が多い場合、データ抽出にかなりの時間がかかる可能性があります。
(likeに限らずですが)そういった事象に遭遇した時はSQLに詳しい方に相談してください。

* 取得結果

|email|name|
|:--|:--|
|ito1@example.com|伊藤一郎|
|ito2@example.com|伊藤次郎|


### 複数カラムの条件指定

user_examsから国語(exam_id=1)で80点以上のユーザーIDを取得する場合

```sql
select
  user_id, point
from
  user_exams
where
  exam_id = 1
  and point >= 80
```

条件に複数のカラムを指定する場合は`and`を使います。`and`を使うことで全ての条件を満たすデータを取得できます。
数値データの場合は`'`で囲む必要はありません。
値の比較に不等号を使うこともできます。具体例は下記の通り。

|where|抽出できるデータ|
|:--|:--|
|point = 100|１００点のデータ|
|point >= 50|5０点以上のデータ|
|point > 50|5０点より高いデータ|
|point <= 50|5０点以下のデータ|
|point < 50|5０点より低いデータ|
|point <> 100|１００点以外のデータ|

* 取得結果

|user_id|point|
|--:|--:|
|1|100|
|2|90|

ユーザーID1,2のユーザーが国語で８０点以上をとったということがわかりました。
ただ、よくあるユースケースではユーザーIDだけではなく名前も欲しくなりますよね？
追加でusersから`id in (1, 2)`と検索することで取得できますが、人数が多いと大変なのでもっと手軽に取得したいですよね。
ということで次は複数のテーブルからデータを取得する方法を説明します。

## 複数テーブルからデータ抽出


複数のテーブルを結合することで各テーブルの情報をまとめて取得することができます。

### テーブル結合の構文

`from`のところで結合したいテーブルを`inner join [テーブル名] on [結合条件]`で指定します。

```sql
select
  [参照したいカラム1, 参照したいカラム２, ...]
from
  [テーブル名]
  inner join [結合テーブル１] on [結合条件]
  inner join [結合テーブル2] on [結合条件]
  ...(結合したいテーブルの数だけ記述)
where
  [抽出条件]
```

結合条件にはレコードを結合する条件を書きます。
例えばusersとuser_examsでは、users.idとuser_exams.user_idが同じユーザーのことを示しているので下記のように指定します。

```sql
from user_exams
  inner join users on users.id = user_exams.user_id
```

上記のように結合させると結合後のテーブルは次のようなイメージになります。

|user_exams.exam_id|user_exams.user_id|user_exams.point|users.id|users.name|users.email|
|--:|--:|--:|--:|:--|:--|
|1|1|100| 1|伊藤一郎|ito1@example.com|
|1|2|90|2|伊藤次郎|ito2@example.com|
|1|3|65|3|鈴木太郎|suzuki@example.com|
|1|4|70| 4|佐藤花子|sato@example.com|
|2|1|100| 1|伊藤一郎|ito1@example.com|
|2|2|60|2|伊藤次郎|ito2@example.com|
|2|3|95|3|鈴木太郎|suzuki@example.com|
|2|4|80| 4|佐藤花子|sato@example.com|
|3|1|100| 1|伊藤一郎|ito1@example.com|
|3|3|60|3|鈴木太郎|suzuki@example.com|
|4|1|100| 1|伊藤一郎|ito1@example.com|
|4|2|55|2|伊藤次郎|ito2@example.com|
|4|4|80| 4|佐藤花子|sato@example.com|

user_examsに結合条件であるuser_exams.user_idとusers.idが一致するレコードがくっつくイメージです。

### users, user_exams, examsの結合
先ほどの例と同じでuser_examsから国語(exam_id=1)で80点以上のユーザーIDを取得する場合を考えます。
今回はユーザー名が知りたいのでusersテーブルを結合します。ついでに試験名も取得するためにexamsも結合します。

```sql
select
  users.id, users.name, user_exams.point, exams.name
from
  user_exams
  inner join users on users.id = user_exams.user_id
  inner noin exams on user_exams.exam_id = exams.id
where
  user_exams.exam_id = 1
  and user_exams.point >= 80
```

１テーブルの時と違い、カラム名に`[テーブル名].`をつけています。
１テーブルの場合はテーブル名を指定しなくてもどのテーブルのカラムかわかるので省略できるのですが、複数テーブルある場合は同名のカラムがある可能性があるのでテーブル名を指定します。
ということなので、どのテーブルとも被っていないカラムはテーブル名は省略可能です。ただ、最初のうちはわかりやすさのためにもテーブル名は指定するものと覚えても良いかもしれません。

抽出結果は下記の通り

|users.id|users.name|user_exams.point|exams.name|
|--:|:--|--:|:--|
|1|伊藤一郎|100|国語|
|2|伊藤次郎|90|国語|

## 並び順

抽出するデータの並び順を指定することができます。
下記のように`order by`で指定します。

```sql
select
  [参照したいカラム1, 参照したいカラム２, ...]
from
  [テーブル名]
where
  [抽出条件]
order by
  [ソートカラム] [ソート条件], [ソートカラム2] [ソート条件],...
```

例えばuser_examsで点数の高い順に並べたい場合は下記のように書きます。点数が同点の場合はuser_idの昇順にします。
ソートカラムに指定した1つ目のカラムで並び替えますが、もし同じ値の場合は2つ名のソートカラムでさらに並び替えます。もしそこも同じ値であれば3つ目のソートカラム、いうように同値がある限り次のソートカラムを参照して並べようとします。
もし同じ値があるのに次のソートカラムが指定されていない場合は取得順は不定になります。

```sql
select
  exams_id, user_id, point
from
  user_exams
order by
  point desc, user_id asc
```

ソート条件に指定している`desc`は降順を指定しています。`asc`は昇順です。
このSQLを実行すると下記のように出力されます。

|exam_id|user_id|point|
|--:|--:|--:|
|1|1|100|
|2|1|100|
|3|1|100|
|4|1|100|
|2|3|95|
|1|2|90|
|2|4|80|
|4|4|80|
|1|4|70|
|1|3|65|
|2|2|60|
|3|3|60|
|4|2|55|

point=100のデータは全てのuser_id=1であり、orderで指定されたカラムだけでは順番が確定できないので、順番は不定になります。
もし順番を確定させるのであれば、ソートカラム3にexam_idを指定するなどして順番が確定するようにします。

## 集計
SQLではデータ数などの集計情報を取得することができます。
集計を詳しく説明するととても複雑なのでここでは単純なデータ数の取得だけ触れることにします。興味がある方は別途調べてみてください。

### データ数をカウントする

データ数をカウントします。例えばuser_examsで80以上のデータ数をカウントする場合は下記のSQLを書きます。

```sql
select
  count(*)
from
  user_exams
where
  point >+ 80
```

結果は下記の通り

|count(*)|
|--:|
|8|

# 最後に

なんやかんやで長くなってしまいましたが、単純に抽出するだけのSQLであれば比較的簡単に理解できたと思います。
できる限り具体例を交えて説明しましたが、習得するには実践あるのみだと思いますのでガンガンSQLを使っていってください。
簡単に書いたつもりではいるのですが、SQLを知っている人（私）が書いているので知らない人には意味がわかりづらい表現もあると思います。
そういうところはぜひコメントなどしていただければと思います。
できる限り改善したいと思います！

この記事では基本的なことしか書いていません。SQLでデータ抽出をよくやるようになると書いてあることだけではできない様々な抽出をやりたくなると思います。
そうなったときにはすでにSQL初心者は脱していると思うので、次のステージへステップアップしていってください！