SQL以外の関係データベース言語が出てこない理由を考える

Tutorial DLINQがあるから「出てこない」というのは本当は正しくないけど、プログラミング言語については野心的なものから冗談みたいなものまで色々な言語が日々作られているのと比較すると、関係データベース向けの言語は全然出てこないという印象がある。
波カッコとbegin endのどちらがいいかとかコロンはターミネータとセパレータのどちらを表すのがいいかとかですら議論が発生するのに比べると、データベース言語についてはSQLの問題点とか代わりの言語といった話はほとんど聞かない。

関係データベースの基本アイデアは(コッドの論文をおぼろげに思い出せば)、

  • データを関係(直積集合の部分集合)で表現する
  • 問い合わせ(クエリ)は、集合演算(関係代数演算)によっておこなう

で、 その最小限の機能を実現するオモチャ言語を作るのはそれほど大変ではないように思える。関係データはタプル(配列)の列で表現すればいいし、それへの操作(関係代数演算)も素朴に実装すればいい。『プログラミング言語AWK』の第4章「レポートとデータベース」でも数ページで単純な機能を解説・実装している。
ところが属性名(カラム名)のために話が面倒になる。

属性名の唯一性

普通の集合演算ではタプルの各成分を数字(何番目の成分か)で区別するのだけど、関係データベースでは普通、各成分に名前(属性名、カラム名)を与えて区別する。それだけならタプルを構造体や(オブジェクト指向言語でいう)オブジェクトに置き換えれば済む。
でも関係代数の場合、関係の結合や直積をすると新しい型のタプルが作られ、そのとき属性名が重複してしまう可能性がある。これに何らかの対処をする必要がある。
例えば俳優personと登場人物charactorという二つの関係データの自然結合を考える(personのnameとcharactorのcastについて結合する)。
person

name age

charactor

name age cast

Tutorial Dでは、名前の重複が起こらないように結合前に属性名を変更しておく必要がある。また自然結合は、同じ名前の属性に対しておこなわれる。

person rename(prefix "" as "p_")
join charactor{age, cast} rename(age as c_age, cast as p_name)

結合の結果、次のような関係が作られる。

p_name p_age c_age

SQLでは、属性(カラム)に名前がついていなくても構わない。

それでは、結果の属性名(より正確には、列名)を指定するという問題に、SQLはどのように対処するのか。答えは、「きちんと対処しない」である。(C.J.Date『データベース実践講義』)

select person.name, person.age charactor.age
  from person, charactor
  where person.name = charactor.cast;
person.name person.age charactor.age

テーブル名による修飾(「person.」や「charactor.」の部分)はカラム名の一部ではない(はず)なので、属性名の唯一性が成り立っていない。

他にも新しい属性を追加する場合も、Tutorial Dでは属性名が必須だけど、SQLではなくてもいい。
Tutorial D

extend person add(age - 1 as fudged_age)
name age fudged_age

SQL

select name, age, age - 1 from person;
name age  

SQLでは、カラム名の唯一性が成り立っていなかったりカラム名がなかったりしても、参照する上で支障がない限りは許容するようにできている。そのため(属性名は関係データの必須構成要素のはずなので)SQLでは、関係演算の結果として関係ではないデータが生じる場合がある(関係演算の閉包性が成り立っていない)。
またSQLのようなテーブル名やカラム名の扱いは、変数と相性が悪いように感じる。もしもSQLに変数を追加したとしたら、

x = person
select x.name, person.age charactor.age
  from x, charactor
  where x.name = charactor.cast;

と書いていいのかどうか(テーブル名の修飾はどの範囲で有効となるのか)。

こうしたSQLの特徴は、新しいデータベース言語を作ろうとするときの障害になる。新しい言語を単なる興味からだけでなくある程度の実用も考えて作る場合、まずはSQLへのトランスレータを書いて使ってみるというのがたぶん現実的なやり方になる。
そうするとデータ構造上の首尾一貫性を求めたり変数を導入したりして普通のプログラミング言語に近づけようとすればするほどSQLとの乖離が大きくなって、トランスレータが書きにくくなる。逆に初めからSQLへの変換を念頭において言語設計をしてしまうと、SQLの文法や機能を引きずった中途半端な言語になってしまう(Schemeでマクロを使って実現されたSchemeQLがその例だと思う)。

追記:関連文献

式の中の属性名の扱い

Tutorial DとSQLのどちらにも共通する何となく気持ち悪い構文として、計算式の中での属性名(カラム名)の使い方がある。
Tutorial D

(person where age > 20){name}

SQL

select name from person where age > 20;

ここでのageは普通のプログラミング言語での変数のように使われているけど、このageは特定の値を指しているのではなく、各行のageカラムを指定している。その点では変数というより構造体やオブジェクトのメンバ名に近いけれど、それともズレていて何か気持ち悪く感じる(Rubyでselfが省略された場合がいくらか近いかもしれない。各行の処理毎にselfまたはageの値が変わる、というような)。
この気持ち悪さはカラム名をテーブル名で修飾したものにも感じる。
SQL

select name from person where person.age > 20;

これは一見、構造体のメンバ指定に似ているけど、それとは違う。ここでの「person.age」は「personテーブルのageカラムの値」を指しているのではなく、「personテーブルの各行のageカラムの値」を表している。
それから集計関数での属性名・カラム名の使い方にも同様の違和感がある。
Tutorial D

summarize person add(avg(age) as avg_age)

SQL

select avg(age) from person;

これらは、LINQのように変数にタプル(行)を代入した上で参照するというのが自然な感じがする。

from p in person
where p.age > 20
select p.name;
person.Where(p => p.age > 20)
      .Select(p => p.name);
person.Average(p => p.age);

(集計関数については、集計関数を引数で渡す方がもっと好みかも)
でも、このような文法を採用しても、SQLへのトランスレータを書くとたぶんノイズにしかならない。

本題

本題は「SQL以外の関係データベース言語が出てこない理由を考える」だったけど、考えるのがめんどうになったので尻切れとんぼで終わる。
『データベース実践講義』を読み返した方がいいかもしれない。それか『データベースシステム概論』をちゃんと読むとか。

データベースシステム概論

データベースシステム概論