【技術書まとめ】『達人に学ぶSQL徹底指南書』のまとめ

同じ著者の『SQL 第2版 ゼロからはじめるデータベース操作』を読んだのでさらにSQLを理解するために読みました。

## 対象

この書籍の対象は半年から一年ほどSQLを書いている中級者とのことです。またSQLとはそもそもなんなのかを知りたい人にとっても有益です。

# 第１部

## 魔法のSQL

### `case`式

`case`は結局ラベルの読み替えです。他のコードと組み合わせることで絶大な効果を発揮します。

- 単純`case`式

```sql:単純`case`式
case sex
  when '1' then '男'
  when '2' then '女'
else 'その他' end
```

- 検索`case`式
  - 基本的にはこちらを使う

```sql:検索`case`式
case when sex = '1' then '男'
  when sex = '2' then '女'
else 'その他' end
```

- case式は見つかった時点で打ち切られる
  - 以降は無視される
- **注意点**
  - 戻り値のデータ型をそろえる
    - 最終的には一つの値に定まるから
  - `end`の書き忘れ
  - `else`は必ず書く
    - 書かないと`else null`となって結果が変わる
      - エラーにならないので厄介

- 県ごとの数値を地方ごとに変換して集計する

```sql:県ごとの数値を地方ごとに変換して集計する
select case pref_name
    when '徳島' then '四国'
    when '香川' then '四国'
    when '愛媛' then '四国'
    when '高知' then '四国'
    when '福岡' then '九州'
    when '佐賀' then '九州'
    when '長崎' then '九州'
  else 'その他' end as district,
  sum(population)
from PopTbl
group by district;
```

- `where`で出し分けるものを`select`を使って一発で表示する
    - `1`の男性と`2`の女性人口を集計したい
    - レコード集約のための`sum`

```sql:`where`で出し分けるものを`select`を使って一発で表示する
select pref_name
    sum(case when sex = '1' then population else 0 end) as cnt_m,
    sum(case when sex = '2' then population else 0 end) as cnt_f
  from PopTbl2
group by pref_name;
```

- 条件分岐させた`update`

```sql:条件分岐させた`update`
update Personnel
  set salary = case when salary >=300000
                      then salary * 0.9
                    when salary > 250000 and salary < 280000
                      then salary * 1.2
               else salary end;
```

- テーブル同士のマッチング
    - `where`を使わず`select`で絞り込む

```sql:テーブル同士のマッチング
select CM.course_name,
    case when exists (select course_id from OpenCourses OC
                            where month = 201806
                            and OC.course_id = CM.course_id)
        then 'o' else 'x' end as '6月',
    case when exists (select course_id from OpenCourses OC
                            where month = 201807
                            and OC.course_id = CM.course_id)
        then 'o' else 'x' end as '7月',
    case when exists (select course_id from OpenCourses OC
                            where month = 201808
                            and OC.course_id = CM.course_id)
        then 'o' else 'x' end as '8月'
  from CourseMaster CM;
```

- `case`の中で集約関数を使う
    - `having`を使わず`select`で分岐させる

```sql:`case`の中で集約関数を使う
select std_id,
    case when count(*) = 1
      then max(club_id)
    else max(case when main_club_flg = 'Y'
               then club_id
             else null end) end as main_club
  from StudentClub
group by std_id;
```


## 必ずわかるウィンドウ関数

- ウィンドウとは
   1. `partition by`によるレコード集合のカット
   2. `order by`による順序づけ
   3. フレーム句によるカレントレコードを中心としたサブセットの定義


## 自己結合の使い方

- 自己結合は非等値結合と組み合わせて使う

- 同じ値段だけど、商品名が違うレコードを検索するSQL

```sql:同じ値段だけど、商品名が違うレコードを検索するSQL
select distinct P1.name, P1.price
  from Products P1 inner join Products P2
    on P1.price = P2.price
  and P1.name <> P2.name
order by P1.price;
```

## 3値論理とNULL

SQLは`NULL`があるため３値論理となる。この項は理解度が低かったので、また読み直す必要がある。

- `NULL`とは
    - サングラスをかけた人の目の色
    - 冷蔵庫の目の色
