【新人教育 資料】第6章 SQLへの道 〜ソート編〜

# 【新人教育 資料】第6章 SQLへの道 〜ソート編〜
## あらすじ
新人がいっぱい入ってくる。新人のレベルもバラバラ。教育資料も古くなっているので、更新しましょう。
どうせなら、公開しちゃえばいいじゃん。という流れになり、新人教育用の資料を順次更新していくことにしました。

※後々、リクエストに応じて更新することが多いのでストックしておくことをおすすめします。

自分は[TEMONA株式会社](http://temona.co.jp/)でCTOをしていますが、頭でっかちに理論ばっかり学習するよりは、イメージがなんとなく掴めるように学習し、実践の中で知識を深めていく方が効率的に学習出来ると考えています。

※他の登壇やインタビュー記事は[Wantedly](https://www.wantedly.com/users/105935)から見てください。

教育スタイルとしては正しい事をきっちりかっちり教えるのではなく、未経験レベルの人がなんとなく掴めるように、資料を構成していきます。

以下のようなシリーズネタで進めます。

|No.|記事|
|:-----------|-----------|
|1|[【新人教育 資料】第1章 SQLへの道 〜DB編〜](http://qiita.com/devopsCoordinator/items/9b70e506150888e190be)|
|2|[【新人教育 資料】第2章 SQLへの道 〜3値論理編〜](http://qiita.com/devopsCoordinator/items/9c10410b50f8fcc2ba79)|
|3|[【新人教育 資料】第3章 SQLへの道 〜基本数学編〜](http://qiita.com/devopsCoordinator/items/08e405a9c58cc203d016)|
|4|[【新人教育 資料】第4章 SQLへの道 〜SQL基本操作編〜](http://qiita.com/devopsCoordinator/items/0f742158caf0fdb533e3)|
|5|[【新人教育 資料】第5章 SQLへの道 〜絞込編〜](http://qiita.com/devopsCoordinator/items/dca5aa9c155292f1c6ea)|
|6|[【新人教育 資料】第6章 SQLへの道 〜ソート編〜](http://qiita.com/devopsCoordinator/items/8cad032aa035dc1cdbc0)|
|7|[【新人教育 資料】第7章 SQLへの道 〜集合関数（SUM、MAX、MIN、AVG、COUNT）編〜](http://qiita.com/devopsCoordinator/items/85918c8508dddf19586f)|
|8|[【新人教育 資料】第8章 SQLへの道 〜グループ編〜](http://qiita.com/devopsCoordinator/items/f74228615982932c914b)|
|9|[【新人教育 資料】第9章 SQLへの道 〜結合編〜](http://qiita.com/devopsCoordinator/items/3ba154429973a4ddc9e0)|

では、今回もはじめていきましょう！

# SQL ソート ORDER BY
ソートは、データの並べ替えのことです。昇順、降順のことですね。
めちゃくちゃ大事な事が標準SQL規格99に書いてあります。

mariadbのドキュメントを参考に見てみると
https://mariadb.com/kb/en/sql-99/order-by-clause/

```
An ORDER BY clause may optionally appear after a <query expression>: it specifies the order rows should have when returned from that query (if you omit the clause, your DBMS will return the rows in some random order). The query in question may, of course, be a VALUES statement, a TABLE statement or (most commonly) a SELECT statement. (For now, we are ignoring the possibility that the SQL statement is DECLARE CURSOR, since that is strictly a matter for "SQL in host programs", a later chapter.)
```

要約すると**ORDER BY で並び替えを明示しない限り、RMDBSの実装に委ねることになる**ということです。
特にPostgreSQLは追記型DBのアーキテクチャを採用しているので、ORDER BY でソートを明示しないと
びっくりするぐらい順序が変わってしまいます。

データベースの管理として追記型DBでは、レコードに更新があった場合に、元々あったレコードを利用するのではなく、前のあったレコードとは別に新しいレコードを追加させて、前のレコードのポインタ(位置情報だけを)を消すようなアーキテクチャです。

今回は紹介しませんが
PostgreSQLでは、あるテーブルをORDER BY 句でソートキーを明示せずにSELECTし、結果を取得した後、特定のレコードをUPDATE文で更新をした後、再度SELECTすると結果の順序が変わるのが確認出来ると思います。

では実際にSQLとしてどのようにソートを表現するのか見ていきましょう。

## 事前準備(終わっていない人は実行)
前回同様に、勉強用のレポジトリを用意しているので
下記のレポジトリをForkして、Readmeを参考に環境構築をしてください。
https://github.com/TEMONA/mysql_study

※全てにおいて自己責任でお願いします。

上記の事前準備が終わっていることとして、下記の説明を進めていきます。

```
$ mysql -u root
mysql> use mysql_study;
```

# ORDER BY

```sql
SELECT * FROM テーブル名 ORDER BY ソートしたいカラム[,ソートしたいカラム] 並び順の指定
```

| 指定 | 説明 |
| ---- | ---- |
| ASC | 昇順 |
| DESC | 降順 |

並び順の指定を省略した場合はASCがデフォルトです。
複数のソートキーも対応しています。はじめに書くほど優先でカラムが実行されます。

弊社が用意したレポジトリのテストデータを利用した場合には
ユーザテーブルに1,000件データが入っているので、例にとりみていきましょう。

今までに教えていないところも触れながらいきます。

### SELECT COUNT　データの件数を数える
データの件数を数えます。

```sql
select count(*) from テーブル名;
```

実際に実行してみましょう

```sql
select count(*) from users;
```

![image](https://qiita-image-store.s3.amazonaws.com/0/74153/62479d5d-04c5-796c-afa9-c09c7e303d90.png)


## LIMIT 取得する件数を表示する

```sql
select * from テーブル名 LIMIT 取得件数;
```

実際にやってみましょう。

```sql
select * from users LIMIT 10;
```

画像を見て頂くとわかりますが、ageの列をみると、並びはバラバラですね。
![image](https://qiita-image-store.s3.amazonaws.com/0/74153/a29d34e6-9787-91a4-784d-893ef1f7236e.png)

## ORDER BY 並び順を省略してみる

```sql
select * from users ORDER BY age LIMIT 10;
```

![image](https://qiita-image-store.s3.amazonaws.com/0/74153/f3e52f88-a6fd-0c00-de9d-6b17ae523fed.png)

## ORDER BY ASC

```sql
select * from users ORDER BY age LIMIT 10;
```

![image](https://qiita-image-store.s3.amazonaws.com/0/74153/29e0f4ac-cec7-52ac-b15a-812a423a02a1.png)

# DESCRIBE
テーブルの定義情報を確認することが出来るのがDESCRIBE文です。

```sql
DESCRIBE テーブル名;
```

![image](https://qiita-image-store.s3.amazonaws.com/0/74153/c89b5964-13bd-37fa-7278-00c16cfcb14d.png)

上記の図で説明すると
左のカラムから「Field(カラム名)」、「Type(データ型)」、「Null(null制約)」、「Default(初期値)」、「Extra(その他)」となります。

RMDBSの種類によってDESCRIBE文の構文は違うので注意してください。

# EXPLAIN
ここからは少しコストの話しも含めて理解するために「EXPLAIN」というSQLをご紹介します。
RMDBSではオプティマイザという機能があり、データの分布情報を示す、統計情報に合わせてRMDBSの内部で処理の最適化を行ってくれます。EXPLAINを使う事で内部の実行コストがある程度分かります。

## EXPLAIN 出力フォーマット

| カラム | 	意味 |
| ---- | ---- |
| id | SELECT 識別子。 |
| select_type | SELECT 型 |
| table | 出力行のテーブル |
| partitions | 一致するパーティション |
| type | 結合型 |
| possible_keys | 選択可能なインデックス |
| key | 実際に選択されたインデックス |
| key_len | 選択されたキーの長さ |
| ref | インデックスと比較されるカラム |
| rows | 調査される行の見積もり |
| filtered | テーブル条件によってフィルタ処理される行の割合 |
| Extra | 追加情報 |

詳細のところはまたどこかで説明するとして今回紹介しておきたいのは

「**type**」です。

| 種類 | 説明 |
| ---- | ---- |
| const | PRIMARY KEYまたはUNIQUEインデックスのルックアップによるアクセス。最速。|
| eq_ref | JOINにおいてPRIARY KEYまたはUNIQUE KEYが利用される時のアクセスタイプ。constと似ているがJOINで用いられるところが違う。|
| ref | ユニーク（PRIMARY or UNIQUE）でないインデックスを使って等価検索（WHERE key = value）を行った時に使われるアクセスタイプ。|
| range | インデックスを用いた範囲検索。|
| index | フルインデックススキャン。インデックス全体をスキャンする必要があるのでとても遅い。|
| ALL | フルテーブルスキャン。インデックスがまったく利用されていないことを示す。OLTP系の処理では改善必須。|

詳細は以下のページを御覧ください。
[MySQL 5.6 リファレンスマニュアル  /  ...  /  EXPLAIN 出力フォーマット](http://dev.mysql.com/doc/refman/5.6/ja/explain-output.html#explain-join-types)

実際にEXPLAIN文を追加してSQLクエリを実行していきましょう。

## 主キーをソートキーに指定してみる

```sql
EXPLAIN SELECT * FROM users ORDER BY id DESC  LIMIT 10 ;
```

「type」が「index」になっていますね。
「rows」が「10」になっていますね。

これは索引となるINDEXだけをみたので、検索時に触った件数が「10」件だった事が読み取れます。

![image](https://qiita-image-store.s3.amazonaws.com/0/74153/9cd60722-e5b4-4796-e579-0e048f4b1883.png)

## 主キー以外をソートキーに指定してみる
では主キー以外をソートキーに指定してみるとどうでしょうか？

```sql
EXPLAIN SELECT * FROM users ORDER BY name DESC  LIMIT 10 ;
```

「type」が「all」になっていますね。
「rows」が「100」になっていますね。
「Extra」が「Using filesort」になっていますね。

これは索引となるINDEXだけが存在しなかったため、テーブルの情報を全件検索した後に、ソート(並び替え)を行ってから、結果を表示していることが読み取れます。名前のカラムに対して降順で10件取得したいだけなのに、わざわざテーブルの全件を検索してから、並び替えた後に検索結果を10件と絞り込んでいます。

この**テーブルの全件を検索してから**という箇所が大変やっかいな話しなのです。

[【新人教育 資料】ハードウェア編](http://qiita.com/devopsCoordinator/items/1aa4a76072a61b15c56f)で紹介した、まな板や冷蔵庫の話で説明します。

RMDBSはパフォーマンスを最大化するためにハードウェアのリソース(CPU,メモリ,HDD)を頑張って利用する設計に大抵はなっていますが、このテーブル上のデータを律儀に全件を検索してからというのは、モノを取り出すのに遅い、冷蔵庫から毎回データを取り出していることになります。

しかも10件だけ表示したいのに、テーブルの全件を検索するなんて、とてもナンセンスですよね。
100件だけだったら、何も差を感じないと思いますが、これがテーブルに保持されているレコード件数が1000万件だったらどうでしょうか？想像してみてください。

![image](https://qiita-image-store.s3.amazonaws.com/0/74153/dcefece7-022a-e20b-653d-79cf0cf01e67.png)

最近のRMDBSのオプティマイザもだいぶ頭が良くなっていますが、解決するには色々チューニングも含めて対応していく必要があります。

※少しむずかしい話もしってみたいという人は以下のページをよく読んでみてください。
[8.2.1.15 ORDER BY の最適化](http://dev.mysql.com/doc/refman/5.6/ja/order-by-optimization.html)

# 照合順序というお話

あとで書きます。

# 演習

- データ量の多いareasテーブルで色々ソートしてみましょう。
- [【新人教育 資料】第5章 SQLへの道 〜絞込編〜](http://qiita.com/devopsCoordinator/items/dca5aa9c155292f1c6ea)で紹介しているSQLをEXPLAINで実行してみましょう。

# 参考文献
mysql5.6 リファレンスマニュアル: https://dev.mysql.com/doc/refman/5.6/ja/

# あとがき
この章で紹介しましたが、ORDER BY句がない場合は取得順序に保証がありません。特にPostgreSQLのような追記型だと如実に困るのでORDER BY句は必ず入れるようにしましょう。例えば、何かのデータの表示をID降順で表示させたい場合には、UPDATE文が実行されしまうと、表示順序が変わってしまいます。ORDER BYを明示で入れることが必要です。

実際にはDBをデータ移行する自体が発生しましたとか、マスタ用のデータで表示順序を変えたいみたいな要望が出たりするので
データ保持用のカラム以外に、表示順序番号みたいなカラムを別途用意し、意図的に表示順序変えられるようにするなどの考慮が必要になってきます。

次回は「【新人教育 資料】第7章 SQLへの道 〜集合関数（SUM、MAX、MIN、AVG、COUNT）編〜」をお送りする予定です。

明日は[ヒカラボ](https://atnd.org/events/73992)に登壇する予定です。もしかしてこの記事を見てくれている人もお会いするかもしれないですね。もし見たって人がいれば声をかけてください。