読書

「達人に学ぶSQL徹底指南書 第2版」を読んだ

データベースとSQLの学び直しをしようと思い、界隈で評価の高いミック氏の著書を読んでみた。

どんな本か

2018年10月11日発行。翔泳社。SQLを半年〜1年程度経験した中級者向け。CASE式、EXISTS、ウィンドウ関数などの応用を演習問題付きで解説する第一部と、RDBの歴史や経緯を読み物として楽しめる第二部の二部構成。著者はSQLに関する著書を数々出版しているミック氏。第1版は2008年発行。

https://www.shoeisha.co.jp/book/detail/9784798157825

所感

もっと早く読んでおけばよかったと後悔する一冊。ただSQLを使っているだけではなかなか身に付かない集合指向と述語論理の考え方を伝えてくれる本だった。業務でSQLを触るものの体系的に学んだことはない、という人には強く推奨できる。

本書の序文にこんな一文があるが、まさにこの本が伝えたい内容を表している。そういう意味で第二部が特に面白い。

しかし、なかには「自分の使っている道具の成り立ちを知りたい」と思う人もいるでしょう。本書は、そのような好奇心を持ってしまった読者に対して、SQLの原理となっている仕組みや、この言語を作った人々が何を考えて現在のような形にしたのか、というバックグラウンドを掘り起こして伝えることを目指しています。

また、巻末の参考文献リストに一冊ずつ丁寧なおすすめコメントが書かれていて、次に読む本を考える参考になった。

要点

要点だらけなので自分的に覚えておきたいと感じた項目を挙げておく。

CASE式は便利

CASE式はどこにでも書けるので便利。「式」なので 1 + 1a / bの仲間。

  • SELECT 句
  • WHERE 句
  • GROUP BY 句
  • HAVING 句
  • ORDER BY 句
  • 関数、述語の引数
  • 式の中

WHERE句で条件分岐させるのは素人のやること。プロはSELECT句で分岐させる

順列、組み合わせは自己結合で得られる

=以外で繋ぐ非等値結合を使うことで組み合わせを得られる。使うシーンは多くなさそうだけど、非等値結合という選択肢があることは念頭に置いておきたい。

name
りんご
みかん
バナナ
-- 順列を得る
SELECT P1.name AS name_1, P2.name AS name_2
  FROM Products P1 INNER JOIN Products P2
    ON P1.name <> P2.name;

名前が一致しない行だけを結合するので順列が得られる。

name_1name_2
りんごみかん
バナナみかん
みかんりんご
バナナりんご
みかんバナナ
りんごバナナ
-- 組み合わせを得る
SELECT P1.name AS name_1, P2.name AS name_2
  FROM Products P1 INNER JOIN Products P2
    ON P1.name > P2.name;

大小比較することで、順序の違う組み合わせも結果から排除。

name_1name_2
りんごみかん
バナナみかん
バナナりんご

3値論理とNULL

  • NULLには「未知」と「適用不能」の2種類がある
  • SQLの真理値はtrue, false, unknownの3値を持つ
  • NULLは値でも変数でもない。値がないことを示すだけの記号
  • SQLでは排中律が成立しない
  • NOT INとNOT EXISTSの結果は一致しない

HAVING句

  • GROUP BY句は母集合を切り分けて過不足のない部分集合を作る
  • HAVING句は集合の性質を調べるための道具
  • WHERE句は要素の性質を調べるための道具
  • SQLを図にするのはベン図が向いている
条件式用途
COUNT(DISTINCT col) = COUNT(col)colの値が一意である
COUNT(*) = COUNT(col)colにNULLが存在しない
COUNT(*) = MAX(col)colは歯抜けのない連番(開始値が1)
MIN(col) = MAX(col)colは一つだけの値を持つかまたはNULL

集合演算

  • 2テーブルが等しいかの確認はUNION後の件数比較で行える
    • 2つの集合が等しければ和集合も等しい

ソートが発生する演算

SQLには暗黙的にソートが行われる演算があるので注意する。

  • GROUP BY句
  • ORDER BY句
  • 集約関数(SUM, COUNT, AVG, MAX, MIN)
  • DISTINCT
  • 集合演算子(UNION, INTERSECT, EXCEPT)
  • ウィンドウ関数(RANK, ROW_NUMBER等)

インデックスが使われない場合

索引列の加工

索引列を加工するとインデックスが使われなくなる場合が多い。

SELECT * FROM SomeTable WHERE col_1 * 1.1 > 100;

インデックスを利用する場合は、列を裸にしておく。

否定形の使用

以下のような否定形の述語はインデックスを使えない。

  • <>
  • !=
  • NOT IN

SQLコメントの必要性

SQLは宣言型言語であり、手続き型に比べ、目的の処理についてのコードが凝縮される特徴がある。そのためコードに語らせることが難しく、コメントを残すのが重要になる。

-- 一行コメント
SELECT *
  FROM hoges;
/* 複数行
コメント */
SELECT *
  FROM fugas;
SELECT *
  FROM foobar
 WHERE col_1 = 'a'
   AND col_2 = 'b'
-- 文の途中にも挿入できる   
   AND col_3 = 'c';

SQLはFROM句から考える

SELECT句はSQLで最後に実行される部分であり、書き始めで考えるのが難しい場合が多い。どのテーブルからデータを取得したいかは最初に考えられるので、FROM句から考えるのがスムーズ。

FROM → WHERE → GROUP BY → HAVING → SELECT (→ORDER BY)

カラムはテーブルに属しているので、確かにこの考えは理に適ってる。

NULLは極力排除する

  • 原則はNOT NULL
  • どうしても必要な場合にNULLを許可する
  • 整数カラムのNULLは0で代用できないか検討する
  • 文字列カラムでは『不明』や『XXXXX』などの代用文字列を検討する

集約後に元の列が参照できない理由

GROUP BY句で集約した後はSELECT句に元の列名を指定できず、GROUP BYに指定した列か集約関数しか使えない。グループ化したあとは集団についての統計的属性しか持たなくなるので、SELECT句で取得できない。

  • 個人の属性

    • 氏名
    • 年齢
    • 身長
    • 体重
  • 集団の属性

    • 平均年齢
    • グループ名
    • 人数
    • 最大身長

『チームAの身長を教えてください』には答えられないが『チームAの平均身長を教えてください』であれば答えられる。的な。

その他

  • RDB, SQLはループを排除したい思想で作られている
  • RDBはアドレスの概念をユーザから隠蔽した
  • ウィンドウ関数は行の順序を意識しており、集合指向のSQLの中では異端
  • SQLはCASE式を活用することで関数型言語に近い考えで扱える

とりあえず必要な時にCASE式を使いこなせるようになりたいと思った。


<< 記事一覧へ