長門有希のSQL

涼宮ハルヒの憂鬱

久しぶりに読んでました。

1巻P191で長門さんがSQL文を読んでますが、

改めて見てみるとポカーンな内容。

いや、私はSQLの使用経験自体は10回くらいしか無いのであんまり偉そうな事は言えないんですが。



当時は「おお、SQLだ」くらいにしか思ってませんでした。



そのSQL文は以下の通り。

見易くする為に勝手に改行を入れています。



SELECT シリアルコード

FROM データベース

WHERE コードデータ

ORDER BY 攻性情報戦闘

HAVING ターミネートモード




そもそもSQLというのは2次元表に格納されたデータに対して検索・更新・追加等を行う為の言語です。



SELECT句は表内のどの列を抽出するかを表しています。

FROM句はどの表を参照するかを表しています。



よって1、2行目の

SELECT シリアルコード

FROM データベース


は、"データベース"表の"シリアルコード"列を抽出する、という意味です。





SELECT 社員番号, 社員名

FROM 社員


というSQL文を実行すると。



社員番号	社員名

0001 key

0002 夜桜 鍵

: :

: :




という抽出結果が出力されます。

これを使えば社員番号"0001"の人の名前が"key"ということがすぐに分かりますね。







次のWHERE句なんですが、これがよく分からないです。

WHEREは"選択"を意味、

つまり特定の行を検索するために使います。





SELECT 社員番号, 社員名

FROM 社員

WHERE 性別 = '男'


を実行すると男性のみの社員番号と社員名を出力します。

表を作るのはめんdなのでしません。



長門さんの言った内容は、

WHERE コードデータ


です。



SQLってこの文法は成立するのでしょうか?

試してみたらすぐに分かるのでしょうけど、

SQLの起動方法を忘れて、調べるのがめんdなのでやりません。

因みに私が持っている参考書にはこのような記述は載っていません。



C言語とかのプログラミング言語なら成立します。

例えば、

if( a )

と記述すれば、「aが真のとき」という判定をします。

因みに私はこの記述が嫌いです。



if( a == true )

if( a != false )

if( a >= 1 )

等などの表現方法がありますが意味は全て同じです。



成立するのなら、

"コードデータ"が真のとき

という意味になります。

つまり、"コードデータ"列が空欄、0またはfalseの否定を示す語句ではない行、ということになります。

"コードデータ"がどんなデータを格納しているのか分かりませんのでこれ以上は何とも言えません。







ORDER BY 攻性情報戦闘


これは簡単です。

"攻性情報戦闘"列をベースに並べ替えるという意味です。

並べ替え方を記述していないので昇順に並べ替えます。







HAVING ターミネートモード


何でここでHAVING句……。

HAVING句はORDER BY句よりも先行していなければなりません。

よってこのSQL文自体が誤りです。



HAVING句の前にGROUP BY句の説明をします。

GROUP BY句はGROUPの通り、グループ化です。



GROUP BY句で列名を指定すると、

同じデータを格納した行を全て纏めて1つのデータとします。





SQLの文法には存在しない語句がありますが便宜上使用可能としてください。



国語、数学、理科、社会、英語のそれぞれの試験の結果を纏めるとき

SELECT 科目名, 履修人数, 最高点, 最低点, 平均点

FROM 履修

GROUP BY 科目名


を実行すると、



科目名	履修人数	最高点	最低点	平均点

国語 1 1 1 1

数学 2 2 2 2

理科 3 3 3 3

社会 4 4 4 4

英語 5 5 5 5


となります。

数字は適当ね。



HAVING句はグループの選択です。

ここで条件を指定すると、それに当てはまるグループのみが抽出されます。



HAVING 履修人数 >= 3


とするのなら、理科、社会、英語のみが抽出される事になります。



なお、GROUP BY句を省略してHAVING句を指定した場合、

表全体を1つのグループとみなします。



HAVING句もWHERE句同様に値のみを指定する記述方法が成立するのかは分かりません。

多分無理だと思いますが。







長門さんの言ったSQL文が間違っているのはHAVING句で言いましたが、

無理にでも解釈すると、

"データベース"表の"コードデータ"列が空欄で無く、否定を示さない語が格納されている行を抽出し、