- `NUll`が入っていた場合の困ったことの事例
    - `not in`サブクエリで`NULL`が入ると結果は常に空となる

1. `NULL`は値ではない
2. 値でないから述語もうまく適用できない
3. 無理やり適用すると`unknown`となる
4. `unknown`が入るとSQLが直感に反する動作となる
5. 段階的なステップに分けてSQLの動作を追うことが大切

> 最善の対策を挙げるなら、テーブルにNOTNULL制約を付けて極力NULLを排除すること


## `EXISTS`述語の使い方

述語とは**戻り値が真偽値となる関数**のこと。`EXISTS`はド・モルガンの法則とともに効果的に使える。

> ド・モルガンの法則
∀xPx=¬∃x¬Px
（すべてのxが条件Pを満たす＝条件Pを満たさないxが存在しない）
∃xPx=¬∀x¬Px
（条件Pを満たすxが存在する＝すべてのxが条件Pを満たさないわけではない）

- テーブルに存在**しない**データを探す

> 考え方としては、全員が皆勤したと仮定した場合の集合を作り、そこから現実に出席した人々を引き算すればよいわけです。


```sql:欠席者だけを求めるクエリ：その1存在量化の応用
select distinct M1.meeting, M2.person
    from Meetings M1 cross join Meetings M2
  where not exists
    (select *
        from Meetings M3
      where M1.meeting = M3.meeting
        and M2.person = M3.person);
```

- 「すべての行について～」という全称量化の表現を、「～でない行が1つも存在しない」という二重否定文へ変換する

```sql:「すべての教科が50点以上である」→「50点未満である教科が1つも存在しない」
select distinct student_id
    form TestScores TS1
  where not exists
    (select *
        from TestScores TS2
      where TS2.student_id = TS1.student_id
        and TS2.score < 50);
```

- `exists`だけが行の集合を引数にとる
- その点で`exists`は高階関数の一種とみなせる
- `SQL`には全称量化子に相当する演算子がないので、`not exists`で代用する。

> SQLの述語のほとんどが3値論理に従う中で、EXISTSだけは2値論理的に振る舞うという奇妙な仕様になっている

そのため`unknown`も`false`となる。よって条件をつくるときは`NULL`を考慮しないといけません。


## `HAVING`句の力

`SQL`は集合指向で考えられた言語です。

> 最初に学んだ言語のスキーマ（概念の枠組み）が心理的モデルとして固定され、それを通して世界を見るようになるため、異なるスキーマを持つ言語の理解が妨げられる

HAVING句は集合の性質を調べる道具となります。また現在の標準SQLではHAVING句を単独でも使えます。

> HAVING句は集合の性質を調べる道具として使えます。特に集約関数やCASE式と組み合わせたときの記述力は強力無比です。

HAVING句は円（ベン図）を書くとわかりやすいです。

> HAVING句で集合を切り分けて問題を解く際には、紙の上に円を描いてみるのが非常に効果的です

考え方は、何を持って集合と見なすかに着目します。

> WHERE句が集合の要素の性質を調べる道具であるのに対し、HAVING句は集合自身の性質を調べる道具である。

`where`は要素の性質を、`having`は集合自身の性質を調べます。

> ・実体1つにつき1行が対応している➡要素なのでWHERE句を使う。
・実体1つにつき複数行が対応している➡集合なのでHAVING句を使う。


### ループや代入を使わないで`group by`や`all`で最頻値を求める
- 最頻値を求めるALL述語の利用

```sql:最頻値を求めるALL述語の利用
select income, count(*) as count
    from Grauates
  group by income
having count(*) >= all (select count(*)
                            from Graduates
                          group by income);
```

### NULLを含まないものを選択する

`count`の二つの種類

> COUNT(*)がNULLを数えるのに対し、COUNT(列名)は他の集約関数と同様、NULLを除外して集計するという結果の違いです。

`=`であれば`NULL`は含まれていない。

```sql:countを使って、提出日にNULLを含まない学部を選択する
select dpt
    from Students
  group by dpt
having count(*) = count(sbmt_date);
```

`case`を使うと？

```sql:caseを使って、提出日にNULLを含まない学部を選択する
select dpt
    from Students
  group by dpt
having count(*) = sum(case when sbmt_date is not null
                        then 1 else 0 end);
```


