2010年12月30日木曜日

Meadow で Scala - 空白を含むパスはトラブルの元

Notepad++ で Scala を書く場合、起動は軽いけれどコードの補完をしてくれない。 Eclipse では 補完はしてくれるけれど起動が遅く、メモリの消費が大きい。補完も今一な気が。。 (+_+) やっぱ Meadow を使うのがいいのかな?

上記を参考に Meadow で Scala を利用できるようにした。

 

Scala のインストールされているディレクトリへのパスが空白を含まないように変更

しかし、当初 scala-mode を使えるように設定した後、 REPL を起動しようと M-x scala-run-scala を実行したら、

'c:\Program' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

とエラーが表示された。

これは、scala を c:\Program Files\scala にインストールしていたのがダメだったようだ。 c:\scala に再インストールしたら scala-run-scala で起動できるようになった。

 

elisp のあるディレクトリへのパスが空白を含まないように変更

次に ENSIME を利用するための設定をした後、M-x ensime を実行。しかし、

Failed to connect to Swank: server process exited.

とエラーが表示される。

The Swank Protocol について調べても判然とせず。

The Emacs ENSIME client communicates with the server using the Swank protocol.

と説明があるので、server を起動していそうなバッチファイルを、ENSIME をインストールしたディレクトリにおいて適当な引数を与えて実行。(cf. scala - How to make ensime work in windows? - Stack Overflow)

bin/server.bat 12345

結果はコマンドラインに、

server listening on XXXXX

と表示される。これに対して telnet で

telnet 127.0.0.1 XXXXX

と試みると接続がされる。Meadow において、M-x ensime-connect をしても同様に接続可。

ところで、複数の PC で Meadow の設定を共有するために設定ファイル・elisp は Dropox へ置いている。エラーの原因はまたもこのパスだった。

Dropbox へのパスは以下の通り、空白が含まれていた。

C:\Users\ユーザ名\Documents\My Dropbox

これを Dropbox の Preferences > Advanced の ‘Dropbox location’ にて、空白を含まない場所へ変更。

C:\Users\ユーザ名\Documents\Dropbox

これにより、M-x ensime で ENSIME を実行すると、正常に動作してくれるようになった。

 

関連記事

2010年12月29日水曜日

Eclipse で Scala

Scala IDE のインストール

Scala IDE for Eclipse によると、必要なものは

Eclipse は Release Build: 3.5.2 よりダウンロード。

Scala を書くためのプラグインをインストールするには、Eclipse を起動し、メニューより

  • Help > Install New Software…

を選択。

Install ダイアログの work with: には、Scala IDE for Eclipse – Downloads にお勧めとして記載されている以下の URL を入力して、

http://download.scala-ide.org/nightly-update-master-2.8.1.final

必要なものをチェックしてインストールを進める。

CropperCapture[8]

 

Scala のコンパイルと実行

流れとしては、

  1. Perspective を Scala に変更
  2. Scala の Project を作成
  3. Scala Object を作成するときに、main メソッドを自動で作ってもらう。
  4. コードを書いて、コンパイルして実行

CropperCapture[9]

下図は Test オブジェクトに main メソッドを自動的に作成してもらっているところ。

CropperCapture[10][4]

REPL を利用するには、Cosole を選択し、Open Console ボタンで Scala Console を選ぶ。

CropperCapture[11]

 

関連記事

参考サイト

2010年12月28日火曜日

Python のパッケージを pip でインストール

1. EasyInstall を使い、Python Package Index よりパッケージをインストール

PyPI とは、

The Python Package Index is a repository of software for the Python programming language.

Python のパッケージが管理されているリポジトリ。

 

easy_install

CheeseShopTutorial - PythonInfo Wiki によると、お手軽に Python のパッケージをインストールするには、EasyIntall を使うとのこと。

EasyInstall (easy_install) gives you a quick and painless way to install packages remotely by connecting to the Package Index or even other websites via HTTP.

EasyInstall を利用するには setuptools 0.6c11 を予め入れておく。

OS と Python のバージョンに合わせ、Using Setuptools and EasyInstall にある

をダウンロードしてインストール。

Windows Notes によると、