その抽出した行の集合を1つのグループとし、

"ターミネートモード"列が空欄で無く、否定を示さない語が格納されているグループを、

"攻性情報戦闘"列に格納されているデータを昇順に整列して、

"シリアルコード"列を出力。

ということですかね。



意味不明です。

何を検索してるんですかね?







ここからは推測。

ORDER BY 攻性情報戦闘




GROUP BY 攻性情報戦闘


の間違い?

だとするなら、

HAVING 攻性情報戦闘 = 'ターミネートモード'


とすることが出来、HAVING句が通るようになります。

また、HAVING句の前にORDER BY句があるという文法上の誤りも解消します。

更に、"シリアルコード"を抽出するだけなのに何故か整列するという首を傾げる処理も無くなります。



ただし、そうするなら

SELECT シリアルコード


がおかしくなります。



基本的に、SELECT句で指定した列とGROUP BY句で指定した列は一致していなければなりません。

"攻性情報戦闘"で括ったのに"シリアルコード"列を抽出するというのでは意味が通りません。



この

攻性情報戦闘 = 'ターミネートモード'


という記述が成立するのであれば、

これをGROUP BY句で指定してやる必要は無く、

普通にWHERE句で指定すれば良い事になります。



SELECT シリアルコード

FROM データベース

WHERE コードデータ = 'hogehoge'

AND 攻性情報戦闘 = 'ターミネートモード'




コードデータが何を示しているのか分かりませんので"hogehoge"としておきます。







さて、ここまで書いたところで"シリアルコード"が主キーなのか疑いたくなってきました。

今までの内容は全て"シリアルコード"が主キーであるという前提で書いています。

実はこれがそうでないとなると話が変わってきます。



主キーというのは、この列(または列の集合)が特定できればその行を一意に識別できるというものです。

上の方で挙げた「"社員番号"が"0001"の人の"社員名"は"key"である」を例にします。

名前が"key"という人は何人もいるかもしれませんが、

"社員番号"が"0001"の人は絶対に一人しか存在してはいけません。

つまり、"社員番号"が特定できれば、その社員の情報が特定出来る事になりますので、

"社員番号"が主キーという事になります。



これは無かった事にして次に進みましょう。

"シリアルコード"は主キー主キー。







これで"攻性情報戦闘"が"ターミネートモード"である"シリアルキー"の抽出が出来ました。

次は"パーソナルネーム"との照合になります。



2007年4月1日改訂

これは簡単で、"パーソナルネーム"が"朝倉涼子"と一致するものの"シリアルコード"を抽出すれば良いです。

但し、同姓同名がいる場合、複数の"シリアルコード"が抽出されることに注意。



同姓同名がいないかどうかを調べます。

SELECT パーソナルネーム

FROM データベース

GROUP BY パーソナルネーム

HAVING COUNT(*) = 1


これは同姓同名がいなければ、"パーソナルネーム"を抽出するというSQL文です。

同姓同名が1人でもいれば抽出されません。



最後に1つのSQL文に纏めます。

SELECT シリアルコード

FROM データベース

WHERE コードデータ = 'hogehoge'

AND 攻性情報戦闘 = 'ターミネートモード'

AND '朝倉涼子' IN ( SELECT パーソナルネーム

FROM データベース

GROUP BY パーソナルネーム

HAVING COUNT(*) = 1 )


これで"コードデータ"が"hogehoge"で、

"攻性情報戦闘" = "ターミネートモード"で、

"パーソナルネーム"が他の誰とも重複していない"朝倉涼子"の"シリアルコード"

を抽出する事が出来ます。



1行も抽出できなければ上記の条件のいずれかが合致していないということです。

"コードデータ"が何か分かれば他のやり方が出てくるのでしょうけど、

与えられた条件で"朝倉涼子"を判別するにはこんなものでしょうか。

これだけの情報で同姓同名の"朝倉涼子"を判別する方法を思い付かないので。



もう少し詳しい事が分かれば、

SELECT シリアルコード

FROM データベース

WHERE コードデータ = 'hogehoge'

AND 攻性情報戦闘 = 'ターミネートモード'

AND パーソナルネーム = '朝倉涼子'


だけで特定できるかもしれません。



