ラベル Tutorial D の投稿を表示しています。 すべての投稿を表示
ラベル Tutorial D の投稿を表示しています。 すべての投稿を表示

2010年12月8日水曜日

Tutorial D で外部キーの定義 - 整合性制約で記述

Tutorial D の拡張と要約 - 値の計算と集約」のつづき

Tutorial D で関係変数の定義と代入」 では、以下のモデルに基いた関係変数を定義した。

5093301582_c738e3bb96

ソースコードはこちら。

上記 「割当て」 に対応した関係変数の定義は以下の通り。

var assignments base
  relation { id integer
           , p_id integer
           , g_id integer
           , date character }
    key {id};

「外部キー」を設定するのを忘れていたので、以下のように修正。

var assignments base
  relation { id integer
           , p_id integer
           , g_id integer
           , date character }
    key {id}
    foreign key {rename(p_id as id)} references persons
    foreign key {rename(g_id as id)} references groups;

しかし、Rel ではエラーが出て定義できない。 (+_+)

 

外部キーを整合性制約として記述する

Rel for M359 students によると「外部キー」は、

not defined in Tutorial D (use constraint instead,

Tutorial D では外部キーに対して特別な構文があるわけではなく、 「制約」 で記述するとのこと。

4873112753これに関して データベース実践講義 (p71) では次のように述べられている。

ここまで外部キーについて説明してきたのは、それらが実際に非常に重要であり、当初定義されたモデルの一部であるからだ。だが筆者が思うに、それらが実際には基本原理ではなく、現実に必要になることが多い特定の整合性制約の省略表記にすぎないことを強調しておくべきだろう。

 

制約の書き方

先ほどの "Rel for M359 students" には外部キーの制約を、

  • 半結合 
  • 関係が空であることを調べる IS_EMPTY 演算子

の二つを利用した方法が書かれている。

constraint 制約の識別子
  IS_EMPTY(参照元の関係変数 not matching 参照先の関係変数);

これを参考にして、上記の 「割当て」 に整合性制約を設定してみる。

 

「割当て」 の整合性制約

設定したい制約は、「割当て」 における 「人」 を指し示す属性 p_id に、「人」 として存在しないタプルのキーが入力されないようにすること。

大雑把に次のように言い換えることができる。

  • 「割当て」 から見て、「人」 へとリンクしない「割当て」のタプルがあってはならない。

これを制約として書くなら、

constraint AssignmentsFKPersons 
  IS_EMPTY(assignments not matching persons rename(id as p_id));

ただし、IS_EMPTY は大文字で記述する必要がある。

もう一つ、「割当て」における 「グループ」 を指し示す属性 g_id に、「グループ」 として存在しないタプルのキーが入力されないように制約を設定。

constraint AssignmentsFKGroups 
  IS_EMPTY(assignments not matching groups rename(id as g_id));

 

演算子は大文字・小文字を区別する

ところで、演算子 IS_EMPTY を is_empty と小文字で書くとエラーとなる。 Rel / Tutorial D Grammar を見ると is_empty がなかったので、最初 Rel では関係が空であることを確認する演算子が用意されていないのかと思った。

Language Enhancements によると、

Case sensitivity: Rel language keywords are not case sensitive. By convention, keywords are shown here in upper case to visually distinguish them, but the parser recognises them in lower case as well. Rel identifiers and string comparisons are case sensitive.

キーワードは大文字・小文字の区別をしないけれど、識別子は区別される。 よって、演算子の名前は大文字・小文字が区別される。 ( cf. op_def )

これに対して、Rel / Tutorial D Grammarsummary に定義されている COUNT 等は、キーワードなので小文字で書いても問題ないようだ。

2010年11月12日金曜日

Tutorial D の拡張と要約 - 値の計算と集約

Tutorial D でリレーショナル代数」のつづき

拡張

既存の属性を元に計算した結果を関係に追加する。 SQL では SELECT 句においてカラムに演算子を適用することに相当。Date によると、

「オリジナルで定義されたリレーショナル代数の範囲を超えてしまう」

(データベース実践講義, p101より)

ために 「拡張」 と呼ばれる。

例えば、「人」 に対して、

  • 年齢の 2 倍
  • 男性であるかどうか?

となる属性を関係に追加したい場合は、

extend persons 
  add(age * 2 as DoubleAge, gender = 1 as isMale)

311-12-2010CropperCapture[3]

(対象のデータベースはこちら)

 

拡張の後に制限

上記の書き方を見ると SQL の方が簡単に思えるが、Tutorial D の拡張により返されるのは 「関係」 であるため、返される値に対してスムーズに制限を適用できる。

先ほどの結果に対して、

  • 年齢を 2 倍したら 50 以上で、かつ、男性である人

を抽出したい場合、

extend persons
  add(age * 2 as DoubleAge, gender = 1 as isMale)
  where DoubleAge >= 50 and isMale

と書くことができる。

 

要約

「人」 の要素数を知りたい場合は、

summarize persons 
  add(count() as pCount)

411-12-2010CropperCapture[4]

最高齢の人を求めるには、対象となる属性 age を max に与える。

summarize persons 
  add(max(age) as pMax)

511-12-2010CropperCapture[5]

定義されている要約は summary を参照。

 

特定の基準で要約 - by

SQL で言う GROUP BY に相当するもの。

例えば、性別ごとの人数を得るには、

summarize persons 
  by {gender}
    add(count() as pCount)

611-12-2010CropperCapture[6]

per を使っても上記と同じ結果が得られる。

summarize persons
  per(persons {gender})
    add(count() as pCount)

ただし、これだと書くのが面倒なので、上記のように by が定義されているのかな。

 

他の関係変数と組み合わせる - per

per は別の関係変数と組み合わせたときに有用。

例えば、「割当て」 を 「人」 の id ごとにカウントしたい場合に by を使うと、

summarize assignments 
  by {p_id}
    add(count() as pCount)

811-12-2010CropperCapture[8]

当然ながら、「割当て」 がされてない人は表示されない。

これに対し、「人」 を基準にして 「割当て」 を要約すると、

summarize assignments 
  per((persons rename(id as p_id)) {p_id})
    add(count() as pCount)

 711-12-2010CropperCapture[7]

「割当て」 がされてない人も表示される。

by と per の定義については、summarizeper_or_by を参照。

Tutorial D で外部キーの定義 - 整合性制約で記述」につづく

2010年11月11日木曜日

Tutorial D でリレーショナル代数

Tutorial D で関係変数の定義と代入」 のつづき

データベース実践講義」 における “5.2 オリジナル演算子” (pp.92-99) の練習。

SQL の相関サブクエリ (2)」 で利用したデータベースと同じものを使う。

5093301582_c738e3bb96

Tutorial D での定義は以下通り。

削除するときはこちらより。

 

制限

特定の条件に一致するタプルを含む関係を返す。例えば、男性を取得するには、

persons where gender = 1

111-11-2010CropperCapture[1]

SQL の WHERE 句に相当し、同じキーワード where を使うので覚えやすい。

 

射影

特定の属性を含む関係を返す。人の名前と年齢を取得するには、

persons { name, age }

211-11-2010CropperCapture[2]

SQL の SELECT 句に相当する。こちらは位置も表現も異なる。

SQL との違いは、以下を評価するとハッキリとする。

persons { gender }

411-11-2010CropperCapture[4]

リレーショナル代数で 「関係」 が返されるわけだから、重複はない。 SQL では SELECT 句で DISTINCT を指定することに相当。

 

結合

「人」 と 「割当て」 を結合する。 SQL の自然結合に相当。

persons rename(id as p_id) join assignments

ただし、結合には同じ属性名が使われるので、結合前に属性名を変更するため rename 演算子を利用する。

ここでは assignments で 「人」 を示すための属性として p_id を利用しているので、上記では persons の属性 id を p_id に変更している。

311-11-2010CropperCapture[3]

次に 「人」 「割当て」 「グループ」 の 3 つを結合し、「人の名前、グループ名、日付」 を表示する。

(persons rename(id as p_id)
  join assignments
  join groups rename(id as g_id, name as g_name))
    {name, g_name, date}

複数の属性名を一気に変更する場合は、rename 演算子において prefix を利用する。

(persons rename(prefix "" as "p_") 
  join assignments
  join groups rename(prefix "" as "g_"))
    {p_name, g_name, date}

結合は、join { A, B, C … } のように書くことができる。

join { persons rename(prefix "" as "p_")
     , assignments
     , groups rename(prefix "" as "g_")}
  {p_name, g_name, date}

 

交わり

「人」 の属性 id と 「割当て」の属性 p_id の交わりを求める。

(persons rename(id as p_id)) {p_id} 
  intersect assignments {p_id}

1011-11-2010CropperCapture[10]

交わりは、属性が全て同じ関係の結合と見なせるので、join を使っても書ける。

(persons rename(id as p_id)) {p_id} 
  join assignments {p_id}

 

デカルト積

「人」の名前 と 「グループ」名 のすべての組み合わせを求めたい。

デカルト積は、共通属性がない関係の結合と見なせるので、結合したい関係の属性名が重ならないように rename 演算子を用いる。このとき、prefix を利用すると便利。

(persons rename(prefix "" as "p_")
  join groups rename(prefix "" as "g_"))
    {p_name, g_name}

1111-11-2010CropperCapture[11]

SQL では FROM 句にテーブル名を並べることに相当。

 

半結合

「グループ」 に 「割当て」 られている 「人」 を抽出したい。

persons semijoin assignments

911-11-2010CropperCapture[9]

データベース実践講義 (p94) によると、

(半結合についてこれまでに聞いたことがないかもしれないが、実がとても重要な演算子である)。定義は次の通り。 r と s の半結合は、 r と s の結合であり、結果は r の属性に射影される。

semijoin の代わりに matching と書くこともできる。こちらの方が言葉としては理解しやすいかな。

SQL では exists 述語を利用して記述できる。

 

「人」 と 「人」 の和。

 (persons where id = 1) union 
  relation {tuple {id 100, name "Gonzou", gender 1, age 100}}

1211-11-2010CropperCapture[12]

 

直和

共通のタプルがあった場合に、エラーが出力される和も用意されている。

(persons where id <= 3)
  d_union (persons where id >= 3)

この場合、「人」 の id が 3 の人が共通してるのでエラーとなる。

 

特定の 「人」 を取り除きたい場合。

persons { name } minus
  relation { tuple { name "Hanko" }
           , tuple { name "Jirou" }
           , tuple { name "Sadayo" }}

 

半差

先ほどの 「半結合」 とは対照的な 「半差」。 SQL では not exists 述語に相当する演算子。

persons rename(id as p_id) 
  semiminus assignments

111-11-2010CropperCapture[1][4]

差は、属性が全て共通している関係の半差と見なせる。

persons { name } semiminus
 relation { tuple { name "Hanko"}
          , tuple { name "Jirou"}
          , tuple {name "Sadayo" }}

 

その他

「商」 はどうやって書いたらいいんだろう。。 (+_+)

Tutorial D の拡張と要約 - 値の計算と集約」へつづく

2010年11月10日水曜日

Tutorial D で関係変数の定義と代入 - Rel を利用して

SQL に対する批判

4873112753 C.J.Date の 「データベース実践講義」 には、SQL に対する批判が随所に書かれている。

例えば、1 章の 「概要」 だけ見ても、初っ端から次のようにある。

リレーショナルモデルに関する知識が SQL の知識にのみ基づくものであるならば、リレーショナルモデルを十分に理解しているとは言えない。 (p1)

既存の実装に対しても手厳しい。

SQL をリレーショナルに使用することはもちろん可能だが(...)、既存の実装は完璧というにはほど遠いため、そのように使用するとパフォーマンスが大幅に低下することがある。その場合には、「完全にリレーショナル」ではない方法に頼らざるを得ないだろう (同上, p4)

一貫して主張されているのが null の否定。

実体整合性のルールには、実は問題がある。それは、筆者が 「null」 の概念を完全に否定しているからである。すなわち、null はリレーショナルモデルに存在する意味がないというのが、筆者のこだわりなのである。(同上, p7)

また、リレーショナルモデルにおける 「関係」 が SQL で正しく扱われていないことも指摘。

  • SQL では行の重複が許されていること。

... 関係に重複するタプルが含まれることはあり得ない。なぜなら、本体はタプルの集合であり、数学における集合に重複する要素が含まれることはないためである。ところで、これは SQL には当てはまらない。知ってのとおり、SQL のテーブルには重複する行が含まれていてもかまわない。このため、一般的には、SQL のテーブルは関係ではない。 (同上, pp.14-15)

これは重複を許すと、RDB はリレーショナル代数に基いているのにも関わらず、「関係」 に対する演算が「関係」でなくなるため、 代数 ではなくなってしまうということかな。

  • 列に順序があること。

... 関係の属性についても、左から右への順序付けがあるわけではない。なぜなら、見出しも数学的な集合だからである。... SQL のテーブルの列は、左から右へ順序付けされている。(このこともまた、SQL のテーブルが一般的に関係でない理由の一つである)。 (同上, p15)

テーブルとビューについては、扱う 「関係」 という点から見れば、本質的には変わらないことが述べられている。

... こうした状況は SQL 規格 (...) からもうかがわれるほどで、一般的には (...) 「テーブルとビュー」という表現が使われている。当然ながら、そうした表現が使われたのでは、誰もがテーブルとビューが別のものであると勘違いし、「テーブル」が物理的なもので「ビュー」が実体のないものであると思い込んでしまうだろう。... ビューは「通常の関係」なので、通常の関係で実行できる処理は(少なくともリレーショナルモデルでは)ビューでも同じように実行できる。(同上, p17)

リレーショナルモデルで扱うのは 「関係」 という値と、それに対する演算であって、実装の問題は別腹と考えるのはシンプルですっきりしている。

 

Tutorial D

… 本書では可能な限り SQL を使って例を示すが、このように何らかの理由でそれが不可能である場合には、 Tutorial D というきわめて直感的な (そして真のリレーショナル) 言語を使用する。 (同上, p19)

ということで、真のリレーショナルと言われる Tutorial D を試してみる。

第3のマニフェスト – Wikipedia によると、

第3のマニフェストは、デイトとダーウェンが考案したデータベース言語 Tutorial D を使って、関係モデルを説明している。 Tutorial D は、データベース言語仕様 D の実装の一つである。 D は、関係データベースデータベース言語が満たすべき要件の集合である。

http://dbappbuilder.sourceforge.net/Rel.php

Rel via kwout

実装として挙げられている中で、お手軽に試せるのが Java で実装された Rel

SourceForge.net より、ダウンロードしてインスール。

スタート > すべてのプログラム > Rel > DBrowser を起動。

sys.Catalog

を入力して Evaluate すると、データベースに定義されている変数の一覧が表示される。

 

Rel に慣れるために

以下ざっと目を通す。

簡単な計算を評価すると、

1 + 2

`3’ と結果が表示される。

 

タプル、関係の評価

タプルを評価すると、

tuple {name "Tarou",  age 10 }

結果は、

111-10-2010CropperCapture[1][4]

「関係」を評価すると、

relation {
  tuple {name "Tarou",  age 10 },
  tuple {name "Hanako", age 20 },
  tuple {name "Jirou",  age 30 }}

結果は、

311-10-2010CropperCapture[3]

Character, Integer と型を指定しなくても、属性の型が表示された。

 

式 と 文

上記の評価では、最後に `;’ を付けないことに注意。

Rel / Tutorial D Grammar を見ると、は、

evaluate ::= ( compound_statement_body ";" )? expression ( <EOT> | <EOF> )

これに対して、は末尾に `;’ を付ける。

statement := statement_body ";"

 

関係変数の定義と代入

変数の定義

SQL で最初にテーブルを作成するときの要領で、「関係」 を値に持つ関係変数 (relvar) を定義してみる。

例えば、以下の表にある 「人」 型の関係変数を定義。

var persons base
  relation { id     integer
           , name   character
           , gender integer
           , age    integer}
    key {id};

変数 persons は relation で指定した型となる。ここでは組み込みの型のみを使って定義した。

末尾に `;’ を付けるのを忘れずに。

 

値を変数に代入

次に、関係変数を定義したので、変数に値である 「関係」 を代入する。

persons :=
  relation{
    tuple { id 1, name "Tarou",   gender 1, age 10 },
    tuple { id 2, name "Hanako",  gender 2, age 20 },
    tuple { id 3, name "Jirou",   gender 1, age 30 },
    tuple { id 4, name "Saburou", gender 1, age 40 },
    tuple { id 5, name "Akemi",   gender 2, age 8  },
    tuple { id 6, name "Sadayo",  gender 2, age 70 },
    tuple { id 7, name "Hiroko",  gender 2, age 15 }};

先ほどの relation が型を返すのに対して、この relation は値を生成する。

関係変数の内容を表示させたい場合は、

persons

と入力して Evaluate する。 `;’ を末尾に付けない。

411-10-2010CropperCapture[4]

 

変数の削除

作成した関係変数を削除したい場合は、

drop var persons;

Tutorial D でリレーショナル代数」へつづく

 

関連記事

2010年11月6日土曜日

SQL の相関サブクエリ (5) – forall (∀) の exists (∃) への読み替え

SQL の相関サブクエリ (4) - SELECT 句で使う」 のつづき

今回も 前回と同じデータベース を使う。構造は以下の通り。

CropperCapture[3]

ここから、

すべての 「グループ」 に 「割当て」 られたことがある 「人」

を抽出したい。

データベースの内容をオブジェクト図で示すと、一見して目的の人がわかる。

CropperCapture[1]

Tarou だけが 3 つのグループすべてに 「割当て」 られた。 Jirou, Saburou は割当てがされてるものの 1 グループに対してのみ。

では、これを SQL で書くにはどうしたらいいのか?

これまでと同じように、特定の条件に一致する 「人」 を抽出したいので、

select *
from persons
where exists (select *
              from ...

と書き始めてみたものの、

「すべての x は p である」

という形式をどのように表現するのだろう?

exists 述語は、サブクエリで返される結果が一つでもあれば True となる。

「すべての…」

を表現してるのではなく、

「 p である x が少なくとも一つ存在する」

ということを表すために用いる述語なので、このように使用するのは不適切。

 

全称命題と存在命題

全称命題

ところで、「すべての x は p である」 という表現は、「論理記号」 によると、

「任意のx に対し
(=すべてのx について・どのようなxをとっても)
P(x)である」
(ただし、P(x)はxに関するある性質・条件を表す)
は、
論理記号「 ( ∀x ) ( P(x) ) 」で表される。 …

全称命題を表す論理記号「∀」を
全称記号、全称量化子、普遍量化universal quantifierなどと呼ぶ。

 

存在命題

これに対して、SQL の EXISTS 述語に対応している 「論理記号」は、

「 P (x)を満たすxが( 少なくとは一つは )存在する」
(ただし、P(x)はxに関するある性質・条件を表す)
は、
論理記号「 ( ∃x ) ( P(x) ) 」で表される。 …

存在命題を表す論理記号「∃」を
存在記号、存在量化子existential quantifierなどと呼ぶ。

SQL の相関サブクエリ (2) – EXISTS 述語」 で見たように、

「グループ」 に 「割当て」 られた 「人」

を抽出したい場合、以下のように記述した。

select * 
from persons as p
where exists (select *
              from assignments as a
              where p.id = a.p_id)

求めるものを言い換えると、

「グループ」 に 「割当て」 られたことがある 「人」

「グループ」 に 「割当て」 られたことが少なくとも一度ある 「人」

繰り返すが、EXISTS 述語は一行でも結果を返せば True となる。

 

日常的な言葉の表現における言い換え

「すべての x は p である」 という命題は別の表現で表すことができる。

全称命題 - Wikipedia によると、

全称命題は、存在命題の否定と論理的に等値である。それゆえ、「全ての牛は空を飛ぶ」という命題を主張することは、「少なくとも一頭は空を飛べない牛がいる」という命題を否定することと等値である。

例えば、日常的な言葉の使い方で考えるなら、

「すべてのプログラマは面倒なことが嫌い」

の意味は、

「面倒なことが嫌いでないプログラマはいない」

と言うのと同じ。

もう少し言い換え、上記の引用と同じ表現にするなら、

『少なくとも一人は面倒なことが嫌いでないプログラマがいる』 を否定すること

に等しい。

これを記号を用いて表すなら、全称記号 – Wikipedia によると、

「∀xPx」は存在記号否定記号とを用いて、「¬∃xPx」と表現することもできる。「¬∃xPx」は「P でないような x は存在しない」という意味だから、これはすなわち「すべての xPである」ということである。

 

「すべての…」 を SQL の EXISTS 述語で書く

4894714809プログラマのためのSQL 第2版 (pp.193-194) には、上記のような 「すべての…」 という表現を変換して SQL で記述する方法が述べられている。

「すべての人間は死ぬ」が、「死なない人間はいない」を包含するというのは誰もが賛成するでしょう …

… 「すべてのセールスマンは嘘つきである」 という述語を、 SQL の EXISTS 述語を使って書きたいかもしれません。これについては、今議論していた変換ルールを使います。

NOT EXISTS (SELECT *
            FROM Personnel AS P1    
            WHERE P1.job = 'Salesman'   
              AND P1.name NOT IN (SELECT L1.name     
                                  FROM Liars AS L1));

これを平易にいえば、「嘘つきでないセールスマンはいない」 ということです。

この SQL を順に読んで行くと、

  1. 冒頭の `NOT EXISTS’ は、以降で指定するものが「存在しない」 ということを意味し、
  2. 何が存在しないかと言えば、とある 「人」 なんだけれど、
  3. その人の仕事は 「セールスマン」 であり、
  4. かつ、名前が、「嘘つき」の名前には含まれてない人である

という流れで書かれている。

まとめると、

  1. 「すべてのセールスマンは嘘つきである」 を
  2. 「嘘つきでないセールスマンはいない」 に言い換え、
  3. SQL の定義で言えば、
    • いないですよ
    • とある「人」で
    • セールスマンであり
    • かつ、嘘つきでない人

により表現する。

 

forall (∀) と exist (∃) の変換

4873112753C.J.Date の 「データベース実践講義」 (pp.204-205) では、forall (∀) と exist (∃) による表現を相互に変換できることが述べられている。

以下の文は、

  EXISTS x ( p ( x ) )

論理的には以下の文に等しい (この場合、述語 p は x に加えてほかのパラメータを含んでいてもよい。

  NOT ( FORALL x ( NOT ( p ( x ) ) ) )

つまり、「p である x が少なくとも一つ存在する」 は、「『すべての x は p ではない』ということはない」に等しい。

同様に、以下の文は

  FORALL x ( p ( x ) )

論理的には以下の文に等しい (この場合も、述語 p は x に加えてほかのパラメータを含んでいてもよい。)

  NOT ( EXISTS x ( NOT ( p ( x ) ) ) )

これは プログラマのためのSQL 第2版 で書かれていたこと同じ。

繰り返しになるが、「すべての x は p である」 は、「『ある x は p ではない』ということはない」 に等しい。

この説明の上で、C.J.Date はEXIST しかサポートしていない SQL に対して、次のように述べている。

… EXISTS で表わすほうが「自然な」問題と、FORALL で表わすほうが「自然な」問題があるからだ。たとえば、SQL は EXISTS をサポートするが、FORALL をサポートしない。結果として、SQL で表現しようとすると非常にやっかいなクエリが存在する。

(同上より)

この後に示されている例を参考にして、最初の問題、

すべての 「グループ」 に 「割当て」 られたことがある 「人」

を SQL で表現する。

ただし、C.J.Date が以下のように強調していることを予め心得ておくこと。

単一否定でも十分に問題なのに(多くのユーザは理解するのに苦労する)、この SQL クエリのような二重否定はもってのほかである。

( 「データベース実践講義」 、p205 より)

 

二重否定の SQL

さて、Date 曰く 「もってのほか」 な SQL を書いてみる。

日本語で言い換える

抽出したいのは、

すべての 「グループ」 に 「割当て」 られたことがある 「人」

これを言い換えるには、FORALL x ( P(x) )NOT ( EXISTS x ( NOT P(x) ) にすることを考えればよかった。

ここで P に相当するのが、

「グループ」 に 「割当て」 られたことがある

だから、全体では、

『「グループ」 に 「割当て」 られたことが少なくとも一度もない人』の否定

つまり、

『「グループ」 に 「割当て」 られたことが少なくとも一度もない』ことがない 「人」

… と書いたところで、何だかよくわからない日本語。 (@_@; これあってるのかな?

 

そのままで考える

もとい。日本語で考えず、そのまま考えた方が良さげ。

FORALL x ( グループに割当てられたことがある (x) )

この意味は、「すべてのグループに割当てられたことがある x」。

単純に変換のための式に当てはめると、

NOT ( EXISTS x ( NOT ( グループに割当てられたことがある (x) ) )

NOT ( EXISTS x ( グループに割当てられたことがない (x) ) )

 

対象は 「人」 なので、 SQL 風に書くなら、

人 WHERE FORALL x ( グループに割当てられたことがある (x) )

変換した場合、

人 WHERE NOT ( EXISTS x ( NOT ( グループに割当てられたことがある (x) ) )

人 WHERE NOT ( EXISTS x ( グループに割当てられたことがない (x) )  )

 

SQL で書く

次に SQL で書き直す。いきなり書くのは難しいので徐々に、上記を見ながら、

select *
from persons as p
where not exists(グループに割当てられたことがない)

次に、「グループに割当てられたことがない」 の部分を書いて完成。

select *
from persons as p
where not exists (select *
                  from groups as g
                  where not exists (select *
                                    from assignments as a
                                    where a.g_id = g.id and
                                          a.p_id = p.id))

 

動作をイメージする

上記 SQL の動作を確かめるため、各々の人ごとに固定してサブクエリを考える。

Tarou が割当てられていないグループを取得するには、

select *
from groups as g
where not exists (select *
                  from assignments as a
                  where a.g_id = g.id and a.p_id = 1)

Tarou に割当てられていないグループはないので、抽出されるものはない。

111-06-2010CropperCapture[1]

Hanako の場合は、

select *
from groups as g
where not exists (select *
                  from assignments as a
                  where a.g_id = g.id and a.p_id = 2)

Take, Ume グループが抽出される。

211-06-2010CropperCapture[2]

Saburou はどのグループにも割当てられていないので、

select *
from groups as g
where not exists (select *
                  from assignments as a
                  where a.g_id = g.id and a.p_id = 4)

全てのグループが抽出される。

311-06-2010CropperCapture[3]

よって、割当てられていないグループがない Tarou だけが以下の SQL によって抽出される。

select *
from persons as p
where not exists(select *
		    from groups as g
		    where not exists (select *
                                   from assignments as a
                                   where a.g_id = g.id and a.p_id = p.id))

… とは言ったものの、パッと理解できないなぁ。。 (+_+)

 

FORALL 述語があれば…

もし、SQL に forall 述語があるなら、以下のように書けるのかな?

select *
from persons as p
where forall groups as g 
      exists (select * from assignments as a where a.g_id = g.id and a.p_id = p.id))

ちなみに Tutorial D には exists だけでなく forall もあるので、以下のように書けるらしい。 (未確認)

persons where forall groups exists assignments 
(assginments.g_id = groups.id and
    assignments.p_id = persons.id)

 

Haskell で書く

Haskell で類似したものを書いてみる。わかりずらい二重否定を使うなら、

[p | p <- persons
   , null [g | g <- groups
             , null [a | a <- assignments
                       , a_g_id a == g_id g
                       , a_p_id a == p_id p]]]

( cf. gist: 645292 – GitHub )

「すべてのグループに割当てられたことがある人」 を素直に書くなら、

[p | p <- persons
   , all (\g -> exists [a | a <- assignments
                          , a_g_id a == g_id g
                          , a_p_id a == p_id p]) 
         groups]

( cf. gist: 645292 – GitHub )