### 特性関数を使いこなして、どんな複雑な条件でも記述する

- クラスの75％以上の生徒が80点以上のクラスを選択せよ。

```sql:クラスの75％以上の生徒が80点以上のクラスを選択せよ。
select class
    from TestResults
  group by class
having count(*) * 0.75
    <= sum(case when score >= 80
            then 1
            else 0 end);
```

- 50点以上を取った生徒のうち、男子の数が女子の数より多いクラスを選択せよ。

```sql:50点以上を取った生徒のうち、男子の数が女子の数より多いクラスを選択せよ。
select class
    from TestResults
  group by class
having sum(case when score >= 50
                      and sex = '男'
              then 1
              else 0 end)
     > sum(case when score >= 50
                      and sex = '女'
              then 1
              else 0 end);
```

- 女子の平均点が、男子の平均点より高いクラスを選択せよ。
    - 女子だけのクラスの場合を考慮する
        - `null`になると選択対象外になることを使う

```sql:女子の平均点が、男子の平均点より高いクラスを選択せよ。
select class
    from TestResults
  group by class
having avg(case when sex = '男'
              then score
              else null end)
     < avg(case when sex = '女'
              then score
              else null end);
```

`SQL`は集団の特徴傾向を把握するのに向いています。

> 今回の例題でも、私たちが考えたのは、あくまでクラスが集団として持つ特徴や傾向性であって、誰が何点を取ったかという生徒の個人情報は詮索しません。構成員の匿名性を保ったまま集団の傾向を把握するこの考え方は、そのまま統計学の方法論でもあります。BIとSQLの親和性が高いのもうなずける話です。


### 全称文を集合で表現する

####  状態が「待機」の行数と、集合全体の行数が一致するグループを選択する

- `count`を使う
    - 総数と数値に変換したステータスとを比較する

> この2つだけが持っていて、他の集合が持っていない性質とは何でしょう。それは、状態が「待機」の行数と、集合全体の行数が一致する、という性質です。

```sql:全称文を集合で表現する(COUNT)
select team_id
    from Teams
  group by team_id
having count(*) = sum(case when status = '待機'
                        then 1
                        else 0 end);
```

- `MIN`と`MAX`を使う
    - 同じであればその1種類の数値のみ

```sql:総員スタンバイかどうかをチームごとに一覧表示
select team_id,
    case when max(status) = '待機' 
          and min(status) = '待機'
        then '総員スタンバイ'
        else '隊長！ メンバーが足りません'
        end as status
          from Teams group by team_id;
```


### 一意集合と多重集合

- 資材のダブっている拠点を選択する

```sql:havingを使って資材のダブっている拠点を選択する
select center
    from Materials
  group by center
having count(material) <> count(distinct material);
```

```sql:existsを使って資材のダブっている拠点を選択する
select center, material
    from Materials M1
  where exists (select *
                    from Materials M2
                  where M1.center = M2.center
                    and M1.receive_date <> M2.receive_date
                    and M1.material = M2.material);
```


### バスケット解析

```sql:ビールと紙オムツと自転車をすべて置いている店舗を検索する
--商品マスタと店舗在庫テーブルを結合した結果が3行になる店舗が選択される
select SI.shop
  from ShopItems SI inner join Items I
    on SI.item = I.item
  group by SI.shop
having count(SI.item) = (select count(item)
                          from Items);
```

### `HAVING`句のまとめ

HAVING句はCASE式や自己結合などと組み合わせた時に真価を発揮します。

> HAVING句もまた集合指向言語の強力な武器の1つです。そしてその真価は、CASE式や自己結合といった他の武器と組み合わせたときに発揮されるのです。

- 集合の性質を調べるための条件の使い方一覧

```sql:集合の性質を調べるための条件の使い方一覧
-- colの値が一意である
count (distinct col) = count(col)

-- colにNULLが存在しない
count(*) = count(col)

-- colは歯抜けのない連番(開始値は1)
count(*) = max(col)

-- colは歯抜けのない連番(開始値は任意の整数)
count(*) = max(col) - min(col) + 1

-- colが1つだけの値を持つか、またはNULLである
min(col) = max(col)

-- すべてのcol_xの符号が同じである
min(col) * max(col) > 0

-- 最大値の符号が正で最小値の符号が負
min(col) * max(col) < 0

-- colは少なくとも1つのゼロを含む
min(abs(col)) = 0

-- colの最大値と最小値が指定した定数から同じ幅の距離がある
min(col - 定数) = - max(col - 定数)
```


