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 )

2010年11月2日火曜日

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

SQL の相関サブクエリ (3) - 量化された比較述語 ALL, ANY」 のつづき

これまでは WHERE 句において相関サブクエリを利用する例を見た。今回は SELECT 句で使う。

前回と同じデータベースを対象にして

例えば、

性別ごとに年齢の順位

を求めたい。

 

SELECT 句で相関サブクエリ

… とは言ったものの、いきなり書こうと思っても頭が混乱するのでゆっくりと。

まずは、「人」の属性に 「全体の人数」 を加えた結果を取得する。

select *, 
       (select count(*) 
        from persons) as num
from persons as p1

結果は、

+----+---------+--------+-----+------+
| id | name    | gender | age | num  |
+----+---------+--------+-----+------+
|  1 | Tarou   |      1 |  10 |    7 |
|  2 | Hanako  |      2 |  20 |    7 |
|  3 | Jirou   |      1 |  30 |    7 |
|  4 | Saburou |      1 |  40 |    7 |
|  5 | Akemi   |      2 |   8 |    7 |
|  6 | Sadayo  |      2 |  70 |    7 |
|  7 | Hiroko  |      2 |  15 |    7 |
+----+---------+--------+-----+------+
7 rows in set (0.00 sec)

次に、性別ごとに人数のトータルを求めるため、相関サブクエリを使う。

方法は、

  1. 各々の 「人」 ごとにその性別を同じくする集合を求め
  2. その要素数を得る。

頭に思い浮かべたのは下図。

111-02-2010CropperCapture[1]

select *, 
       (select count(*) 
        from persons as p2
        where p2.gender = p1.gender) as num
from persons as p1

結果は、

+----+---------+--------+-----+------+
| id | name    | gender | age | num  |
+----+---------+--------+-----+------+
|  1 | Tarou   |      1 |  10 |    3 |
|  2 | Hanako  |      2 |  20 |    4 |
|  3 | Jirou   |      1 |  30 |    3 |
|  4 | Saburou |      1 |  40 |    3 |
|  5 | Akemi   |      2 |   8 |    4 |
|  6 | Sadayo  |      2 |  70 |    4 |
|  7 | Hiroko  |      2 |  15 |    4 |
+----+---------+--------+-----+------+
7 rows in set (0.00 sec)

最後に、順位を求めるために各々の 「人」 要素において、

  • 自分の年齢よりも小さい人の人数をカウントして 1 を足す

ついでなので性別ごとに昇順に表示。

select *, 
       (select count(*) + 1
        from persons as p2
        where p2.gender = p1.gender and
              p2.age    < p1.age) as rank
from persons as p1
order by gender, age

結果は、

+----+---------+--------+-----+------+
| id | name    | gender | age | rank |
+----+---------+--------+-----+------+
|  1 | Tarou   |      1 |  10 |    1 |
|  3 | Jirou   |      1 |  30 |    2 |
|  4 | Saburou |      1 |  40 |    3 |
|  5 | Akemi   |      2 |   8 |    1 |
|  7 | Hiroko  |      2 |  15 |    2 |
|  2 | Hanako  |      2 |  20 |    3 |
|  6 | Sadayo  |      2 |  70 |    4 |
+----+---------+--------+-----+------+
7 rows in set (0.00 sec)

 

Haskell で書く

Haskell で類似したコード書いてみる。

順に考えるなら、最初は全体の人数を結果に追加。

[(p1, length [p2 | p2 <- persons]) | p1 <- persons]

次に、性別ごとに人数のトータルを追加。

[(p1, length [p2 | p2 <- persons
                 , gender p2 == gender p1]) | p1 <- persons]

最後に順位を求める。

[(p1, 1 + length [p2 | p2 <- persons      
                     , gender p2 == gender p1
                     , age p2 < age p1]) | p1 <- persons]

( cf. gist: 645292 – GitHub )

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

SQL の相関サブクエリ (3) - 量化された比較述語 ALL, ANY

SQL の相関サブクエリ (2) – EXISTS 述語」 のつづき

量化

SQL をはじめて触ったとき、exists 述語と並んで直感的に理解しずらかったのが ALL や ANY を伴った比較。

WHERE 句において単体の値を比較するのは問題ないけれど、 「すべての XXXX と比べてどうか?」 とか、「少なくとも 1 つの XXXX と比べてどうなのか?」 となるとイメージしずらくなる。

量化 – Wikipedia によると、

全ての人間の言語は、完全な数体系がない場合でも、量化を利用している(Wiese 2004)。例えば、日本語での例は次の通りである。

  • 全ての方針に目を通す必要がある」
  • 「川を渡っている人のうち何人かが白い腕章をしている」
  • 「私が話した人々のほとんどが、誰に投票するか決めていなかった」
  • 「待合室の誰もが小沢氏に対する少なくとも1つの不満を持っていた」
  • 「クラスの誰かが、私の出した全ての問題に答えられるはずだ」
  • 多くの人々は賢明である」

これらを量化を使わずに、複数の文の論理和論理積で表す単純な方法は存在しない。例えば、「Aの方針に目を通す必要がある」かつ「Bの方針に目を通す必要がある」……などと続くことになる。

日常会話の中でこれらの言葉を使うのに抵抗はない。しかし、SQL で書こうとすると難しく感じることがしばしば。特に相関サブクエリを利用する場合に。

 

ALL, ANY の仕様

ところで、SQL-92 における 「述語」 は "BNF Grammar for ISO/IEC 9075:1992 - Database Language SQL (SQL-92)" によると以下の 9 つが列挙されている。

