ホームリレーショナル・データベースの世界



そんなあなたが大好きよ(SQL礼賛)

 この文章の目的は、SQL を誉めちぎることです。なんでそんなことするかって? 普段から散々お世話になっている言語ですし、大変優れた言語ですから、誉めても罰は当たらないでしょう。だから、3値論理を導入したせいで「IS NULL」なんていう紛らわしい述語が必要なことや、実装ごとの拡張がバラバラで互換性がないことや、計算完備でないことや、そういう諸々の欠点にはこの際目をつぶります。

 というわけで、文章の趣旨に同意いただけたらご一緒にどうぞ。SQL ばむざい!


下準備

 しかし SQL を誉める前に、彼女がどういう言語なのかを簡単に説明しておきましょう。相手のことをしらずに誉めるのも無責任な話です。

 SQL(Structured Query Language) が誕生したのは、1970年代のことです。1974年に Donald Chamberlin らが IBM の研究所において SEQUEL (Structured English Query Language)という言語を定義したのが始まりです。この言語は 1974年から 75年にかけて SEQUEL-XRM という名称で IBM が最初にプロトタイプを実装しました。1976年から 77年には SEQUEL/2 という改定版も定義され、その後名称が SQL へ変更されます。1977年、IBMのプロトタイプである System R に実装され、翌年には Oracle や SYBASE など現在でも御馴染みの商用 DBMS が実装しました。

 商用への応用と歩調を合わせて、標準化もスタートします。1982年に米国規格協会(ANSI)はデータベース委員会 X3H2 に対し、標準リレーショナル言語を提案するための準備を依頼します。この提案は1986年に批准されましたが、基本的には IBM 版の方言という性格のものでした。

 転機が訪れるのは1987年、この ANSI 規格が ISO によって国際標準として認められたときです。日本でも同年に JIS で規格化されました。この ISO と JIS によって規格化された SQL が標準SQLと呼ばれます。また最初の規格バージョンには非公式に SQL-86 という名前が付けられています。

 1989年にこの規格が拡張され、やはり非公式に SQL-89 と呼ばれる規格が完成しました。さらに同年、ESQL(Database Language Embedded SQL)という関連規格が考案されます。いわゆる「埋め込みSQL」です。ISO と ANSI の両委員会は、当初の規格を大幅に拡張したバージョンを定義し、これが SQL2 あるいは SQL-92 と呼ばれるものになります。このバージョンは1992年の終わり頃、「国際標準 ISO/IEC 9075:1992 データベース言語 SQL」 として承認されました。

 その後、 SQL-99(または SQL3)が、SQL-92 から7年の歳月をかけて標準化されました。「ようやくか」とお待ちかねの人も多かったであろう真理値型や OLAP 機能といった便利なアイデアや、配列型や REF 型のようなあまり有難くないアイデアが盛り込まれています。そして最後に、現在の最新標準がSQL:2003(なぜか区切り文字がハイフンからコロンに変わった)です。MERGE 文、連番生成機能、ウィンドウ関数(RANK や ROW_NUMBER)など重要な機能が追加され、SQL はますます進化を遂げています。



良いところ その1: 最適化

 ユーザが SELECT 文を書くとき、記述されるのは、欲しい結果の内容です。結果をいかにして得るかという「手続き(procedure)」は記述されません。つまり、SQL は手続き型言語ではないのです。手続きを考えるのは DBMS の仕事であって、ユーザが気にすることではない、というのが関係モデルの基本理念の一つだからです。だから、何回ループさせて、どこで条件分岐させて、といった具体的な手続きは、SQL の中には一切現れません。SQL で一度でもループや分岐を使ったことがありますか? ないですよね[1]。コッドが関係演算子を定義するときに一番気を遣ったのがこの点でした。
関係操作では、関係全体をまとめて操作の対象とする。目的は繰返し(ループ)をなくすことである。いやしくも末端利用者の生産性を考えようというのであれば、この条件を欠くことはできないし、応用プログラマの生産性向上に有益であることも明らかである。[2]

 また、デイトも言っています。
 …… SELECT、PROJECT、JOIN のような関係型の演算子は、集合レベルの演算子である。結果として、SQL のような関係型の言語は非手続き型の言語と呼ばれるが、これはユーザは「いかに」ではなく「何を」を指定する ―― つまり、獲得するための手続きを指定することなしにユーザは何が欲しいかをいう ―― からである。[3]

 「非手続き型」という言い方は正式なものではありませんが、データ操作に関する SQL の特徴の一面をよく表しています。より積極的な定義として、宣言型言語(declarative language)という名称もあります。データを取り出す手続きを DBMS が勝手に行なってくれることから、RDBMS はまた自動ナビゲーション・システム(automatic navigation system)とも言われます。

 データにアクセスする方法が複数ある場合、手続き型システム(デイトが具体的に想定しているのは網モデルや階層モデルの DBMS )では、ユーザが数ある方法から一つを決めなければなりません。従って、メモリ効率や応答時間の長短、ソースコードの読みやすさなどいろいろな要素を考慮する必要が生じます。仕事で C言語や Perl、Java を使う SE やプログラマの方なら容易に同意していただけるでしょうが、これはなかなかに大変な作業です。

 ですが、関係モデルのシステムにおいては、オプティマイザ(optimizer)と呼ばれるプログラムが、ユーザの代わりにアクセスパスを自動的に決定してくれます。素晴らしい話じゃありませんか。アクセスパスの決定などという低レベルな仕事は、DBMS にさせておけばいいのです[4]。ユーザ様はもっと抽象度の高いレベルの問題に専心してください、というわけです。もっとも現実には、より効率のよいアクセスパスをオプティマイザに選ばせるために、ルール・ベースとコスト・ベースのどっちしようか悩んだり索引を張ったりテーブルの統計を取ったり SQL を解析したりメモリの配分を考えたり …… 結局のところユーザが面倒を見なければいけないこともかなり多いのですが、ここではそういう問題点からは目を逸らします。文章の趣旨に反しますから。それに、理想的には、こういう作業は本当はユーザが行なうべきではないのです。低レベルの作業をユーザが負担しなければならないという事実は、現在の DBMS がまだまだ非力であることの証拠に他ならないのです。各ベンダの一層の努力を期待します。