## ウィンドウ関数で行間比較を行なう

相関サブクエリでやっていたものをウィンドウ関数で行ないます。相関サブクエリはネストが深く読みにくく、パフォーマンスも落ちるためです。

- 成長、後退、現状維持を一度に求める
    - チャートランキングのようなもの

```sql:成長、後退、現状維持を一度に求める
select year, current_sale as sale,
    case when current_sale = pre_sale
      then '→'
      when current_sale > pre_sale
      then '↑'
      when current_sale < pre_sale
      then '↓'
    else '-' end as var
  from (select year,
          sale as current_sale,
          sum(sale) over (order by year
                    range between 1 preceding
                    and 1 preceding) as pre_sale
        from Sales) TMP
order by year;
```

`sum`は見かけだけで実際集約はしません。

> SUM(sale)という形でSUM関数を使っているようにも見えますが、実はこれは見かけだけのことで、本物の（というのも妙な表現ですが）集約関数のSUMのようにテーブルのレコード数を減らす（集約する）動作はしないのです

またウィンドウ関数は物理的なレコードの順序で集計範囲を設定できます。

> ウィンドウ関数の場合、……フレーム句でRANGEをROWSに変えることで、物理的なレコードの順序で集計範囲を設定できる

- ウィンドウ関数 vs 相関サブクエリ
    - ウィンドウ関数ではサブクエリを単体で実行できる
        - 理解しやすい
    - テーブルへのスキャンも一回なのでパフォーマンスも良い

- 各商品分類について、平均単価より高い商品を選択する

```sql:各商品分類について、平均単価より高い商品を選択する
select shohin_mei, shohin_bunrui, hanbai_tanka
    from (select shohin_mei, shohin_bunrui, hanbai_tanka,
                  avg(hanbai_tanka)
                    over(partition by shohin_bunrui) as avg_tanka
              from Shohin) TMP
  where hanbai_tanka > avg_tanka;
```

- ウィンドウ関数は元のテーブルに列として結果を追加するだけ
    - あとは比較すれば良い

> このウィンドウ関数の結果のすばらしい（都合の良い）ところは、商品分類ごとの平均単価を計算していながらも、レコードを集約せずそのまま元のテーブルに列として結果を追加するだけ、という情報保全性が働くところです。


## 外部結合の使い方

本来`SQL`は帳票作成のための言語ではないが実際にはよく利用されています。外部結合を使って表を成形する便利な方法もたくさんあります。

- 行→列に水平展開する
    - `case`を使う

```sql:行→列に水平展開する(case式)
select name,
    case when sum(case when course = 'SQL入門'
                    then 1 else null end) = 1
        then 'o' else null end as 'SQL入門',
    case when sum(case when course = 'UNIX基礎'
                    then 1 else null end) = 1
        then 'o' else null end as 'UNIX基礎',
    case when sum(case when course = 'Java中級'
                    then 1 else null end) = 1
        then 'o' else null end as 'Java中級'
  from Courses
group by name;
```

- 列→行に変換する
    - `union all`を使う

```sql:列→行に変換する(UNIONALL)
select employee, child_1 as child from Personnel
union all
select employee, child_2 as child from Personnel
union all
select employee, child_3 as child from Personnel;
```

- 表側を入れ子にしたい
    - 外部結合を一度にまとめる

>表側を入れ子にするときは、その形のマスタをあらかじめ用意してやればよい


- 商品ごとに総計でいくつ売れたかを調べる帳票を出力する
    - `1 : 多`と考えて`group by`でまとめる

```sql:商品ごとに総計でいくつ売れたかを調べる帳票を出力する
select I.item_no, sum(SH.quantity) as total_qty
        from Items I left outer join SalesHistory SH
            on I.item_no = SH.item_no
    group by I.item_no;
```

> 結合は、一対一でなくとも一対多ならば行数は（不当には）増えない