If typing easy_install at the command prompt doesn't work, check to make sure your PATH includes the appropriate C:\\Python2X\\Scripts directory.

コマンドラインから easy_install を実行するために、

  • C:\Python25\Scripts

を環境変数の PATH に追加した。

 

2. pip でパッケージをインストール

ところで、easy_install 以外にもパッケージをインストールするツールは存在する。

pip 0. 8.2 は、

pip is a replacement for easy_install. It uses mostly the same techniques for finding packages, so packages that were made easy_installable should be pip-installable as well.

上記には easy_install と比べて以下の利点が挙げられている。

  • All packages are downloaded before installation. Partially-completed installation doesn't occur as a result.
  • Care is taken to present useful output on the console.
  • The reasons for actions are kept track of. For instance, if a package is being installed, pip keeps track of why that package was required.
  • Error messages should be useful.
  • The code is relatively concise and cohesive, making it easier to use programmatically.
  • Packages don't have to be installed as egg archives, they can be installed flat (while keeping the egg metadata).
  • Native support for other version control systems (Git, Mercurial and Bazaar)
  • Uninstallation of packages.
  • Simple to define fixed sets of requirements and reliably reproduce a set of packages.

例えば、上記「パッケージをアンインストールすることができる」に関して easy_install は、

easy_install never actually deletes packages (unless you're installing a package with the same name and version number as an existing package), so if you want to get rid of older versions of a package, please see Uninstalling Packages, below.

(Upgrading a Package より)

 

pip のインストール

まずは、easy_install を利用して pip をインストール。

easy_install pip

 

pip でパッケージのインストールとアンインストール

pip でパッケージをインストールするには、

pip install パッケージ名

アンインストールするには、

pip uninstall パッケージ名

2010年12月23日木曜日

Notepad++ の NppExecプラグインで Scala のコンパイルと実行

1. ちょっとコードを試したいときは REPL

Scala で、ちょっとコードを試したいときは、コマンドラインから

scala

と入力し REPL を起動。

特別なコマンドを入力しなくても、複数行の入力に対応しているので便利。

CropperCapture[7]

 

2. エディタを使うなら Notepad++

少し長いコードを試したいときは、Notepad++ EUC-JP 対応版 を利用する。このエディタは起動が早いので、さっと書きたいときに重宝している。

上記のリンクより Notepad++ をダウンロードし、解凍したものを管理者権限が必要のないフォルダへ配置。C:\Program Files (x86) へ入れなかったのは、後述するプラグインが動作しなかったため。

 

キーワードの色付け

Scala のコードを色付けしたい場合は、

Dependent on your installation configuration, it's either in \Documents and Settings{your profile name}\Application Data\Notepad++\userDefineLang.xml or Notepad++ installation folder\userDefineLang.xml

(Notepad++ custom syntax highlighting setting - where to look for it? - Super User より)

CropperCapture[9]2Scala をインストールしたフォルダ内にある

  • scala\misc\scala-tool-support\notepad-plus\userDefineLang.xml

を Notepad.exe のあるフォルダに置いて Notepad++ を再起動。

これによりメニューの「言語」に `Scala’ が追加される。

(%APPDATA% の方に置いたら表示されなかった。)

 

「実行...」ダイアログ で コンパイル と実行

CropperCapture[8]Notepad++ でコードを実行するには、メニューより「実行 > ファイル名を指定して実行」を選択。(ショートカットキーでは F5)

「実行...」ダイアログに以下のように入力して実行ボタンを押してコンパイル。

scalac "$(FULL_CURRENT_PATH)"

( Notepad++ で用意されている特別な引数については、Help > Commands を参照。)

コンパイルされたクラスファイルを実行するには、もう一度「実行...」ダイアログを表示させ、

scala 起動クラス名 && pause

を実行して結果を確認する。

 

3. NppExec プラグインでコンパイルと実行

上記の方法ではコンパイルと実行をそれぞれ行う必要があるので面倒。そこで、NppExec プラグインを利用して複数のコマンドを実行できるようにする。

 

インストール
  1. メニューより「プラグイン > Plugin Manager > Show Plugin Manager」を選択。
  2. NppExec を選択してインストール。

Notepad++ を再起動すると、メニューの「プラグイン」に `NppExec’ が表示される。

 

コンパイルと実行を一度に行わせる方法

Scala のコードをコンパイルしてエラーがなかった場合に、続けて実行するためのバッチファイルを用意。 NppExec プラグインより呼び出すようにする。

Notepad++.exe と同じ階層に以下のバッチファイル scalaCompileAndRun.bat  を作成。

call fsc "%1"
if ERRORLEVEL 1 exit
call scala "%2"

Notepad++ のメニューより「プラグイン > NppExec > Execute…」を選択。(ショートカットキーは F6)

ダイアログに以下を入力。

INPUTBOX "起動するクラスを指定してください" : $(SYS.MAINCLASS)
ENV_SET  MAINCLASS = $(INPUT)
NPP_SAVE
cmd /c "$(NPP_DIRECTORY)\scalaCompileAndRun.bat"  $(FULL_CURRENT_PATH)  $(SYS.MAINCLASS)

Save… ボタンを押して適当な名前で保存する。

上記を利用するには、

  1. 実行したいコードを入力
  2. F6 キーを押し NppExec のExecute… ダイアログを表示
  3. 先ほど保存したスクリプトをセレクトボックスで選択してOK ボタンを押す

起動するクラスを指定するダイアログが表示されるので入力して OK ボタンを押すと、コンパイルして実行される。

5283697369_e77c92178d_z

一度実行したら、Ctrl + F6 により再実行できる。

 

NppExec のドキュメント

ところで、NppExec のドキュメントは以下の場所にある。

  • npp.5.8.5-6.1.bin\unicode\plugins\doc\NppExec

参考にしたヘルプは、

  • Help > 3.7 NppExec's script
  • Help > 4.2 Environment
上記スクリプトに対応したメニューとショートカットを作成したい場合は以下を参考に。

 

関連記事

参考サイト

NppExec プラグイン

バッチ処理

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 で外部キーの定義 - 整合性制約で記述」につづく

Firefox の Adblock Plus でホワイトリストの設定

1. ホワイトリストの追加

追記(2013/06/08)Adblock Plus を有効にしていると利用できないサイトがある。その場合、Adblock Plus のホワイトリストにサイトを追加する必要がある。

特定のサイトをホワイトリストに追加するには、当該サイトをブラウザで開いておき、

  • Adblock Plus のアイコンをクリック > XXXXXX で無効

を選択する。 XXXXXX として、「ドメイン、ページ、すべてのサイト」を選択できる。

 

全ページで AdBlock を無効にしてから、ホワイトリストに追加する

追記(2013/06/25):上記の方法によりホワイトリストに追加できない場合、

  1. 全ページで AdBlock を無効にする。
  2. ホワイトリストに追加したいサイトを開き、当該サイトのみ Adblock を無効にする。
  3. 全ページで AdBlock を有効にする

AdBlock を有効にした状態で「楽天トラベル」を開いたら、何も表示されなくなった。このとき、上記の方法で「楽天トラベル」で Adblock が無効になるようにした。

 

2. ホワイトリストの編集

手動でホワイトリストを編集するには、Adblock Plus の設定より、「フィルタ設定」を開く。

  1. 「自作フィルタ」タブを開く
  2. デフォルトでは「ホワイトリスト」という名前のフィルタグループが存在するので、「アクション」を押す
  3. 「フィルタを表示/非表示」を選択すると、右側に「フィルタ」のリストが表示される。
  4. 「フィルタを追加」ボタンを押す。

SnapCrab_No-0260

ホワイトリストに追加したい URL の先頭に

@@

を付けてフィールドに入力。

例えば、Google AdSense をホワイトリストに登録したい場合は、

@@https://www.google.com/adsense/

とする。

 

3. 古いバージョンの Adblock

Adblock Plus の古いバージョンでは、設定ウィンドウを開き、「追加」ボタンを押す。

211-12-2010CropperCapture[2]

 

参考サイト

キーボードの入力を改善する方法

Majestouch 黒軸を軽く押して入力する目安」 のつづき

キーボードの置き方を変えてみる

ノートPC の上にキーボードを載せて使ったとき、なぜかキータッチがいつもより軽く感じた。

DSC03010

キーボードの後ろの爪は立てず、ペタンとそのまま置く形。手は少し浮いた状態で、たまごを掴むときのような形。

雑誌を下に敷く

試しに、薄めの雑誌をキーボードの下に敷き使ってみたら、ノートPCの上に置いたときと同じような感覚。 Filco の Majestouch 黒軸はこの打鍵位置が良いみたい。

imgkeyborad

追記(2012/05/05): 打鍵に慣れてくると、背面のツメを立て、使えるようになる。手を動かす範囲が狭く、最小限の力で打てるようになるには、相当の期間が必要。

 

関連記事

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月8日月曜日

Haskell の日付型

Ruby, Python, Java の日付型

Java で日付型の値を扱う場合、何だかゴテゴテとしたコードを書く必要がある。これに対し、Ruby や Python の記述はシンプルでスッキリとしている。

例えば Ruby で `2010年4月1日’に対応した日付オブジェクトを使いたい場合、

require 'date'
puts Date.new(2010, 4, 1)

Python では、

import datetime
print datetime.date(2010, 4, 1)

ちなみに Java だと、

import java.util.Calendar;
import java.text.SimpleDateFormat;

public class TestDate {
    public static void main(String[] args){
	Calendar cal = Calendar.getInstance();
	cal.set(2010, Calendar.APRIL, 1);
	System.out.println
	    (new SimpleDateFormat
	     ("yyyy-MM-dd").format(cal.getTime()));
    }
}

うーん、ややこしい。 (+_+)

Java の日付時刻に関することは以下も参考に。

 

Data.Time

Haskell でも同じく Date 型がないかと探してみると、Data.Time の階層に日付を扱うためのモジュールがある。

の 4 つのモジュールに分かれており、上 3 つ はその名前通りの用途に用いることができそう。最後の LocalTiem モジュールの中には、日付を含まない時間を表す型が定義されていた。

(cf. 時刻を扱う - 特定の日付に属さない時間 )

 

日付から Day 型への変換

Data.Time.Calendar には日付に対応した Day 型が定義されている。

コンストラクタは、

ModifiedJulianDay {toModifiedJulianDay :: Integer} 

この型の説明には、

The Modified Julian Day is a standard count of days, with zero being the day 1858-11-17.

とある。 Modified Julian Day とは 「 ユリウス日(Julian Day)」 によると、

ユリウス日(ユリウス通日)とは紀元前4713年1月1日からの連続した通し番号の日数です。

ユリウス通日 – Wikipedia

数年にわたる2点の日数を計算するのに便利で、天文学年代学などで使われている。ユリウス通日では桁が多すぎるため、ユリウス通日から2400000.5を引いた修正ユリウス日(MJD)も広く使われている。

内部的にはこの数値が Day 型に使われているようだ。

 

fromGregorian 関数

日付からこの Day 型に変換するには、

fromGregorian :: Integer -> Int -> Int –> Day

この関数を使ってみる。

import Data.Time.Calendar
main = print $ fromGregorian 2010 4 1

 

toModifiedJulianDay 関数

fromGregorian 関数によって Day 型に変換された値は、セレクタ toModifiedJulianDay により修正ユリウス日を得られる。

フリーゲルの公式 によると、

例えば、2004年1月1日はy=2003、m=13、d=1なので

\lfloor 365.25 \times 2003 \rfloor + \lfloor 2003 / 400 \rfloor -  \lfloor 2003 / 100 \rfloor + \lfloor 30.59 ( 13 - 2 ) \rfloor + 1 - 678912
= 731595 + 5 − 20 + 336 + 1 − 678912 = 53005

となり、53005が修正ユリウス日となる。

確かめてみる。

Prelude> :m Data.Time.Calendar
Prelude Data.Time.Calendar> toModifiedJulianDay $ fromGregorian 2004 1 1
53005

 

グレゴリオ暦

それにしても Haskell では、Ruby や Python のようにシンプルに Date というような名前のコンストラクタではなく、fromGregorian というゴツイ関数名が付けられているのだろう?

Gregorian とは 「グレゴリオ暦」 のことを指し、

現在、日常使われているグレゴリオ暦は、1582年に制定された事実上の世界標準の暦です。 このグレゴリオ暦の前身はユリウス暦で、そのユリウス暦は、古代ローマ暦にエジプト 発祥の太陽暦を導入したものです。

日本では、1872年からグレゴリオ暦が採用されています。

普段意識せずに使っているのがグレゴリオ暦で、その前に使われていたのがユリウス暦だと。

なぜこの暦が使われるようになったかと言えば、グレゴリオ暦 - Wikipedia によると、

それまで用いられていたユリウス暦では、通常の年(平年)は1年を365日とし、4年に1回を閏年として366日とし、平均年を365.25日としていた。…

しかし太陽年は約365.2422日であるため、ユリウス暦の方式では1000年で約8日の誤差が生じる。これにより、比較的頻繁に補正することが必要であった。…

これに対して、新たに定められたグレゴリオ暦では、平年は1年を365日とし、4年に1回を閏年とするところまではユリウス暦と変わらないものの、さらに調整を加えて平均年を365.2425日とした。この調整とは「西暦紀元(西暦)の年数が100で割り切れてかつ400では割り切れない年は閏年としない[2]」というルールを加えることである。これはすなわち、ユリウス暦の方式では閏年とされる年であっても400年間に3回は閏年とせずに平年に戻すということである[3]

プログラムの入門書でよく見かける 「うるう年を判定するプログラム」 の判定の理屈は、このグレゴリオ暦に基いている。

ちなみに、2000年問題 – Wikipedia においても、

直接の原因は、プログラム内でを扱う際の年数の表現方法である。年数の表現をグレゴリオ暦の下二桁のみで行っている場合、2000年が内部で00年となり、これを1900年とみなしてしまい、例えば「データベースを日付順に並び替える処理をすると、順序が狂う」などの誤作動につながる可能性があるとされた。

また、現行の太陽暦であるグレゴリオ暦では

  1. 年が4で割り切れる年は閏年とする
  2. (1)のうち、年が100で割り切れる年は閏年としない
  3. (2)のうち、年が400で割り切れる年はこれを適用しない(つまり閏年とする)

というルールがあり、このため2000年は閏年だったが、誤って1と2のみを適用し、閏年としなかったプログラムが存在したため、この対応も併せて必要とされた。

 

他の言語における定義

Date - Rubyリファレンスマニュアル では、

new([year[, mon[, mday[, start]]]])

暦日付に相当する日付オブジェクトを生成します。

… 最後の引数は、グレゴリオ暦をつかい始めた日をあらわすユリウス日です。グレゴリオ暦の指定として真、ユリウス暦の指定として偽を与えることもできます。省略した場合は、Date::ITALY (1582年10月15日) になります。

Python の 6.10.3 date オブジェクト には、

日付は理想的なカレンダー、すなわち現在のグレゴリオ暦を過去と未来の両方向に無限に延長したもので表されます。 … この暦法は、全ての計算における基本カレンダーである、 … "予期的グレゴリオ (proleptic Gregorian)" 暦の定義に一致します。

Java の Calendar (Java Platform SE 6) における getInstance() メソッドにより返されるカレンダーオブジェクトを文字列表現にすると、

java.util.GregorianCalendar …

このクラスは GregorianCalendar (Java Platform SE 6) によると、

GregorianCalendar は、Calendar の具象サブクラスであり、世界のほとんどの地域で使用される標準的なカレンダシステムを提供します。 

GregorianCalendar は、グレゴリオ暦とユリウス暦をサポートするハイブリッドカレンダシステムで、単一の変わり目を処理します。

 

Data.Time の使用例

まずは Data.Time 関連のモジュールを読み込む。

Prelude> :m Data.Time
Prelude Data.Time>

Data/Time.hs を見てわかる通り、このモジュールで下位のモジュールがエクスポートされている。

 

日付の加算・減算

例えば、「2010年4月1日の 100 日後の日付」 を求めたい場合は、CalendaraddDays 関数を使い、

Prelude Data.Time> addDays 100 $ fromGregorian 2010 4 1
2010-07-10

「大晦日まで後何日か?」 求めたいなら diffDays 関数。

Prelude Data.Time> fromGregorian 2010 12 31 `diffDays` fromGregorian 2010 4 1
274

 

現在の日付・時刻は?

Ruby で現在の時刻を求める場合、

require 'date'
puts DateTime.now

同じように Haskell でも ClockgetCurrentTime 関数を使うと現在の時刻が表示される。

Prelude Data.Time> getCurrentTime
2010-11-08 03:09:48.769875 UTC

ただし、UTCTime 型の値が返される。この型の値は 協定世界時 – Wikipedia

協定世界時(きょうていせかいじ、UTC - Universal Time, Coordinated)とはセシウム原子時計が刻む国際原子時(TAI)をもとに、天文学的に決められる世界時(UT1)との差が1秒未満となるよう国際協定により人工的に維持されている世界共通の標準時である。…

世界各地の標準時はこれを基準として決めている。例えば、日本標準時は(JST)で協定世界時より9時間進んでおり、「+0900(JST)」のように表示する。

日本標準時を取得するには LocalTimegetZonedTime 関数を利用する。

Prelude Data.Time> getZonedTime
2010-11-08 12:10:21.06675 JST

 

LocalTime と ZonedTiem の関係

LocalTime 

A simple day and time aggregate, where the day is of the specified parameter, and the time is a TimeOfDay. Conversion of this (as local civil time) to UTC depends on the time zone.

LocalTime は日付と時刻を持つ型。これに TimeZone を加えると、世界標準の UTCTime になる。

タイムゾーンとは、

共通の標準時を使う地域全体を「等時帯」、「時間帯」または「タイムゾーン(time zone)」といい、その地域の標準時を示す際にはUTCとの差で示すことが多い。

(標準時 - Wikipedia)

例えば、日本は UTC + 9 で、この設定をする関数が hoursToTimeZone

localTimeToUTC 関数に TimeZone LocalTime 「2010年4月1日 6時0分0秒」 を与えて UTCTime を得る。

Prelude Data.Time> let tz = hoursToTimeZone 9
Prelude Data.Time> let lt = LocalTime (fromGregorian 2010 4 1) (TimeOfDay 6 0 0)
Prelude Data.Time> localTimeToUTC tz lt
2010-03-31 21:00:00 UTC

日本は UTC よりも 9 時間早いので、前日の 21 時となる。

 

現在からの日数を求める

先ほどと同様に、「現在の日付から 100 日後」 と、「現在から大晦日まで何日あるか」 を調べたい。

予め ZonedTime から日付と時刻に分解する補助的な関数を定義。

dayAndTime :: ZonedTime -> (Day, TimeOfDay)
dayAndTime zt = let lt = zonedTimeToLocalTime zt
                    day  = localDay lt
                    time = localTimeOfDay lt
                in (day, time)

day  = fst . dayAndTime
time = snd . dayAndTime

これを用いて、

main = do
-- 現在の時刻を取得 now <- getZonedTime -- 現在の日付から 100 日後は? print $ addDays 100 (day now) -- 現在から大晦日までは何日? print $ fromGregorian 2010 12 31 `diffDays` (day now)

 

後何分?

「現在の時刻から、特定の時刻まで後どれくらの時間があるか」 を調べたい。

時刻 t1 から t2 までの長さを求める diffTime を定義。

diffTime :: TimeOfDay -> TimeOfDay -> TimeOfDay
diffTime t1 t2 = timeToTimeOfDay 
                 (timeOfDayToTime t2 - timeOfDayToTime t1)

これを使い 「今から 22 時までの時間」 を求める例。

print $ diffTime (time now) (TimeOfDay 22 0 0) 

 

フォーマット

日付 `2010-04-01’ の表示を変更して `2010—04—01’ のような書式にしたい。

予め System.Locale をインポートしておき、defaultTimeLocale 関数を使えるようにしておく。

import System.Locale

formatTime 関数を使い、

print $ formatTime defaultTimeLocale "%Y--%m--%d" 
          $ fromGregorian 2010 4 1

 

全体のコード

関連記事

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 )