良いところ その2: アドレスからの解放

 この話はあちこちで繰り返しているので「またか」と思われる人も多いでしょうが、大切なポイントなので何度でも使いまわしますよー。

 前節で、SQL には手続きが現れないという特徴を紹介しました。その理由は、関係モデルが極力、抽象水準を高めることを目的にしているからでした。全く同様の理由から、SQL には、基本的に変数も登場しません。手続き型言語を使うときに変数を一つも使わずにプログラミングするというのはまず不可能ですが、SQL ではそれがごく当然のように行なわれます。(余談ですが、何かの特徴を把握するときには、それ以外の何かと対比させるのが分かりやすいですね。)

 「基本的に」という留保をつけたのは、SQL においてもテーブルやビューの名前は、関係変数の名前なので、その意味で全く変数が現れないわけではないからです。関係モデルが排除しようとしたのは、別に変数そのものではなく、物理レベルにおけるアドレスと、そのプログラミング言語レベルにおける表現としての変数です。一般にそういう変数は「ポインタ」として使われます。従って先の言葉を修正すると、「SQL には変数が現れない」ではなく「SQL にはポインタが現れない」ということです。

 ポインタや添え字が示すのは、物理的なものにせよ論理的なものにせよ、データのアドレス(=番地、住所)です。土台の計算機の仕様がアドレスでデータを管理するようになっているため、その上で動くプログラムも、ストーカーのごとくデータの住所に固執します。人柄や能力よりもどこに住んでいるかの方が重要なのです!

 しかし、こういう低レベルの概念を使うことは、コンピュータには平気でも、人間には一苦労です。なぜなら、電話番号や郵便番号の例を見ても分かる通り、アドレスというのは無意味な数字や記号の羅列だからです。コンピュータは意味のあるなしなんて意に介さないのでこういう単純作業はお手の物ですが、人間は無意味な記号操作が非常に苦手なように作られています。事実、手続き型言語においては、ポインタ周りは昔からバグの温床で、最近は何とかしてポインタをプログラマから隠そうという方向へ発展しています(それでも、配列の添え字まで隠すのは、なかなか難しいでしょう)。

 「関係モデルでは、位置による番地決めを一切排して、完全に内容による番地決めを採用した」[5]というコッドの言葉は、アドレスの呪縛からいかにして逃れるかという昔からの大問題に対する、データモデルの観点からの一つの解答でした。そして、その後のリレーショナル・データベースの成功は、コッドの洞察が正しかったことを裏付けています。一つのエレガントなデータ構造は百の優れたコードに勝る、ということです。そういえば、かの E.S.レイモンドも言っていたではありませんか。

 「賢いデータ構造と間抜けなコードのほうが、その逆よりずっとまし」[6]
 