## SQLで集合演算

### 注意点

- 重複を認める時は`UNION ALL`を使う
    - ALLオプションを付けるとソートが行なわれないのでパフォーマンスが向上する


> SQLの集合演算子にも、重複を認めるバージョンと認めないバージョンの2通りが用意されています。通常、UNIONやINTERSECTをそのまま使うと、結果から重複行を排除します。もし重複行を残したい場合は、ALLオプションを付けて、UNIONALLのように記述します。

- 演算の順番に優先順位がある
    - UNIONとEXCEPTに対して、INTERSECTのほうが先に実行される
- 除算の標準的な定義がない

### テーブル同士の比較

- `UNION`は冪等性をもつ
    - 繰り返し処理を実行しても、一度だけ実行した場合と結果が同じになる
        - インストールとアンインストール
    - 注意
        - `UNION ALL`は変化する
        - 重複があれば`UNION`も変化する
    - 主キーは大事


### 相等性をチェックするには？

- 「AとBは互いに等しい」
    - `A union B = A intersect B`で交差を使う
        - AとBが違えば必ず`union`の方が多くなる
            - `(A union B) except (A union B)`が空集合かどうかを調べるとわかる
                - 列数や列名関係ない
                - `null`を含むテーブルにも使える
                - 事前準備がいらない

```sql:「等しい」か「異なる」を返すクエリ
select case when count(*) = 0
        then '等しい'
        else '異なる' end as result
    from ((select * from tbl_A
        union
        select * from tbl_B)
        except
        (select * from tbl_A
        intersect
        select * from tbl_B)) TMP;
```

- 違うことがわかったらその行を表示する

```sql:テーブル同士のdiff
(select * from tbl_A
except
select * from tbl_B)
union all
(select * from tbl_B
except
select * from tbl_A);
```

## SQLで数列を扱う

もともと`SQL`は順序の概念がなかったので手続き的に扱うことが難しかったのが、`ウィンドウ関数`によってスムーズにできるようになってきました。

- 数を集合ととらえることで連番を作れる
    - ビューとして保存しておくと後々便利
        - 差集合として欠番を洗い出すこともできる
            - `EXCEPT`や`NOT IN`などを使う
- 差集合の4通り
    - 王道の`except`
    - わかりやすい`not in`
    - `not in`と考え方が同じ'not exists`
    - ひねくれ者の`外部結合`

### 連番（シーケンス）を見つける

1. 自己結合で始点と終点の組み合わせを作る
    - S2.seat = S1.seat + (:head_cnt -1)
2. 始点-終点間の全ての点が満たすべき条件を記述する
    - すべての行の座席の状態が「空」であること
        - 条件Pを満たさない行が存在しない
            - `not exists`と`S3.status <> '空'`

```sql:notexistsを使って人数分の空席を探す
select S1.seat as start_seat, '〜',
       S2.seat as end_seat
        from Seats S1, Seats S2
    where S2.seat = S1.seat + (:head_cnt -1)
        and not exists
            (select *
                    from Seats S3
                where S3.seat between S1.seat and S2.seat
                    and S3.status <> '空');
```

ウィンドウ関数ではどうなるか？

- `終点 - 始点 = 2`となることを使う

```sql:ウィンドウ関数を使ってシーケンスを探す
select seat, '〜', seat + (:head_cnt- 1)
        from (select seat,
                max(seat)
                    over(order by seat
                        rows between (:head_cnt - 1) following
                            and (head_cnt - 1) following) as end_seat
                    from Seats
                where status = '空') TMP
    where end_seat - seat = (:head_cnt - 1);
```

- 特定の取引日における株価が上昇しているか否か

```sql:特定の取引日における株価が上昇しているか否か
select deal_date, price
        case sign(price - max(price)
            over(order by deal_date
                rows between 1 preceding
                    and 1 preceding))
            when 1 then 'up'
            when 0 then 'stay'
            when -1 then 'down' else null end as diff
    from MyStock;
```

`SQL`上達に欠かせないのは、「集合」と「述語」に親しむことです。

- `SQL`のデータの扱い方
    - 順序を無視した集合とみなす方法
    - 順序を持った集合とみなす
        - 基本的にはウィンドウ関数を使う
    - 「すべての〜」を表現するには`not exists`を使う