"朝倉涼子"が敵性なのかどうかを知りたいのであれば、

SELECT句に"パーソナルネーム"と"攻性情報戦闘"を加えた方が良いでしょう。


改訂ここまで







長門さんの台詞を改めて見てみると、

"シリアルコード"を特定することで"パーソナルネーム"を識別していますね。

なので"シリアルコード"の特定に"パーソナルネーム"を使うのはいささか違和感が。



ということで、"シリアルコード"の特定を副問い合わせにした方法でアプローチしてみましょう。







その前に表の構成についての考察を。



朝倉涼子が敵性かどうかを判定した条件は"攻性情報戦闘"だと思われます。

"シリアルコード"ごとに持っていなければならない"攻性情報戦闘"を"データベース"表が持っているということは、

"データベース"表は個々のヒューマノイドインターフェースを管理できるものだと思われます。



理由は"シリアルコード"と"パーソナルネーム"を持った表と、

"シリアルコード"と"攻性情報戦闘"を持った表を分離するメリットはあまり無さそうだからです。

よって、"データベース"表に"パーソナルネーム"が含まれているものと思われます。

ただ、これに関しては他の条件で話は変わってきます。



例えば一つの"シリアルコード"に対して"攻性情報戦闘"が2つ以上の値を持つのならば、

これは分離しなければなりません。

長門さんが"監視役"、朝倉涼子が"バックアップ"等の役職に就いていたり、

"急進派"等の派閥があることから、

派閥ごと役職ごとに"攻性情報戦闘"の値の定義が違う可能性は大いにあります。

(朝倉涼子は監視役の長門さんのバックアップなので監視役としての視点で見れば敵性とされるかもしれませんが、

朝倉涼子自体は急進派に属しているので、

この視点から見た場合は敵性にはならない可能性があります。)



原文を推測したものでは「"攻性情報戦闘"が"ターミネートモード"であるかどうか」としか問い合わせていないので、

一つの"シリアルコード"が複数の"攻性情報戦闘"を持っているかどうかまでは分かりませんが、

複数の値を持っていても"敵性"と判定できる要素があれば排除の対象とするということで、

ここでは"パーソナルネーム"と"攻性情報戦闘"は別の表にあるとして話をしていきます。

そして、別の表とは"シリアルコード"ごとの"攻性情報戦闘"を管理する"SK"という表とします。



せっかくなのでついでに派閥、役職も定義しておきますかね。

他にも影響を与える属性はあるでしょうが、(多分)原作中に出ていないので考察しようがありません。



派閥ごと役職ごとのターミネートモードと判定する表を"攻性情報戦闘定義"とします。

そこに含まれる属性は"派閥"、"役職"、"ターミネートモード判定値"です。

また、主キーは { 派閥, 役職 } となります。







まず、最終的に欲しいのは"パーソナルネーム"なので、

SELECT パーソナルネーム

FROM データベース


となります。



ここのWHERE句の指定はちょっと面倒ですね。

"SK"表の"攻性情報戦闘"に"ターミネートモード"が含まれている行を抽出するので、

WHERE EXISTS (	SELECT *

FROM SK

WHERE 攻性情報戦闘 = 'ターミネートモード'

AND SK.シリアルコード = データベース.シリアルコード )


ですかね。

これで"データベース"表の"シリアルコード"ごとに"SK"表の"攻性情報戦闘"を検査し、

一つでも"ターミネートモード"があれば真を返します。



これで、"攻性情報戦闘"が"ターミネートモード"の"シリアルコード"を全て抽出出来ます。



派閥ごと役職ごとの"攻性情報戦闘"の定義を盛り込むと、

WHERE EXISTS (	SELECT *

FROM SK

WHERE SK.シリアルコード = データベース.シリアルコード

AND 攻性情報戦闘 >= ( SELECT ターミネートモード判定値

FROM 攻性情報戦闘定義

WHERE 派閥 = '長門さんが属している派閥'

AND 役職 = '監視役' ) )


こんな感じでしょうか?



今までの攻性情報戦闘の右辺の"ターミネートモード"は文字列として扱っていましたが、

ここでの"ターミネートモード"は定数です。