<predicate>    ::=
<comparison predicate>
     | <between predicate>
     | <in predicate>
     | <like predicate>
     | <null predicate>
     | <quantified comparison predicate>
     | <exists predicate>
     | <match predicate>
     | <overlaps predicate>

上記の 「量化された比較述語」、

<quantified comparison predicate>    ::=   <row value constuctor> <comp op> <quantifier> <table subquery>

の一部として定義されているのが 「量化子」  ALL, SOME または ANY のキーワード。

<quantifier>    ::=   <all> | <some>

<all>    ::=   ALL

<some>    ::=   SOME | ANY

値を比較するための演算子 <comp op> とサブクエリ <table subquery> と共に用いられるのがわかる。

 

サブクエリの結果が固定されている場合

前回と同じデータベースを対象にして ALL, ANY の使用例を考える。

 

ALL 述語

例えば、

すべての男性よりも年齢の高い女性

を抽出したいとする。

すべての男性の年齢を取得するには、

select age
from persons
where gender = 1

上記で抽出された 「すべての年齢」 と比較するために使う述語が ALL 。 各々の 「人」 の年齢と 「すべての男性の年齢」 を比較する。

select *
from persons as p
where p.gender = 2 and
      p.age > all (select age
                   from persons
                   where gender = 1)

このサブクエリは、外側のクエリの値を参照していないので相関サブクエリではない。

上記の SQL を実行すると結果は、

+----+--------+--------+-----+
| id | name   | gender | age |
+----+--------+--------+-----+
|  6 | Sadayo |      2 |  70 |
+----+--------+--------+-----+
1 row in set (0.03 sec)

ただし、この場合 ALL 述語を使うよりも、素直に最大値と比較した方が効率は良いと思うけれど。

 

ANY 述語

次に、ALL と対照的な ANY 述語。

例として、

いずれかの男性よりも年齢の高い女性

を求める。

select *
from persons as p
where p.gender = 2 and
      p.age > any (select age
                   from persons
                   where gender = 1)

結果は、

+----+--------+--------+-----+
| id | name   | gender | age |
+----+--------+--------+-----+
|  2 | Hanako |      2 |  20 |
|  6 | Sadayo |      2 |  70 |
|  7 | Hiroko |      2 |  15 |
+----+--------+--------+-----+
3 rows in set (0.00 sec)

ALL, ANY いずれもサブクエリの値が固定されているので、割とイメージしやすい。

 

Haskell で all, any 述語を利用して書く

上記の SQL に類似したコードを Haskell で書くなら、

[p | p <- persons
   , gender p == 2
   , all (\p' -> age p > age p') 
         [p | p <- persons
            , gender p == 1]]

( cf. gist: 645292 - GitHub )

[p | p <- persons
   , gender p == 2
   , any (\p' -> age p > age p') 
         [p | p <- persons
            , gender p == 1]]

( cf. gist: 645292 – GitHub )

 

相関サブクエリにより要素ごとに結果が異なる場合

次は、要素によってサブクエリの結果が異なる場合を考える。

例えば、

同性の中で最高齢の 「人」

を求めたいとする。

もし、ALL 述語を使うなら、

  1. 抽出したいのは、とある 「人」なんだけれど、
  2. その 「人」 はすべての同性の年齢以上である

というように意識して書けばよい。

select *
from persons as p1
where p1.age >= all (select age
                     from persons as p2
                     where p2.gender = p1.gender)

ここで注目するのは、外側のクエリにおける persons テーブルを指し示す名前 p1 と、サブクエリで persons テーブルを指し示す名前 p2 。 同じテーブルに対して別名を付与している。

この意味は下図のように、p1 で指し示す 「人」 の要素に対して、p2 で指し示す同じく 「人」 の要素を組み合わせて評価するということ。

111-01-2010CropperCapture[1]

今回の場合は 「同性」 が条件なので、組み合わせの抽出条件として gender を用いている。

結果は、

+----+---------+--------+-----+
| id | name    | gender | age |
+----+---------+--------+-----+
|  4 | Saburou |      1 |  40 |
|  6 | Sadayo  |      2 |  70 |
+----+---------+--------+-----+
2 rows in set (0.09 sec)

 

Haskell で書くと…

毎度、Haskell で類似したコードを書くなら、

[p1 | p1 <- persons
    , all (\p -> age p1 >= age p)
          [p2 | p2 <- persons , gender p2 == gender p1]]

( cf. gist: 645292 – GitHub )

 

集約関数 max を使う場合

素直に最高齢を求めてから考えるなら、

select gender, max(age)
from persons as p
group by gender

により、性別ごとの最高年齢が得られるのを確認し、

+--------+----------+
| gender | max(age) |
+--------+----------+
|      1 |       40 |
|      2 |       70 |
+--------+----------+
2 rows in set (0.09 sec)

この結果と各々の 「人」 を比較して、gender と max(age) が一致する人を抽出すればいいので、

select *
from persons as p1
where exists (select *               
              from (select gender, max(age) as mage
                    from persons as p group by gender) as m where m.gender = p1.gender and m.mage   = p1.age)

しかし、全体としてはわかりずらいし、相関が上手く使えておらず冗長な感じ。 (+_+)

そこで、先ほど ALL で求めたときの真似をして次のように変更。

select *
from persons as p1
where age = (select max(p2.age)
             from persons as p2
             where p2.gender = p1.gender)

これでシンプルになった。

各々の要素に述語を適用するとき、比較対象をその要素の属性を用いて限定するということを意識すればよい。

 

Haskell で…

先ほどとほとんど変わらないけれど、Haskell で類似したコードを書くなら、

[p1 | p1 <- persons
    , age p1 == maximum [age p2 | p2 <- persons
                                , gender p2 == gender p1]]

( cf. gist: 645292 – GitHub )

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

参考サイト