良いところ その3: 閉包性

 SQL には色々な関係演算子が登場します。コッドが初期に定義した射影、制限、和、差などの8個の基本的な演算子に加えて、現在では他にも色々と便利な演算子が追加され、その数はかなり増えています。これら演算子に対して関係が満たすべき重要な性質が「閉包性(closure property)」です。これは、「ある演算子の入力と出力が共に関係である」という性質です[7]。関係がこの性質を満たすおかげで、ある演算の出力を別の演算の入力にすることが可能になります。こうして、和の射影をとるとか、制限の差をとる、といった操作の複合的な組み合わせを考えることができます。この性質は、とりわけ、サブクエリ(副問い合わせ)とビューという重要な技術の基礎になっています。

 関係の閉包性は、UNIX のパイプの概念とよく似ています。UNIX のファイルも、様々なコマンドに対して入力・出力になるという閉包性を持っています。それゆえ、「cat text.txt | sort +1 | more」のように、コマンドを複合的に組み合わせてスクリプトを記述することができます。これが UNIX のシェルプログラミングに高い柔軟性を与えています。イメージ的には、バケツリレーのようなものです。演算子が人、関係やファイルが、手から手へ渡されるバケツです。ただ、渡されていく過程でバケツの中身が変化していくのが、火事場のバケツリレーとは異なる点ですが。

 以上のことをまとめると、UNIX におけるファイルがシェルコマンドについて閉じた集合を形成するのと同様に、関係モデルにおける関係は関係演算子について閉じた集合を形成する、と言うことができます。この二つの例で見た閉包性は、数学で言われる閉包性の応用です。「ファイル」や「関係」は数学の「(Field)」と呼ばれる集合の一種(代数閉包)です。このことは、私たちにとって一番身近な体である有理数や実数を例にとって考えるとよく分かります。数学における基本的な演算子は、足す、引く、掛ける、割るの四つ、いわゆる四則演算です。言うまでもなく、有理数と実数は四則演算について閉じています(残念ながら、整数は割り算について閉じません)。この特性は、関係やファイルの閉包性と全く同じものであることが分かります。

 ところで、SQL の演算子の中には、せっかくの美しい特性であるこの閉包性を破る例外が存在します。例えば、階層問い合わせに使われる Oracle の CONNECT BY がそうです。一見すると、CONNECT BY は関係を出力しているように見えます。確かに、これはテーブルを出力してはいるのですが、しかしそのテーブルは、行が上から下へ順序づけられているのです。関係は行の順序を持たない集合ですから、厳密に見た場合、CONNECT BY は閉包性を破っています(参考:「なぜ"関係"モデルという名前なの?」)。SQL に階層問い合わせの機能を実装することは、もちろん意義あることですが、それを優先するあまり、Oracle は決して美しいとは言いがたい逸脱を犯してしまっています。

 関係モデルでは、関係の定義は、第一には、属性の組み合わせによって与えられることはよく知られています。しかしまた、本節で見たように、演算に対する閉包性という特徴からも考察できるということが、お分かりいただけると思います。



終わりに

 SQL というのは、変わった言語です。こういう感想は、人によって感じ方に差があるとは思いますが、概して最初に手続き型言語を学んだ正統派のプログラマや SE ほど そう感じることが多いと思います。まだ違和感程度で済めばいいのですが、もっと程度が進むと「こんな言語はまともなプログラマが触るべきものではない」という反感に変わるケースも少なくありません。

 SQL が奇異に感じられる主な理由は、SQL が「集合指向(set-oriented)」という発想に基づいて設計された言語で、現在のところこの設計思想を持つ言語が少数派である、という点にあります。オブジェクト指向言語がデータをオブジェクトとして表現するように、集合指向言語はデータを集合として表現します。SQL の場合、特にその集合は「命題の集合」を意味します。この発想にだんだんと慣れていくことで、SQL を使いこなせるようになり、最初に感じた違和感も軽減されていくはずです。この文章が、皆さんから SQL のとっつきにくさを取り除く一助になれば幸いです。



[1] 埋め込み SQL でカーソルを使う場合は、話が別です。カーソルとループは確かにセットで使う場合が多いですが、あれはホスト言語の世界の話です。SQL とは別次元です。
 SQL では複数行単位、つまり集合単位での操作(set at a time)が可能ですが、ホスト言語は通常複数行を一度に扱えません(record at a time)。従って、SQL の集合レベルの水準とホスト言語の行レベルの水準を橋渡しする機能が必要になります。これがカーソルです。カーソルとは、手続き型言語におけるファイルのレコードポインタに相当する機能です。
 また「SQL に分岐がないと言うが、あなたの好きな CASE式は分岐の表現方法ではないか」という疑問を持たれる方もいるでしょう。確かに CASE式は SQL において分岐を表現する強力な道具ですが、それはあくまで値の分岐をさせているに過ぎません。手続き型言語における if文のように手続きの分岐をさせているわけではない、というところが重要な相違です。

[2] E.F.コッド「関係データベース:生産性向上のための実用的基盤」p.455。「応用プログラマ」というのは、多分「アプリケーション・プログラマ」の和訳でしょう。原文にあたったわけではありませんが。

[3] C.J.デイト『データベースシステム概論 第6版』p.68

[4] ここで言う「レベルが低い」とは、言うまでもなく「物理レベルに近い」の意味です。決して手続き型言語の使用者の方々を貶める意図はありません、はい。

[5] コッド、p.459

[6] E.S.レイモンド「伽藍とバザール」。それなのに SQL-99 で再びポインタ(REF型)が持ち込まれてしまうとは …… いやいや、ここでは言わない約束でした。

[7] たまに SELECT 文の結果、一行も返らないときがありますが、そういう場合も、データベースは何も返していないのではなく、ちゃんと空な関係(空集合)を返しています。目に見えないので実感湧かないでしょうけど。さもないと、閉包性が崩れてしまいますから。


Copyright (C) ミック
作成日:2002/12/01
最終更新日:2006/11/07
戻る
b_entry.gif b_entry.gif