SQLの中級者になるための本として有名な以下の二冊を読みました。
どちらも大変勉強になりましたので、「概念」と「レシピ集」の2つに分けてアウトプットします。
概念には、SQLやRDBの基礎的な概念や設計などを記載し、
レシピ集には、このような場合はこうした方が良い、または、こうしない方が良いというような具体的な知識について記載します。
本記事は概念の記事です。レシピ集はこちらに記載しています。
SQLとRDBの基礎理論は二つある。
一つは数学の一分野である集合論、もう一つは現代論理学の標準的な体系である述語論理(その中の一階述語論理)である。
Wikipediaによると集合論とは、集合とよばれる数学的対象をあつかう数学理論である。
集合とは高校の時に習ったベン図で表すあれである。
皆さんも、対象 x が集合 E の元(要素)であることを「x ∈ E 」と表現する、とか習ったと思う。
SQLやRDBに集合という概念があることは自明だと思う。
そのため既に知っていたことは記載せず、「なるほど!」と思ったことだけを箇条書きで記載していくことにする。
述語論理や一階述語論理についてWikipediaやコトバンクで簡単に調べた。
一階述語論理とは命題論理を拡張したものである。
命題とは高校の時に習った真か偽で答えるあれである。「ソクラテスは人間である」という命題は真か偽かみたいなやつ。
命題論理とは命題を記号で表すことで、式で表現したり、計算したりできるようにした分野のことらしい。
そして、命題論理よりもさらに詳細に命題の内部構造を表現できるようにしたものを述語論理と呼ぶらしい。
変数を量化できることが述語論理の特徴らしく、一階述語論理は「個体の量化のみを許す述語論理」とのこと。
量化とは、命題など議題領域の個体の「量」を指定すること。量とは「全ての」とか「少なくとも一つの」とかそういうこと。
具体的には、以下のような推論を扱えるのが述語論理。
全ての人間は死ぬ。
ソクラテスは人間である。
したがってソクラテスは死ぬ。
なお、少し専門的に書き換えると以下のようになる。∀は「全ての」を意味する論理記号。
∀x 人間(x)→死ぬ(x)
人間(ソクラテス)
死ぬ(ソクラテス)
「人間」や「死ぬ」を述語、「ソクラテス」を引数と呼ぶ。
引数には変数(x)が使えるので、「∀x 人間(x)→死ぬ(x)」のような一般規則も書ける。
さて、どうだろうか。正直自分はちんぷんかんぷんである。
ちんぷんかんぷんではあるが、「命題」とか「述語」とか「量化」とかいうのがキーワードらしいというのはなんとなくわかった。
これ以上調べてもキリがないので、そろそろ本題に入る。
述語論理はSQLとRDBの基礎理論の一つであると先に述べたが、ここからはSQLやRDBと述語論理との関係を見ていく。
例えば、以下のようなテーブルがあったとする。
name | sex | age |
田中 | 男 | 28 |
鈴木 | 女 | 21 |
山田 | 男 | 32 |
述語論理的に解釈すると、これは命題の集合である。
つまり、一行を一つの命題とみなすことができる。
一行目は「田中は性別が男であり、かつ、年齢は28である」という命題を表現している。
また、本章の最初に述語について簡単に説明したが、もう少し端的に記載すると述語とは関数の一種らしい。
もっと正確に記載すると、戻り値が真理値(真偽値)の関数のことらしい。
SQLには、=、<、>などの比較述語や、 BETWEEN、LIKE、IN、IS NULLなどの関数があるが、これらは述語であるとのこと。
WHERE句では、述語を組み合わせて述語をつくり、その述語の戻り値が真になる命題(集合)のみが命題集合(テーブル)から選択される。
また、そう考えると、述語は集合(レコード)を定義する関数でもあるので、集合と述語はほとんど同じと見なすこともできる。
さて、先ほど=やBETWEENなどは述語であると記載した。
この他に真理値を返すものとして、EXISTSがある。
ただ、=やBETWEENなどとEXISTSは引数にとるものが違う。
具体的には、=やBETWEENなどは引数に「13」や「ソクラテス」などのスカラ値をとるのに対し、EXISTSはSELECT文そのもの(つまり集合)をとる。
述語論理では、このような引数の違い(引数のレベル)に応じて、述語を分類する。
=やBETWEENなどスカラ値をとる述語を「一階の述語」、EXISTSのように集合をとる述語を「二階の述語」と呼ぶ。
Javaなどのプラグラミング言語では関数を引数にとる関数のことを「高階関数」と呼ぶが、考え方は同じ。
述語は関数の一種なので、EXISTSも高階関数と呼べる。
もう少し述語論理とSQLやRDBとの関係について記載する。
述語論理には、量子化という特別な述語が存在する。
「全ての」とか「少なくとも一つ」とかそういったもののことでである。
前者を「全称量子化」、後者を「存在量子化」と呼び、それぞれ「∀」と「∃」と表現する。
「∀」はAllのAを上下逆にした形、「∃」はExistsのEを上下逆にした形。
SQLのExists述語は、この存在量子化を実装したものである。
なお、SQLには全称量子化に対応する述語がないので、存在量子化(Exists)を使って変換(ド・モルガンの法則)するしかない。
具体的には、「全ての行が条件Pを満たす」という文を「条件Pを満たさない行が存在しない」という文へ変換する。
SQLと一般的なプログラミング言語(手続き型言語)との設計思想的な違いはいくつかあると思うが、
書籍では主に以下が挙げられていた。
集合論の説明でも記載したが、RDBではテーブルのレコードは順序をもたない。
順序をもたないため、カーソルもなく、ループ処理もできない。
つまり、SQLではレコード単位での操作ではなく、集合単位での操作をするということを理解すべき。
最近はウィンドウ関数が登場して、カーソルを使うこともできるようになったが、原則は集合として扱うべき。
集合として扱うためには、HAVING句を使いこなす必要がある。
例えば、以下のような連番のテーブルがあったとする。
seq | name |
1 | ディック |
2 | アン |
3 | ライル |
5 | カー |
6 | マリー |
8 | ベン |
このテーブルに歯抜けがあるかどうかを探すにはどうすれば良いか。
手続き型言語の場合は以下のようにして調べることができる。
SQLは順序をもたないが、その代わりに集合とみなすことで以下のようにして調べることができる。
SELECT ‘歯抜けあり’ FROM seq HAVING COUNT(*) <> MAX(seq) |
なお、WHERE句が要素の性質を調べる道具であるのに対し、HAVING句は集合の性質を調べる道具である。
SQLで検索条件を設定するときは、検索対象となる実体が集合なのか集合の要素なのかを見極めることが基本。
SQLは論理値がtrue, false, unknownの3種類ある3論理値である。
なぜunknownという値が存在するのか。それはNULLが原因である。
例えば以下の式は全てunknownに評価される。
つまり、NULLには比較述語を適用できないので、unknownが必要になってしまうということ。
ではなぜ、NULLに比較述語を適用できないかというと、それはNULLが値でも変数でもないからである。
SQLにおいてNULLは「そこに値がない」ことを示すただの視覚的マークにすぎない。
C言語などのプログラミング言語はNULLを1つの定数(整数0など)として定義しているが、SQLでは視覚的マークにすぎないので注意が必要である。
なお、余談だが、NULLには「未知」と「適用不能」の2種類ある。
例えば、サングラスをつけている人の目の色は「未知」、法人の目の色は「適用不能」である。
リレーショナルデータベース(RDB)が採用しているデータモデルはリレーショナルデータモデル、すなわち、関係モデルである。
なぜ表形式モデルではなくて、関係モデルなのか。
まず、関係と表の代表的な相違点をいくつか挙げる。
なお、関係モデルで使用される公式用語と日常的な言葉との対応は以下の通り。
形式的な関係モデルの用語 | 非形式的な日常語 |
関係(relation) | 表またはテーブル |
組(tuple) | 行またはレコード |
濃度(cardinality) | 行数 |
属性(attribute) | 列またはフィールド |
次数(degree) | 列数 |
定義域(domain) | 列の取りうる値の集合 |
さて、関係とは何か。その正確な定義は以下のように表せる。
R ⊆ (D1 × D2 × D3 … × Dn)
関係をR、属性をAi、その属性の定義域をDiとする。
このとき、関係Rは定義域D1, D2, …Dnの直積の部分集合である。
これだけだとわかりづらいので、この定義を簡単な例で説明する。
3つの属性値a1, a2, a3があるとする。
属性a1は1種類、属性a2は2種類、属性a3は3種類の値を取ることが可能。各属性に対応する定義位置をd1, d2, d3と呼ぶ。
d1 = {1}
d2 = {男, 女}
d3 = {赤, 青, 黄}
さて、この3つの定義域を使って関係R1を作る場合、最大いくつのタプルを持つ関係がつくられるだろうか。
答えは1 × 2 × 3で6である。具体的に全部のタプルを書き並べると下表のようになる。
a1 | a2 | a3 |
1 | 男 | 赤 |
1 | 男 | 青 |
1 | 男 | 黄 |
1 | 女 | 赤 |
1 | 女 | 青 |
1 | 女 | 黄 |
この関係R1が直積である。
つまり、直積とは「各属性の定義域の値を使って作りうる組み合わせの最大値」のこと。
なお、SQLではCROSS JOINとして実装されている。
そして、上記の3つの定義域から作られる全ての関係Rnは、この直積の部分集合になる。
たとえば、関係R2を「R1の一行目と二行目からなる関係」と定義することができる。
これが関係の意味である。
なお、集合論では「2つの集合の直積の部分集合」のことを「二項関係」と呼ぶらしい。
やはり、RDBの基礎理論は集合論だということが良くわかる。
関係(relation)と関連(relationship)は異なる。
関係は上述した通り、テーブルそのものやテーブルの列間の関係性を指す。
関連とは、テーブル間の関連という意味で用いる。
テーブル設計において正規化は重要なキーワードである。
正規化を行うにあたり、以下のような適切な関係が成立しているかが大切。
なお、正規化の目的は以下の通り。
関係は閉包性をもつ。
これは、「演算子の入力と出力が共に関係になる」という性質である。
言い換えると「関係の世界が閉じていることを保証する」性質である。
SQLには、射影、制限、和、差など様々な関係演算子が存在する。
ある関係に対して、これらの関係演算子を使用しても結果は関係になる。
UNIXがあらゆるものをファイルとして扱い、ファイルに対してあらゆるシェルコマンドを実行しても結果はファイルであるのは皆さんご存知だと思う。
つまり、UNIXにおけるファイルはシェルコマンドについて閉じた集合を形成するわけだが、
同様に、関係モデルにおける関係は関係演算子について閉じた集合を形成する。
また、関係を四則演算しても関係である。
これを数学的には「体」と呼ぶ。
つまり、「関係は四則演算に対して閉じている集合」と考えることもできる。
このように、閉包性をもつことによって、SQLの演算は強力な表現力を獲得した。