データベースの話をしようと思ったのですが、そこで使う疑似コード用の疑似問い合わせ言語の説明が長くなってしまうので、独立した記事にしよう、と。この記事がそれです。が、1回分にはまだ長いので、この記事は「その1」です。
データベースの問い合わせ言語なら、SQLのSELECT文があるではないか? いや、僕の目的には生のSELECT文はふさわしくないのです。
- コンピュータのメカニズムや物理的ストレージの事情などは完全に忘れたい。
- ストリング図やオペラッド〈複圏〉と相性が良い構文にしたい。
- 格納されたデータと論理的な条件を同等に扱いたい(WHERE句をテーブル扱いしたい)。
- データ型は抽象的・概念的な構造として定義したい。
- 通常のプログラミング言語と同様に変数・関数を使いたい。
単純明快で夾雑物がなくスッキリしていることが第一で実用性なんてもんは考えません。
この記事の最後に、この記事と引き続く記事の「動機と目的と方針」を書いています。最後の節を最初に読むほうがいいかも知れません。$`\require{enclose}
\newcommand{\mbf}[1]{\mathbf{#1}}
\newcommand{\mrm}[1]{\mathrm{#1}}
%\newcommand{\msc}[1]{\mathscr{#1}}
%\newcommand{\msf}[1]{\mathsf{#1}}
%\newcommand{\mbb}[1]{\mathbb{#1}}
\newcommand{\id}{\mathrm{id}}
\newcommand{\hyp}{ \text{-} }
\newcommand{\In}{\text{ in }}
\newcommand{\T}[1]{ \text{#1} }
\newcommand{\TY}[1]{ \enclose{box}{#1} }
\newcommand{\Ty}[1]{ \mrm{#1} }
\newcommand{\N}[1]{ \text{#1} }
\newcommand{\SL}[1]{ ``\text{#1}\!" }
\newcommand{\BR}[1]{ \left[\!\left[ {#1} \right]\!\right] }
`$
内容:
補足:
データベース関連・ハブ記事:
基本データ型
基本データ型〈スカラー型〉は:
- INTEGER型 : 整数の型です。説明の必要はないでしょう。
- TEXT型 : テキスト文字列の型です。SQL標準では VARCHAR のようですが。
- NATURAL型 : 現実のデータベースシステムには無いようですが、自然数〈非負整数〉の型です。
- REAL型 : 実数の型です。「浮動小数点フォーマットがどうたら」とかじゃなくて、ほんとにすべての実数達の集合です。
- BOOL型 : 古典二値真偽値の集合です。真は、$`\mrm{true}`$ でも $`1`$ でもどっちでもいいとします。偽は $`\mrm{false} = 0`$ 。
([追記]ガーンッ、迂闊でした! NATURAL はたいていは予約語です。自然数ではなくて、自然ジョインを指定するときに、NATURAL というキーワードを使うのです。この記事の NATURAL はデータ型なので、ジョインとの混同は起きないでしょうから、修正しないでそのままとします。[/追記])
$`\Ty{INTEGER}, \Ty{TEXT}`$ などは型の名前です。型の名前の解釈をハッキリと書き下すにはスコット・ブラケット〈Scott brackets〉(セマンティック・ブラケット〈semantic brackets〉とも呼ぶ) $`\BR{\;}`$ を使います。基本的な使い方は:
$`\quad \BR{\T{名前}} := \T{意味となる集合や関数}`$
詳しくは以下の記事、特に最初の2つの節「意味論のための変数と記法」「名前の種類とスコット・ブラケット」を参照してください。
型の名前の解釈〈型のデノテーション〉をスコット・ブラケットで書けば:
- $`\BR{\Ty{INTEGER} } := \mbf{Z}`$
- $`\BR{\Ty{TEXT} } := \mbf{Str}`$ (文字列達の集合)
- $`\BR{\Ty{NATURAL} } := \mbf{N}`$
- $`\BR{\Ty{REAL} } := \mbf{R}`$
- $`\BR{\Ty{BOOL} } := \mbf{B}`$
型の名前の意味は、上記右辺の抽象的型(数学的集合)です。
ペア型は $`(S, T)`$ と書きます。このことは以下のように記述できます。
$`\quad \BR{(S, T)} := \BR{S} \times \BR{T}`$
右辺は集合の直積です。より一般に:
$`\quad \BR{(T_1, \cdots, T_n)} := \BR{T_1} \times \cdots \times \BR{T_n}`$
型 $`T`$ と型 $`(T)`$ は、たいていは区別する必要ないですが、一応次のように定義しておきます。
$`\quad \BR{(T)} := \{(x) \mid x\in \BR{T}\}`$
つまり、型 $`(T)`$ の実体である集合は、$`\BR{T}`$ の要素ひとつだけからなる長さ1のタプルです。
$`()`$ の型としての意味は標準的単元集合〈canonical singleton set〉です。
$`\quad \BR{()} := \mbf{1}`$
$`()`$ は型ではなくてデータとして使われるかも知れません。こういう曖昧な状況では、次のように書きます。
$`\quad \BR{() \T{ as type}} := \mbf{1}\\
\quad \BR{() \T{ as data}} := ()
`$
1行目は型としての $`()`$ の意味は標準的単元集合であると言っていて、2行目はデータとしての $`()`$ の意味は空タプル $`()`$ そのものだと言っています。標準的単元集合(ユニット型)の唯一の要素を空タプルにする習慣があります(そうする必然性は別にないですが)。
$`\quad \BR{() \T{ as type}} = \{\BR{() \T{ as data}}\} = \{()\}`$
文字列リテラルは、$`\SL{hello}, \SL{富士山}`$ のようにダブルクォートで囲みます(SQL標準ではシングルクォートですが)。文字列リテラルや数値リテラルが型として登場したときは、そのデータだけを含む単元集合だと解釈します。
$`\quad \BR{\SL{hello} \T{ as type}} = \{\SL{hello}\}`$
型 $`(\Ty{TEXT}, 1)`$ の意味は $`\mbf{Str}\times \{1\}`$ ですが、$`1`$ の型がよく分かりません。こういうときは $`(\Ty{TEXT}, 1 \In \Ty{BOOL})`$ とすればハッキリします。次が言えます($`\mbf{B}\cap\mbf{Z} = \emptyset`$ だと仮定して)。
$`\quad \BR{(\Ty{TEXT}, 1 \In \Ty{BOOL})} \subseteq \mbf{Str}\times \mbf{B}\\
\quad \BR{(\Ty{TEXT}, 1 \In \Ty{BOOL})} \not\subseteq \mbf{Str}\times \mbf{Z}
`$
キーワード $`\T{type}`$ で型の定義ができるとします。SQLなら CREATE TYPE文に相当します。ユーザー定義型の名前は自由です(日本語名もOK)。
\T{type }身長 := (\Ty{NATURAL}, \SL{cm});\\
\T{type }体重 := (\Ty{NATURAL}, \SL{kg});\\
\T{type }身長体重 := (\Ty{身長}, \Ty{体重});
`$
定義された型 $`\Ty{身長体重}`$ の意味は:
$`\quad \BR{\Ty{身長体重}} \\
= \BR{(\Ty{身長}, \Ty{体重})} \\
= \BR{\Ty{身長}}\times \BR{\Ty{体重}} \\
= (\mbf{N}\times\{\SL{cm}\})\times (\mbf{N} \times \{\SL{kg}\})
`$
なお、集合 $`\mbf{N}\times\{\SL{cm}\}`$ の要素 $`(172, \SL{cm})`$ は読みやすい形で $`172\T{cm}`$ と書くかも知れません。
テーブル型(ベキ集合)
前節で定義した型 $`身長体重`$ はタプル型です。タプル型はテーブル(の状態)の型ではありません。テーブル(の状態)はタプルではなくて、タプルの集合です。テーブルの型は次のように定義します。
\T{type }身長体重テーブル := \mrm{Pow}(身長体重);
`$
ここで、$`\mrm{Pow}(\hyp)`$ はベキ集合〈powerset〉を作る型構成子です。その意味はベキ集合です(そのまんま)。
$`\quad \BR{\mrm{Pow}(\Ty{身長体重})} = \mrm{Pow}(\BR{身長体重})\\
\quad = \mrm{Pow}(\,(\mbf{N}\times\{\SL{cm}\})\times (\mbf{N} \times \{\SL{kg}\})\,)
`$
$`(172\T{cm}, 78\T{kg})`$ は型 $`\Ty{身長体重}`$ のデータですが、型 $`\mrm{Pow}(\Ty{身長体重})`$ のデータではありません。一方、$`\{(172\T{cm}, 78\T{kg})\}`$ は型 $`\Ty{身長体重}`$ のデータではありませんが、型 $`\mrm{Pow}(\Ty{身長体重})`$ のデータになっています。$`\{\}`$ や $`\{(172\T{cm}, 78\T{kg}), (152\T{cm}, 48\T{kg})\}`$ は、もちろん型 $`\mrm{Pow}(\Ty{身長体重})`$ のデータです。
ちなみに、「型を語る文脈でリテラルは単元集合を表す」という約束だと:
$`\quad \BR{(172\T{cm}, 78\T{kg}) \T{ as type} } = \{(172\T{cm}, 78\T{kg})\} \\
\quad \BR{(172\T{cm}, 78\T{kg}) \T{ as data } }
= (172\T{cm}, 78\T{kg})
`$
単元集合とその唯一の要素は混同しがちですが、ちゃんと区別してください。
データベースのタプルと言えば、カラム〈フィールド〉に名前が付いているのが普通です。カラム名付きタプル型は次のように定義しましょう。
\T{type } 身長体重 :=
(\N{身長}\: \Ty{NATURAL}, \N{体重}\: \Ty{NATURAL});
`$
カラム名と型名を空白を挟んで並べる書き方は、SQLの CRATE TABLE 文で使われている構文からです。
この場合、データ自体は自然数のペアに過ぎませんが、型名やカラム名でセマンティクス(現実世界での意味)を示唆しています。先ほどは、自然数に単位(cm, kg)を付けることにより現実の量としての意味を与えていました。「どっちが効率的か?」のような話はしません。
さて、カラム名付きタプル型の解釈(数学的デノテーション)ですが、パイ型としての解釈は面倒なので補足に書いておきます。ここでの解釈はカラム名を無視して単なる(カラム名なしの)タプル型と同じだとします。
$`\quad \BR{身長体重} := \BR{\Ty{NATURAL}}\times \BR{\Ty{NATURAL} }
= \mbf{N}\times \mbf{N} = \mbf{N}^2
`$
そして、ユーザの便宜のために、システムがカラム名と順番の対応を憶えているとします。例えば、カラム名 $`\N{体重}`$ は2番目のことだとシステムが憶えています。これにより、$`x\in \BR{身長体重}`$ に対して次の書き方が許されます。
$`\quad x.\N{体重} = x.2 = \pi_2(x) \;\in \mbf{N}`$
ここで、$`\pi_2`$ は直積の第二射影です。一般に、集合 $`A, B`$ の直積 $`A\times B`$ からの第一射影と第二射影は次のように定義されます。
$`\pi_1 : A\times B \to A \In \mbf{Set}\\
\T{For }(x, y)\in A\times B\\
\quad \pi_1( (x, y)) = \pi_1(x, y) := x
`$
$`\pi_2 : A\times B \to B \In \mbf{Set}\\
\T{For }(x, y)\in A\times B\\
\quad \pi_2( (x, y)) = \pi_2(x, y) := y
`$
ほんとは、$`\pi^{A, B}_1`$ のように書くべきですが、煩雑になるので単に $`\pi_1`$ と書きます。$`n`$ 個の集合達の直積からの第$`k`$射影関数も同様で、次のように書きます。
$`\quad \pi_k : A_1 \times \cdots \times A_n \to A_k \In \mbf{Set}\\
\T{where}\\
\quad k \in \{1, \cdots, n\}
`$
タプル型から型構成子 $`\mrm{Pow}(\hyp)`$ により構成される型を、ここではテーブル型と呼びます。先に述べたように、タプル型とテーブル型はまったく別です。
型構成子 $`\mrm{Pow}(\hyp)`$ は、スカラー型〈基本型〉や、長さ 0, 1 のタプルに対して適用してもかまいません。例えば:
$`\quad \BR{\mrm{Pow}(\Ty{BOOL})} = \{ \{\}, \{0\}, \{1\}, \{0,1\} \}\\
\quad \BR{\mrm{Pow}(\,() \,) } = \{\{\}, \{()\} \} \\
\quad \BR{\mrm{Pow}(\,(\Ty{BOOL}) \,) } = \{\{\}, \{(0)\}, \{(1)\}, \{(0),(1)\} \} \\
\quad \BR{\mrm{Pow}(\,(\Ty{BOOL}, \Ty{BOOL}) \,) } = \\
\qquad
\{\{\}, \{(0,0)\}, \cdots, \{(0,0),(0,1),(1,0), (1,1)\} \}
`$
ここまでに出てきた「型と認められるもの」をまとめると:
- 基本型〈スカラー型〉は型である。
- 幾つかの型(0個の型、1個の型でもよい)の直積は型である。総称してタプル型と呼ぶ。
- 特に、0個の型の直積は特定された単元集合〈distinguished singleton type〉となる。特定された単元集合=標準単元集合 の型をユニット型とも呼ぶ。
- 単一のデータ(任意)だけからなる型を認める。
- 型のベキ集合は型である。基本型の直積であるタプル型のベキ集合型を総称してテーブル型と呼ぶ。(くどいが*1、)テーブル型はタプル型とはまったく違う。
- タプル型の成分型は基本型でなくてもよいし、ベキ集合型のもとになる型がタプル型である必要もない。だが、多く使われるケースは、基本型達のタプル型と基本型達のタプル型のベキ集合型。
カラム名付きタプル型を依存型理論のパイ型として定義しましょう。
使うタプル名達の集合を $`X`$ とします。例えば、$`X =\{\N{身長}, \N{体重}\}`$ 。カラムの順番が分かるように、$`X`$ に全順序を指定しておきます。例えば、$`\N{身長}\lt \N{体重}`$ 。
集合族〈indexed family of set〉$`F: X \to |\mbf{Set}|`$ を考えます。例えば:
$`\quad F(\N{身長}) := \mbf{N}\\
\quad F(\N{体重}) := \mbf{N}
`$
まずシグマ型を作ります。
$`\quad \sum_{x\in X}F(x) := \bigcup_{x\in X} (\{x\}\times F(x) )
`$
$`X`$ から $`\sum_{x\in X}F(x)`$ へのセクション達の集合がパイ型です。
$`\quad \prod_{x\in X}F(x) := \mrm{Sect}(\pi : \sum_{x\in X}F(x) \to X)`$
ここで、$`\pi`$ はシグマ型の標準射影で、セクションとは次のような関数です。
$`\quad s: X\to \sum_{x\in X}F(x)\\
\T{where } s;\pi = \id_X
`$
[/補足]
型とデータに関する注意事項
型の意味〈デノテーション〉は集合であり、集合の要素をデータと呼んでいます。タプル型〈直積〉とベキ集合型を作ることができます。これだけでも、けっこう豊富な型を生み出せます。「テーブルの型」と言った場合、タプル型なのかベキ集合型なのか曖昧ですが、ここでは明確にベキ集合型です。タプル型をテーブル型と呼ぶことはありません。タプル型とテーブル型はまったく別ですから。
テーブル型の意味〈デノテーション〉は集合です -- 集合達の集合です。その集合(集合達の集合)の要素(である集合)が、テーブル型のデータです。“テーブル型のデータの要素”はタプルです。なぜなら、「基本型の直積であるタプル型のベキ集合型を総称してテーブル型と呼ぶ」と定義しているからです。
データは、メモリやディスクに格納されるものではなくて、単に在るだけのものです(と考える)。格納される容量だの、データへのアクセスが速いだの遅いだのは一切考えません。基本型もタプル型もテーブル型も、それらの型のデータも、すべて一律に扱います。データの型により考え方・態度を変えることはしません。
現実のデータベースシステムでは、ベキ集合型以外に、リスト型やバッグ型(順番はないが、重複個数は意味を持つコレクションの型)も使っています。しかしここでは、ベキ集合型以外は使いません。ベキ集合型のデータは集合なので、要素の順番も出現個数もありません。これは、“タプルの項目に順番があること”/“項目の重複に意味があること”とは別な話です。
タプルのカラム名は使ってかまいませんが、型/データの意味〈デノテーション〉には影響しないとします。つまり、$`(172, 78)`$ と $`(\N{身長}\:172, \N{体重}\: 78)`$ は同じデータだとみなします。カラム名は、現実世界における意味を示唆するヒントと仮引数名(後述)に使うだけです。
変数と束縛
型とデータは定義できました。つまり、型システムができました。この型システムの上に、ある種のプログラミング言語を考えます。このプログラミング言語は、変数を宣言できて、宣言した変数に値をセットできます。とりあえず、できることはそれだけです。
変数宣言の先頭は $`\T{declare}`$ で、変数への値のセットは $`\T{set}`$ で識別します。これは、SQLの構文を(名前だけ)拝借しています。例を挙げます。
\T{type } \N{身長体重} := (\\
\quad \N{身長}\: \Ty{NATURAL},\\
\quad \N{体重}\: \Ty{NATURAL},\\
);\\
\T{declare } \N{身長体重} : \mrm{Pow}(\N{身長体重});\\
\T{set } \N{身長体重} := \{(172, 78), (152, 48)\};
`$
$`\T{type}`$ で定義された名前 $`\N{身長体重}`$ は型の名前で、$`\T{declare}`$ で宣言された名前 $`\N{身長体重}`$ は変数の名前です。型名と変数名は異なる名前空間に在ると考えるので、同名が許されます。しかし、異なるモノに同じ名前はやはり混乱しがちです。
テーブル型変数〈テーブル変数〉とは、型がテーブル型(基本型達のタプル型のベキ集合型)である変数のことです。テーブル変数にはテーブル型データしかセットできません -- そうじゃないと、型付け〈typing〉する意味がありません。
ここでは、テーブル型変数とテーブル型データがあるだけで、それ以外に「テーブル」と呼ぶべき存在物などありません。「ディスクに保存された ‥‥」なんてモノは考えません。変数と、その値となるデータしか考えません。テーブルインスタンス〈table instance〉という言葉は使いますが、テーブル型データの同義語です。
変数にデータをセットすることは、代入〈assignment〉とか束縛〈binding〉と呼びます。「束縛」のほうが、単に結びつけるだけという感じ(語感)がして望ましいかも知れません。
テーブル型変数にはテーブル型データを束縛〈代入〉できます。ということは、次もOKです。
\T{declare } \N{身長常識的制約} : \mrm{Pow}(\N{身長体重});\\
\T{set } \N{身長常識的制約} := \{(x, y) \mid 0 \lt x \le 250 \};\\
\:\\
\T{declare } \N{体重常識的制約} : \mrm{Pow}(\N{身長体重});\\
\T{set } \N{体重常識的制約} := \{(x, y) \mid 0 \lt y \le 300 \};\\
\:\\
\T{declare } \N{条件} : \mrm{Pow}(\N{身長体重});\\
\T{set } \N{条件} := \{(x, y) \mid 165 \le x \le 175 \;\land\; 55 \le y \le 80 \};
`$
つまり、データ型のドメイン制約(範囲チェック)やWHERE句に書くような条件も、テーブル変数に束縛されたデータとして書けます。タプルを列挙して書いたテーブル型データも、論理式により書いたテーブル型データも差別なし一律に同じ扱いをします。
データベース変数とデータベース型データ
前節で、テーブル変数〈テーブル型変数〉にテーブル型データを束縛する話をしました。我々の型システムはタプル型構成子とベキ集合型構成子を持っているので、テーブル型よりもっと複雑なデータ型を構成できます。幾つかのテーブル型から作ったタプル型をデータベース型と呼びましょう。そして、データベース型の変数がデータベース変数〈データベース型変数〉です。
例を挙げましょう。まず、タプル型の定義とテーブル変数の宣言です。
\T{type }\N{商品タプル} := (\\
\quad \N{商品名}\:\Ty{TEXT} ,\\
\quad \N{価格}\:\Ty{NATURAL} ,\\
\quad \N{カロリー}\: \Ty{NATURAL},\\
\quad \N{商品ID}\:\Ty{NATURAL}\\
);\\
\T{type }\N{店舗タプル} := (\\
\quad \N{店舗名}\:\Ty{TEXT}, \\
\quad \N{電話番号}\:\Ty{NATURAL}, \\
\quad \N{地域}\:\Ty{NATURAL}, \\
\quad \N{店舗ID}\:\Ty{NATURAL}\\
);\\
\:\\
\T{declare }\N{商品} : \mrm{Pow}(\N{商品タプル}); \\
\T{declare }\N{店舗} : \mrm{Pow}(\N{商品タプル});
`$
タプル型の成分型〈項目型〉は $`\Ty{TEXT}, \Ty{NATURAL}`$ しか使ってませんが、適当なお約束を決めて現実的解釈をすることにします。
データベース変数は次のように宣言できます。
\T{declare }\N{商品と店舗} : (\\
\quad \N{商品}\: \mrm{Pow}(\N{商品タプル}), \\
\quad \N{店舗}\: \mrm{Pow}(\N{店舗タプル})\\
);
`$
$`\N{商品と店舗}`$ は変数名です。丸括弧内の $`\N{商品}, \N{店舗}`$ はタプル型のカラム名〈フィールド名〉です。型の名前、変数の名前、カラムの名前は同じ名前が使えるので、「いったい何の名前か?」を常に追跡してないと混乱します。キチンと追跡できているか? 以下の補足で確認してみてください。
身長・体重の例で出てきた名前を出現順で挙げます。米印が付いている名前は同じ名前を既に使っていることを示します。
| 名前 | 名前の種類 |
|---|---|
| 身長 | 型名 |
| 体重 | 型名 |
| 身長体重 | 型名 |
| 身長体重テーブル | 型名 |
| 身長 ※ | カラム名(「身長体重」型の) |
| 体重 ※ | カラム名(「身長体重」型の) |
| 身長体重 ※ | 変数名 |
| 身長常識的制約 | 変数名 |
| 体重常識的制約 | 変数名 |
| 条件 | 変数名 |
商品・店舗の例で出てきた名前を出現順で挙げると:
| 名前 | 名前の種類 |
|---|---|
| 商品タプル | 型名 |
| 商品名 | カラム名(「商品タプル」型の) |
| 価格 | カラム名(「商品タプル」型の) |
| カロリー | カラム名(「商品タプル」型の) |
| 商品ID | カラム名(「商品タプル」型の) |
| 店舗タプル | 型名 |
| 店舗名 | カラム名(「店舗タプル」型の) |
| 電話番号 | カラム名(「店舗タプル」型の) |
| 地域 | カラム名(「店舗タプル」型の) |
| 店舗ID | カラム名(「店舗タプル」型の) |
| 商品 ※ | 変数名 |
| 店舗 ※ | 変数名 |
| 商品と店舗 | 変数名 |
| 商品 ※ | カラム名(「商品と店舗」変数の) |
| 店舗 ※ | カラム名(「商品と店舗」変数の) |
「商品タプル」を「商品」にしても別な名前空間なので許されます。「商品名」を「商品」にしても別な名前空間なので許されます。つまり、次のような極悪なネーミングも許されるのです。
| 名前 | 名前の種類 |
|---|---|
| 商品 | 型名 |
| 商品 ※ | カラム名(「商品」型の) |
| 商品 ※ | 変数名 |
| 商品 ※ | カラム名(「商品と店舗」変数の) |
[/補足]
念の為、変数 $`\N{商品と店舗}`$ の型の意味〈デノテーション〉を計算してみると:
$`\quad \BR{(\N{商品}\: \mrm{Pow}(\N{商品タプル}),\N{店舗}\: \mrm{Pow}(\N{店舗タプル}) )}\\
= \BR{(\mrm{Pow}(\N{商品タプル}),\mrm{Pow}(\N{店舗タプル}) )}\\
= \BR{\mrm{Pow}(\N{商品タプル})} \times \BR{\mrm{Pow}(\N{店舗タプル})}\\
= \mrm{Pow}(\BR{\N{商品タプル}}) \times \mrm{Pow}(\BR{\N{店舗タプル}})\\
= \{(S, T)\mid S \in \mrm{Pow}(\BR{\N{商品タプル}}), T\in \mrm{Pow}(\BR{\N{店舗タプル}})\}
`$
テーブル変数とデータベース変数に、具体的なデータを束縛〈代入〉しましょう。
\T{set }\N{商品} := \{\\
\quad (\SL{ハンバーガー},200, 260, 1), \\
\quad (\SL{チーズバーガー}, 300, 310, 2)\\
\};\\
\T{set }\N{店舗} := \{\\
\quad (\SL{A駅前店}, 1234000, 1, 1), \\
\quad (\SL{'B山頂上店}, 12345000, 2, 2),\\
\quad (\SL{C海岸店}, 12348000, 2, 3)\\
\};\\
\T{set }\N{商品と店舗} := (\N{商品}, \N{店舗});
`$
変数 $`\N{商品と店舗}`$ に束縛されたデータ(データベース型データ)を展開して書くなら:
$`(\\
\quad \{\\
\qquad (\SL{ハンバーガー},200, 260, 1), \\
\qquad (\SL{チーズバーガー}, 300, 310, 2)\\
\quad \},\\
\quad \{\\
\qquad (\SL{A駅前店}, 1234000, 1, 1), \\
\qquad (\SL{'B山頂上店}, 12345000, 2, 2),\\
\qquad (\SL{C海岸店}, 12348000, 2, 3)\\
\quad \}\\
)
`$
これは、タプル型のベキ集合型のデータを並べたタプルです。
以下は、ここまでに説明した内容をSQLiteで書いたサンプルです。SQLiteに(SQL実装全般に)自然数型はないので、代わりに整数型を使っています。
CREATE TABLE 商品 ( 商品名 TEXT, 価格 INTEGER, カロリー INTEGER, 商品ID INTEGER ); CREATE TABLE 店舗 ( 店舗名 TEXT, 電話番号 INTEGER, 地域 INTEGER, 店舗ID INTEGER ); INSERT INTO 商品 VALUES ('ハンバーガー', 200, 260, 1), ('チーズバーガー', 300, 310, 2) ; INSERT INTO 店舗 VALUES ('A駅前店', 1234000, 1, 1), ('B山頂上店', 12345000, 2, 2), ('C海岸店', 12348000, 2, 3) ;
問い合わせとは何なのか?
前節までで、型システムと変数・束縛の話をしました。我々のプログラミング言語において、基本型から新しい複合型を定義することができます。型付き変数を宣言することができます。変数にデータを束縛することができます。つまり我々は、値を持った変数達を作れます。
しかし、我々のプログラミング言語が貧弱であることは否めません。何が足りてないでしょう? そうです、関数です。自前の関数を定義したり呼び出したりできないとツライ。
というわけで関数を導入します。とはいえ、汎用のプログラミング言語を目指してはいないので、関数は特定目的であり機能は制限されます。特定目的の関数を問い合わせ〈query〉と呼びます。問い合わせ(と呼ばれる関数)の定義本体で使用する式が問い合わせ式〈query expression〉です。
関数も変数と同様に、まずは宣言します。例えば次の形です。
\T{declare }\N{俺の問い合わせ} :
(\mrm{Pow}(\N{商品}), \mrm{Pow}(\N{店舗}) )
\to \mrm{Pow}(\, (\Ty{TEXT}, \Ty{NATURAL}) \,);
`$
一般に、関数の宣言は次の形だとします。
\T{declare }\N{関数の名前} : \N{引数型の表現} \to \N{戻り値型の表現};
`$
変数の宣言と関数の宣言を同じキーワード declare にしました。区別したいなら:
- 変数(の名前)の宣言は declare variable
- 関数(の名前)の宣言は declare function
キーワードの整合性を徹底するなら:
- 変数名への値〈データ〉の束縛は set variable
- 関数名へのアルゴリズム〈手順〉の束縛は set function
しかし、整合性の追求が分かりやすいとも意味があるとも限らないので、通常は割とテキトーです。この記事でも、set function ではなくて define にしてます。define を選んだ理由は「関数は『定義する』と言うことが多いからな」です -- テキトーですな。
それと、名前と型の区切りがコロンと空白が混じっています。読み返して気にはなったのですが、マーイイカ。
[/補足]
問い合わせの場合は、引数の型はデータベース型に限ります。そして、戻り値型は必ずテーブル型です。データベース型やテーブル型の表現に、カラム名を入れてもかまいません。例えば:
\T{declare }\N{俺の問い合わせ} : \\
\quad (\N{品}\:\mrm{Pow}(\N{商品タプル} ), \N{店}\: \mrm{Pow}(\N{店舗タプル} ) )
\to \\
\qquad \mrm{Pow}(\, (\N{名}\: \Ty{TEXT}, \N{価}\:\Ty{NATURAL}) \,);
`$
カラム名は、人間に対するヒントになります。それと、問い合わせを定義する際の仮引数名(後述)としても使います。ただし、ここで使うカラム名はあくまで仮の名前なので、リネームが許されます。リネームは、ラムダ計算でいうアルファ変換ですね。
上記のカラム名〈仮引数名〉は、スコット・ブラケットによる形式意味論〈formal semantics〉には何の影響もありません。カラム名によらず、$`\N{俺の問い合わせ}`$ という名前の関数は、次のような集合のあいだの関数〈写像〉です。
$`\quad \mrm{Pow}(\BR{\N{商品タプル}}) \times \mrm{Pow}(\BR{\N{店舗タプル}}) \to \mrm{Pow}(\mbf{Str}\times \mbf{N}) \In \mbf{Set}\\
\T{where}\\
\quad \BR{\N{商品タプル}} = \mbf{Str}\times \mbf{N}^3\\
\quad \BR{\N{店舗タプル}} = \mbf{Str}\times \mbf{N}^3
`$
強調したいことは、問い合わせも単なる関数だということです。テーブルやデータベースは、型/データ/型付き変数/束縛 などの概念で説明できました。何も特別なことはありません。同様に、問い合わせにも何も特別なことはなくて*2、特定目的の関数として説明できます。
「特別だとは思わない」ことにより、テーブル/データベース/問い合わせも、一般論で一様一律かつ透明に取り扱えます。「特別だ、違うんだ」と思うと、雑念で目が曇ってしまいます。
式、変数、演算記号、定数、環境
「データベースだからといって特別なことはない」ということは、中学校や高校で習った一般的知識はデータベースでもそのまま使えるということです。
中学校で一次関数や二次関数を習いました。
$`\quad f(x) = ax + b\\
\quad g(x) = ax^2 + bx + c
`$
より一般には多項式関数ですね。関数を定義する式〈expression〉が多項式〈polynomial〉である関数が多項式関数〈polynomial function〉でした。
同様に、問い合わせ式〈query expression〉という式で表現できる/定義される関数は問い合わせ式関数〈query-expression function〉ということになります。実際は単に問い合わせ〈queruy〉と呼んでいます。
多項式(一次式や二次式)には、変数*3〈variable〉が含まれます。その変数が関数の仮引数〈formal parameter〉になります -- 中学校の習慣では文字 '$`x`$' を使うのでした。
データベースの場合は、問い合わせ(と呼ばれる関数)の引数データはテーブル型データです。複数のテーブル型データを受け取ることもあるので、問い合わせ(と呼ばれる関数)は多引数関数〈多変数関数〉かも知れません。多変数関数とみなした問い合わせの個々の仮引数の型はテーブル型です(引数全体としての型はデータベース型)。テーブル型は単一の型を意味するのではなくて、様々なタプル型達のベキ集合型として得られる型達の総称がテーブル型でした。
多項式は足し算と掛け算の組み合わせなので、当然ながら足し算の記号と掛け算の記号(省略されることが多いが)を含みます。式を組み立てるには基本演算〈{primitive | basic | atomic} operation〉の記号が必要なのです。
データベースにおける問い合わせ式も同様で、基本演算の組み合わせとして構成されています。テーブル型データが集合(ベキ集合の要素)なので、基本演算は集合演算です。問い合わせ式のなかで使われる基本演算は3つで:
- 集合の直積〈direct product〉
- 集合の関数による像〈image〉
- 集合と関数を組み合わせたファイバー積〈fibered product〉
これら3つの基本演算は引き続く記事で述べる予定です。他に、集合の合併・共通部分・補集合などのよく知られた集合演算が使われるかも知れません。
さて、一次関数 $`f(x) = ax + b`$ に出てくる $`a, b`$ は定数〈constant〉だと言われます。でも、数値じゃなくてアルファベットの文字なので定数じゃないですよね。変だと思いませんか? $`a, b`$ も変数だと思えば:
$`\quad f(a, b, x) = ax + b`$
関数 $`f`$ は3変数関数〈3引数関数〉になります。実際は3変数関数なんです。$`a, b, x`$ はいずれも変数です。$`a, b`$ を定数と呼ぶココロは、「変数 $`a, b`$ の値(例えば、$`a = 2, b = 1`$)は、環境から与えられると考えるから」です。変数 $`x`$ に具体的な値を入れて計算する前に、$`a, b`$ の値は決まっているのだ、ということです。$`a, b`$ の値を決めるのは、計算を実行するときの環境〈environment〉です。
変数なんだけど、環境により値が決まる変数を定数と呼ぶわけ*4ですが; データベースでは、問い合わせ式に含まれる変数(の名前)が定数(環境から値が決まる)か、それとも明示的に引数として渡すのかハッキリしません。関数への引数渡し〈argument passing〉のメカニズムが使われることがなくて、環境からデータを得る方法がほとんど(すべて?)です。例えて言えば、局所変数(引数変数含む)を使わず、大域変数だけでプログラミングしているようなものです。外部の汎用プログラミング言語の引数渡しメカニズムを使えばいいので困らないのかも知れませんが。
ここでは、環境と明示的な引数渡しはハッキリと区別します。以下の例で(詳しい説明は引き続く記事)、問い合わせ $`\N{俺の問い合わせ}`$ 内で使うテーブル型データ達のうち、$`\N{俺の条件}`$ は環境としてセットされて、$`\N{品}, \N{店}`$ は引数として渡されます。大域変数に $`\N{商品}, \N{店舗}`$ があるので、大域変数と引数変数(問い合わせ内で使う局所変数)を区別するために、あえて短い名前 $`\N{品}, \N{店}`$ を引数変数名(カラム名でもある)に使っています。
\T{declare } \N{俺の条件} : \mrm{Pow}(\, (x\: \Ty{NATURAL}, y\: \Ty{NATURAL}) \,);\\
\T{set } \N{俺の条件} := \{(x, y) \mid x \le 250 \;\land\; y = 1 \};\\
\;\\
\T{declare }\N{俺の問い合わせ} : \\
\quad (\N{品}\:\mrm{Pow}(\N{商品タプル} ), \N{店}\: \mrm{Pow}(\N{店舗タプル} ) )
\to \mrm{Pow}(\, (\N{名}\: \Ty{TEXT}, \N{価}\:\Ty{NATURAL}) \,);\\
\T{define }\N{俺の問い合わせ}(\N{品}, \N{店}) := \\
\quad \T{from }\N{品}, \N{店}, \N{俺の条件}\\
\quad \T{join }\\
\qquad \N{品}.\N{価格} = \N{俺の条件}.x\\
\qquad \N{店}.\N{地域} = \N{俺の条件}.y\\
\quad \T{select }\N{品}.\N{商品名}\T{ as }\N{名}, \N{品}.\N{価格}\T{ as }\N{価}\\
\quad \T{end}
`$
名前の種類を示しておきましょう。
| 名前 | 名前の種類 |
|---|---|
| 俺の条件 | 変数名 |
| x | カラム名 兼 論理式で使う変数名 |
| y | カラム名 兼 論理式で使う変数名 |
| 俺の問い合わせ | 関数名 |
| 品 | カラム名 兼 関数の仮引数名 |
| 店 | カラム名 兼 関数の仮引数名 |
| 名 | カラム名 |
| 価 | カラム名 |
| 価格 | カラム名 |
| 地域 | カラム名 |
SQLでは、ストアードプロシージャを使ったところで、テーブル型データを引数として受け取る関数は書けないようです。(色々と事情があって書けないのでしょうが、もし書けるなら教えて下さい。)関数に大域変数を渡した〈大域変数に関数を適用した〉結果は、もちろん通常のSELECT文で書けます。
上の疑似コードの関数 $`\N{俺の問い合わせ}`$ に、大域変数 $`\N{商品}, \N{店舗}`$ (に束縛されているテーブル型データ)を渡して戻る値を通常のSELECT文で書いてみます。環境にセットしたテーブル型データ $`\N{俺の条件}`$ はWHERE句にします。以下のようになります。
SELECT 商品.商品名 AS 名, 商品.価格 AS 価 FROM 商品, 店舗 WHERE 商品.価格 < 250 AND 店舗.地域 = 1 ;
動機と目的と方針
まだ話は途中ですが、「その1」はこの節で終わりにします。この節では、ここまでのまとめ、そもそもの動機と目的、残りの部分(引き続く部分)の予告と方針などを述べます。
「概念的に単純明快合理的なデータベース問い合わせ言語」を抽出したい動機は、最初は(冒頭に書いたように)「問い合わせを書く疑似コード用言語が欲しい」だったのですが、「データベース分野で使われている手法・道具を論理やテンソル計算でも使いたい」に変容しました。SQL SELECT文の背後にある発想はとても優れていて、複数の論域〈domain of discourse〉達を同時に扱う述語論理の記述や、テンソルネットワークの記述に好都合です。複数の分野で共通に使える記述言語とするためには、データベースでしか通用しない部分は削り落として、問い合わせ言語のエッセンスだけを抜き出したいのです。
エッセンシャルな問い合わせ言語を支える下部構造は型システム〈type system〉です。型システムは次の2つの能力を持ちます。
- 既存の幾つかの型達から、タプル型〈tuple type〉を作れる。
- 既存の型から、ベキ集合型〈powerset type〉を作れる。
上記の型構成〈型生成〉能力を使って、テーブル型(基本型のタプル型のベキ集合型)を定義できたのです。テーブル型のタプル型はデータベース型でした。データベース型のタプル型を作れば、連合データベース型〈federated database type〉と言えるでしょう。テーブル型のベキ集合型を作ると、「データベース:: テーブルのキーって何なのよ? // テーブルクラス」で導入したテーブルクラスを要素とするテーブルクラス型〈table class type〉が作れます。
$`\mrm{Pow}(\, (\Ty{NATURAL}, \Ty{TEXT})\,)`$ はテーブル型の型表現〈type expression〉ですが、その意味〈デノテーション | 数学的解釈〉は $`\mrm{Pow}(\mbf{N}\times\mbf{Str})`$ でした。“ベキ集合”と“二値ブール値達の集合への関数空間〈写像集合〉”は同型です。
$`\quad \mrm{Pow}(\mbf{N}\times\mbf{Str}) \cong \mrm{Map}(\mbf{N}\times\mbf{Str}, \mbf{B})`$
一般に、$`\mrm{Map}(X, \mbf{B})`$ は述語〈predicate〉達の集合です。これは述語論理〈predicate calculus〉の舞台です。自然数変数と文字列変数を持つ論理式は、$`\mrm{Map}(\mbf{N}\times\mbf{Str}, \mbf{B})`$ の要素である述語を定義します。
型システムがベキ集合型構成 $`\mrm{Pow}(\hyp)`$ を持つということは、述語達の関数空間構成 $`\mrm{Mapp}(\hyp, \mbf{B})`$ を持つことです。述語〈命題〉や高階述語〈メタ命題〉を“データとして”自由に扱えます。1つのテーブル型データは1つの述語です。1つのテーブルクラス型データは1つの二階述語(述語を引数とする述語)です。
さて、$`M\in \mrm{Map}(\mbf{N}\times\mbf{Str}, \mbf{B})`$ としましょう。前段落では $`M`$ は述語だと言いました。$`M`$ への2つの引数を下付き添字にして $`M_{i, s}`$ と書いてみます。$`(M_{i, s})_{i\in \mbf{N}, s\in \mbf{Str}}`$ と書くと何に見えますか? 行列に見えますよね。
添字をもっと増やせばテンソルです。ただし、係数〈スカラー〉は実数や複素数ではなくてブール値〈真偽値〉です。$`n`$個のカラムを持つテーブル型データ〈テーブルインスタンス | テーブル状態)は、それがディスクに格納されていようが、WHERE句の条件だろうが、ランク $`n`$ のブール係数テンソルなのです。
問い合わせ式は、幾つかのテーブル型データ達を組み合わせて、問い合わせ結果である仮想テーブルを生成します。これは、テンソルネットワークが、幾つかのテンソル(と呼ばれるデータ)達を組み合わせて、より大規模なテンソルを定義していることと同じです。
述語論理もテンソル計算も、分野固有の伝統的記法を持ちます。それで間に合っているとは言えるのですが、それが便利であるか、それが最良であるかは疑問です。SQL SELECT文を述語論理やテンソル計算向けにチューニングした記法は、伝統的記法よりむしろ便利に思えます。
SQL SELECT文の注目すべき/特筆すべき機能はジョイン〈join〉です。幾つかのテーブル(型とデータ)はジョインにより組み合わされます。ジョインは、述語論理の等式命題に対応し、テンソル計算ではクロネッカーテンソルに対応します。圏論的構成としてはファイバー積(コスパンの極限)に対応します。
ジョインを活用したSELECT文的記法は、ストリング図、ワイヤリング図、ケリー/マックレーン・グラフなどの図式とも極めて相性がいいものです。ジョインとセレクトリスト(カラムの選択)により、図式のワイヤリングを簡潔に記述できます。
引き続く記事では、ジョインと、セレクトリストの機能である射影の話をするでしょう。変数と環境の話を敷衍する必要もありそうです。