## `SQL`を速くするぞ

`SQL`の家庭の医学。ちょっと遅いなと感じたときの初期診断ツール。低速ストレージへのアクセスを減らします。ボトルネックを見つけ、そこを重点的に解消しましょう。

### サブクエリを引数に取る場合、`in`より`exists`を使う

- `not in`と`not exists`は大抵同じ結果を返す
- `exists`の方が速い
    - `id`にインデックスがあれば`id`の参照だけで済む
    - 合致する行が1行でもあればそこで打ち切る
- `in`の方が可読性は高い
    - そんなに遅くなければそのままで良い
        - `Oracle`は`in`でもインデックススキャンする
        - `PosgreSQL`は`in`の速度改善がされている

```sql:遅い
select *
        from Class_A
    where id in (select id from Class_B);
```

```sql:速い
select *
        from Class_A A
    where exists
        (select *
                from Class_B B
            where A.id = B.id);
```


### サブクエリを引数に取る場合、`in`よりも結合を使う

```sql
select A.id, A.name
    from Class_A A inner join Class_B B
        on A.id = B.id;
```

### ソートを回避する

ソートは裏で動いています。

- ソートが発生する代表的な演算
    - `group by`
    - `order by`
    - 集約関数
        - `sum`
        - `count`
        - `avg`
        - `max`
        - `min`
    - `distinct`
    - 集合演算子
        - `union`
        - `intersect`
        - `except`
    - ウィンドウ関数
        - `rank`
        - `row_number`
        - 等


### 集合演算子の`ALL`オプションをうまく使う

- `union`、`intersect`、`except`を使うと重複排除のためソートする
    - 重複が発生しないとわかっているなら`union all`を使う
        - 実装状況のばらつきに注意


### `distinct`を`exists`で代用する

- 売上のあった商品を探す

```sql:distictで売上のあった商品を探す
select distinct I.item_no
    from Items I inner join SalesHistory SH
        on I.item_no = SH.item_no;
```

```sql:existsで売上のあった商品を探す
select item_no
        from Items I
    where exists (select *
            from SalesHistory SH
        where I.item_no = SH.item_no);
```

### `min`・`max`でインデックスを使う

```sql:これは全表検索が必要
select max(item)
    from Items;
```

```sql:これはインデックスを利用できる
select max(item_no)
    from Items;
```

### `where`句で書ける条件は`having`句には書かない

- `group by`の集約は事前に行数を絞り込んだほうが負荷が軽くなる
- `where`句でインデックスが利用できる


### そのインデックス、本当に使われてますか？

- 索引列に加工を行なっている
    - **インデックスを利用するときは、列は裸**

```sql:インデックスが使われない
-- インデックスが使われない
select * from SomeTable where col_1 * 1.1 > 100;
-- 右側での式はOK
select * from SomeTable where col_1 > 100 / 1.1
```

- インデックス列に`null`が存在する
    - 原則的に`null`は列の正当な値ではない
- 否定形を使っている
    - インデックスが使用できない
        - `<>`
        - `!=`
        - `not in`
- `or`を使っている
- 複合索引の場合に、列の順番を間違えている
    - 最初の列を先頭にする
    - 順番も崩さない
- 後方一致、または中間一致の`like`述語を用いている
    - `like`を使うときは前方一致のみインデックスを使える
- 暗黙の型変換を行なっている
    - オーバーヘッド発生させる
    - インデックスも使用不可

```sql:暗黙の型変換
-- x
select * from SomeTable where col_1 = 10;

-- o
select * from SomeTable where col_1 = '10';

-- o
select * from SomeTable where col_1 = cast(10, as char(2));
```

### 中間テーブルを減らせ

- `having`句を活用する
- `in`で複数のキーを利用する場合は一箇所にまとめる
    - 型変換を気にしなくて良い
    - インデックスを利用できる

```sql:inで一箇所にまとめる
select *
    from Addresses1 A1
  where id || state || city in (select id || state || city
                        from Addresses2 A2);
```

- 集約よりも結合を先に行なう
- ビューのご利用は計画的に


## SQLプログラミング作法