"ターミネートモード"の値は"攻性情報戦闘定義"表から持ってきます。

つまり"攻性情報戦闘"が命令違反等をした場合、

その行為の重大性を基に数値化して累計していき、

"ターミネートモード"と定義した一定値を超えると"敵性"と判定します。







一つに纏めます。

SELECT パーソナルネーム

FROM データベース

WHERE EXISTS ( SELECT *

FROM SK

WHERE SK.シリアルコード = データベース.シリアルコード

AND 攻性情報戦闘 >= ( SELECT ターミネートモード判定値

FROM 攻性情報戦闘定義

WHERE 派閥 = '長門さんが属している派閥'

AND 役職 = '監視役' ) )


これで抽出結果の"シリアルコード"から"パーソナルネーム"を調べるリストが出来ました。

現段階でこのSQL文を実行すると、

パーソナルネーム

……

朝倉涼子

……


となります。



まだ複数人が抽出される可能性のある条件です。



わざわざSQL文を使って朝倉涼子の状態を調べているところから、

長門さんは朝倉涼子の"シリアルコード"を知らないことが分かります。

ですのでこれ以上絞り込むことは出来ません。







さて、"コードデータ"はどうしましょうか。

何を指定しているのか分からないのでどうしようもありません。

このまま記述しても良いでしょうか。



データ構造によっては朝倉涼子長門さんのバックアップであることを直接定義することは出来るので、

"コードデータ"が無くても良さそうな感じ。

("データベース"表に"親シリアルコード"なるものを付与して朝倉涼子のデータに長門さんの"シリアルコード"を指定すれば、

朝倉涼子長門さんの下についているという構造を定義できます。)



自分のシリアルコードが分かれば朝倉涼子を特定出来ますね。

もし自分の"シリアルコード"が分からなくても調べるのは簡単です。

自分のデータですから。







それも加味してみましょう。

SELECT パーソナルネーム

FROM ( SELECT *

FROM データベース

WHERE 親シリアルコード = '長門さんのシリアルコード' ) AS DB

WHERE EXISTS ( SELECT *

FROM SK

WHERE SK.シリアルコード = DB.シリアルコード

AND 攻性情報戦闘 >= ( SELECT ターミネートモード判定値

FROM 攻性情報戦闘定義

WHERE 派閥 = '長門さんが属している派閥'

AND 役職 = '監視役' ) )

AND コードデータ = 'hogehoge'


"パーソナルネーム"を参照する表を"データベース"から"DB"という導出表にしました。

"データベース"表に登録されている全てのヒューマノイドインターフェースから、

"親シリアルコード"に"長門さんのシリアルコード"が格納されている行のみを予め選択します。

よって抽出範囲が長門さんの下についている者だけに限定するので検索する人数が大きく減りました。



なので、これでかなりの高確率で目の前にいる"朝倉涼子"を特定できると思います。

あとは"コードデータ"の内容に期待。





まぁ万全を期すなら、

SELECT パーソナルネーム

FROM ( SELECT *

FROM データベース

WHERE 親シリアルコード = '長門さんのシリアルコード' ) AS DB

WHERE EXISTS ( SELECT *

FROM SK

WHERE SK.シリアルコード = DB.シリアルコード

AND 攻性情報戦闘 >= ( SELECT ターミネートモード判定値

FROM 攻性情報戦闘定義

WHERE 派閥 = '長門さんが属している派閥'

AND 役職 = '監視役' ) )

AND コードデータ = 'hogehoge'

GROUP BY パーソナルネーム

HAVING COUNT(*) = 1


こうですね。



"パーソナルネーム"でグループ化して、

同一の"パーソナルネーム"の数を数えて、

1なら(同姓同名がいないなら)出力です。







今更言うのもなんですが、

書いているうちに自分でもよく分からなくなってましたので適当に受け流してください。







長門さんの台詞の意味が分からない。

意味を知りたい。

長門さんをきっかけにしてSQLの勉強をしよう。

という方は参考になりましたでしょうか?(何







今頃何言ってんの? という突っ込みは無しで。

データベースの勉強してるとこういうのについ反応してしまうんで。



因みに散々偉そうに言ってきましたが、

SQL自体にそんなに強くないので間違っていたら申し訳ないです。