- 名前に意味を持たせる
- なるべくコメントはあったほうがいい
    - SQLは段階的なデバッグがほとんどできない
- `from`句から書く
- 読みにくいコードは何物も、誰も解決してくれない


## RDB近現代史

- 最初は階層型だった
    - アポロ計画
- 1968年IBMの社内報
    - E.F.コッド
        - 「大容量データバンクのための関係モデル」
- RDBの「テーブル」と「SQL」の発明
    - 「ループをなくすのがRDBを考えた目的だった」
        - 1981年チューリング賞でのコッド発言
    - 破壊的イノベーション
        - ローエンドのUNIXマシンでも動く
            - 最初は実用に耐えないおもちゃだった
                - だがユーザフレンドリ
- RDBの限界
    - 性能と信頼性のトレードオフ
        - ボトルネックとなる
    - データモデルの限界
        - 「グラフ」と「非構造化データ」に弱い
            - 木構造のグラフ
            - ネットワーク構造の循環グラフや非循環グラフ
            - 非構造化データの`JSON`
                - スキーマがない
- `NoSQL`
    - `KVS`
        - `Redis`や`memcached`
    - ドキュメント指向型DB
        - `MongoDB`
    - ACIDの犠牲がある
        - 破壊的イノベーションではない

## なぜ"関係"モデルという名前なの？

2つの集合の直積の部分集合を集合論では「二項関係」と呼びます。それをコッドはn項に拡張しました。これは`cross join`として実装されています。


## 関係に始まり関係に終わる

`SQL`は`UNIX`の「なんでもファイル主義」のように「なんでも関係主義」です。`SELECT`文とは実は、テーブル（関係）を引数にとってテーブル（関係）を返す関数なのです。閉包性を持っています。関係は「自由に四則演算が可能な集合」であり、集合論や群論などをそのまま援用できます。


## アドレス、この巨大な怪物

データベースをアドレスから解放することが目標でした。データの管理方法を、位置から内容へ変えることです。これは人間が認識しやすい有意味な世界を作るということです。`SQL`や`RDB`はアドレスの呪縛からいかにして逃れるかという問題への一つの解答でした。『賢いデータ構造と間抜けなコードのほうが、その逆よりずっとまし』。


## 順序をめぐる冒険

順序を持つか持たないかに関して対立する保守派と革新派の話です。


## `group by`と`partition by`

`partition`は「類」です。`SQL`や`RDB`には集合論や群論などの成果が多く取り入れられています。


## 手続き型から宣言型・集合指向へ頭を切り替える７箇条

1. IF文やCASE文は、CASE式で置き換える。SQLはむしろ関数型言語と考え方が近い
2. ループはGROUPBY句とウィンドウ関数で置き換える
3. テーブルの行に順序はない
4. テーブルを集合と見なそう
5. EXISTS述語と「量化」の概念を理解しよう
    - 「全ての」を「〜ないものはない」に変換する
    - 読みにくいがパフォーマンスが良い
6. HAVING句の真価を学ぶ
7. 四角を描くな、円を描け


## 神のいない論理

真と偽と「可能」の3値論理の歴史です。


## SQLと再帰集合

数学の歴史をさらに紐解きます。


## `NULL`撲滅委員会

- コードの場合──未コード化用コードを割り振る
    - `0 : 未知`、`9 : 適用不能`など
- 名前の場合──「名無しの権兵衛」を割り振る
    - `UNKNOWN`を割り振る
- 数値の場合──０で代替する
    - `NULL`を0に変換する
- 日付の場合──最大値・最小値で代替する
    - 「0001-01-01」や「9999-12-31」のように可能な最大値・最小値を使う

1． まずデフォルト値を入れられないか検討する。2．どうしようもない場合だけNULLを許可する。
2．どうしようもない場合だけNULLを許可する。


## SQLに置ける存在の階層

興味深い内容でした。


## 読了後のまとめ

第１部はSQLを集合と捉えた踏み込んだ内容で、第２部はSQLの歴史や数学との繋がりが教養書のように興味深く理解できるように書かれていました。単純に実務で使うからというSQLの観点から、哲学や数学の歴史の一部として納得できるようになりました。表現も平易でわかりやすく、とても質の高い本だと思います。