8.SQL演習(3/3)

先回のSQLZOOの課題、サブクエリについて:

サブクエリを使用したSQLで WHERE a > ANY (サブクエリ) や WHERE a > ALL (サブクエリ) のケースについて解説し忘れたので補足。

4-7 (ちょこっと予習) テーブル名とフィールド名について、テーブル名を指定したフィールドの指定が可能。テーブル名の指定は、表の結合で利用するが、今回は、サブクエリーと外のクエリ―の表の区別に、テーブル名の名前の付け替えを利用している。

各大陸のもっとも大きな国(面積で)の大陸、国名、面積を表示する。

※自己相関クエリー というテクニックを利用して解きます。

SELECT continent, name, population FROM world x
  WHERE population >= ALL
    (SELECT population FROM world y
        WHERE y.continent=x.continent
          AND population>0)

↑ こちらのSQLのサブクエリ(カッコ内のSQL)は、Where の条件式で、外側(メイン)のSQLのテーブルのフィールドを参照しているので、括弧計算の様に、

先にSQLの実行結果(値)を求めることができません。

解説記事参照: http://qiita.com/HirofumiYashima/items/38ed1ecb3d9136b8d619

SQL演習応用:SQLによるテーブルの結合、サブクエリ―、ビューについて扱う。

キーワード: テーブルの結合、ビュー

参考資料:

■Webclass

Webclassにある講義用資料、ITパスポート・基本情報技術者試験・データベース入門には、SQLによる表の結合に関する解説は有りません。

以下に、こちらのページでテーブルの結合について解説します。

■旧講義資料:

表の結合とサブクエリー (表の結合の部分)

正規化と結合 (復習)

質問: 表の結合はなぜ必要なのか?

SQLを利用した表の結合の必要性を確認する為に、表の分割と正規化について復習しておくこと。

確認ポイント:

・テーブルに、レコードの追加・修正・削除などの操作を行ったときに、必要なデータを削除したり、不要なデータを削除し残したりしない様に工夫したい

・テーブルを正規化する。(正規化のルールを適用。第1正規形、第2正規形、第3正規形など。

・テーブルを正規化すると、テーブルは多くの場合、複数のテーブルに分割される

・分割されたテーブルは、主キーと外部キーの値によって元のテーブルに組み立てなおすことが出来る

■RDBMSでは、正規化されたテーブルから必要に応じてテーブルを組み立て直して利用する。

■テーブルの結合(組み立て直し)は、 SQL で指示できる。

■SQLの SELECT 文で作成したテーブルは、特に指示しない限り一時的に作成されて使い捨てにされるテーブルである。このテーブルをビューと呼ぶ。

正規化の手順と利点の例)

所属部活名簿を作成し、複数の学生の複数のクラブへの所属を記録可能なようにテーブルを正規化する。

正規化する前の、所属部活名簿

第1正規形:

このテーブルの場合、 ある学生が、同じクラブに2回入部することは無いので、

つまり、学籍番号とクラブ名の組み合わせで、同じデータが2度記録されることは無いので、

(学籍番号, クラブ名) が 連結キー主キー となる。

第2正規形:

上記のテーブルで、主キーに完全に依存する項目は、主キー自身の学籍番号とクラブ名であり、

氏名や学年は、主キーに完全に従属せず、その一部、学籍番号だけに従属する。

そこで、氏名と、学年を別のテーブルに分割する。

ついでに、クラブ名も繰返し現れると想定できるので、クラブ名にIDを割り振って別の表に分割する。

すると、以下の様になる。

ID

a

b

クラブ名

○○同好会

△△部

正規化前のテーブルの問題点の確認:

・レコードの修正に関する問題: 学籍番号101の氏名、学年を修正した場合、どんなミスが起こり得るか?

・レコードの削除に関する問題: 学籍番号101と102が△△部を退部した場合、何が起こり得るか?

・レコードの追加に関する問題: 新設の部活を登録するにはどうすればよいか?

・クラブ名の修正に関する問題: ○○同好会が○○部に名称変更した場合、どんなミスが起こり得るか?

(応用)

上記のテーブルを、さらに第3正規形に修正可能か検討する。修正可能の場合の条件について考える。

■ テーブルの結合とSQL

データベースで 正規化する前の上の例の 部活名簿 のような、表示や印刷の為の表を利用するには、正規化された表をSQLで結合することで必要に応じて作成する。

つまり、画面や紙に出力するための表は、出力するその時だけに毎回SQLで作成しなおすことになる。

こうすれば、ある表の一部のレコードが別途、修正されていても、最新のデータを用いて結合結果を表示することが出来る。

■ 結合の種類

・ 交差結合

2つの表の直積(2つのテーブルの各レコードの全組み合わせ)を求める。

SELECT * FROM A, B;

※キーワード FROM の後ろに、複数のテーブル名を書くだけでよい。

■ テーブル名の指定と、テーブルに別名をつける方法(SQLの文法)

・複数の表から、同じフィールド名の列を区別して指定する場合:

表Aと表Bのどちらにも、同じ名前のフィールド”NAME”がある場合、これを区別してSQLで利用するには、

テーブル名 A

NAME

小橋

長谷川

CODE

1

2

テーブル名 B

SELECT A.NAME, B.NAME FROM A, B;

と、表名.フィールド名 のように表名とフィールド名の間にピリオドを用いる書式を利用する。

以下の場合、

SELECT NAME, NAME FROM A, B;

だと、AとBどちらの表の NAMEを表示するのかあいまいであるのでエラー。

前述のSQLの実行結果は、

交差結合になる。テーブルの演算でいうと、直積 が求められている。

・表の名前の付け替え:

フィールドの名前をASで付け替えたのと同様にテーブル名をASで変更可能

SELECT AAAAAA.NAME,AAAAAA.CODE,BBBBBB.NAME,BBBBBB.CODE, FROM AAAAAA, BBBBBB;

SELECT A.NAME,A.CODE,B.NAME,B.CODE, FROM AAAAAA AS A, BBBBBB AS B;

※テーブル名が繰返しSQLに登場する場合、名前を付け替えて略記すると読みやすいSQLになる。

ASの省略

SELECT A.NAME,A.CODE,B.NAME,B.CODE, FROM AAAAAA A, BBBBBB B;

テーブル名の後ろに、空白を開けて、 省略名を指定できる。

・ASとフィールド名

フィールド名の付け替えに、ASを利用できる。テーブルの出力結果の見出しを 新たに付けたり、selectの出力で計算式を用いた時、計算結果に AS で名前を与えることが出来る。

・ 等結合

2つの表を条件を指定して結合する。

条件には、

「表Aと表Bの、フィールドCが同じ値のもの」

等と指定する。

正規化済みで分割された表は、外部キーを結合の条件に利用することになる。

SELECT * FROM A,B

WHERE A.CODE = B.CODE;

もしくは、

SELECT * FROM A

INNER JOIN B ON A.CODE = B.CODE;

等結合(内部結合)の例)

SELECT * FROM A

INNER JOIN B ON A.CODE = B.CODE;

・INNER JOIN の省略系

SELECT * FROM A JOIN B ON A.CODE = B.CODE;

INNER を省略できる。

・ 外部結合

表の結合で結合条件がA.CODE = B.CODEのとき、表Aまたは表BのどちらかのフィールドCODEに対応する値が存在しない場合は、結合する側のフィールドの値は空欄(NULL値)として結合する。

左外部結合

SELECT * FROM A

LEFT JOIN B ON A.CODE = B.CODE;

テーブルAに、テーブルBを結合する。

右外部結合

SELECT * FROM A

RIGHT JOIN B ON A.CODE = B.CODE;

テーブルB、テーブルAを結合する。

左外部結合の例)

SELECT * FROM A

LEFT JOIN B ON A.CODE = B.CODE;

※ 左外部結合の場合、 左側(上の例ではA)のレコードは全て結合結果に表示されます。Bのレコードで ON の結合条件に合わないものは、 NULL の値として結合されます。

※ 右の場合も同様(Bの表のレコードが全て表示される)

■演習0: 練習問題です。SQLをそのまま実行して、結果を確認してください。

SQLZOO の練習問題 Tutorial Section から 6.JOIN のテーブルを利用した復習問題。

・参加チームのデータを一覧表示

select * from eteam

・↑国名のみ表示

select teamname from eteam

・何か国参加しているか?

select count(*) from eteam

・この大会での総ゴール数を求める

select count(*) from goal

・チームごとの総ゴール数を求める

select count(*) from goal group by teamid

・チームごとの総ゴール数を求める。表を結合して チーム名 得点数 を表示する。

SELECT e.teamname,count(*) FROM goal g join eteam e on g.teamid=e.id group by teamid

・得点記録がある人を名前の重複を除去して表示

select distinct player from goal

・得点記録がある人の人数を数える

select count(a.player) from (select distinct player from goal) AS a

※sqlzooでは サブクエリーでselectで作成した表に名前を付ける必要がある。

※上記の例では、(select distinct player from goal) の表示結果を a という名前とした。

・得点王(最多得点のプレイヤー)を探す

select player,count(*) pg from goal group by player order by pg desc

■演習1:

SQLZOO の練習問題 Tutorial Section から

6.JOIN

7.More JOIN

に取り組む。

7.More JOIN の問題、 7-12 ~ 7-16 は より難しい問題 となる。選択課題とし、挑戦したいもののみ取り組むこと(回答が有った場合、追加点として考慮する)

問題の回答は、メモ帳などに 問題番号( 6-1 の様な形式)と、SQL文をコピーして

記録しておく。

回答を記録したテキストファイルを、レポートフォルダに 3112999.txt のファイル名で提出。

問題文 と エラーメッセージ の訳文:

■問題文
6.JOIN
UEFA欧州選手権 UEFA EURO 2012 の試合データを使った演習問題
(サッカーの大会、ポーランドとウクライナで開催)
game ゲームテーブル 試合の日時、参加チームを記録
id 試合のid
mdate 試合の日
stadium スタジアム
team1 チーム1
team2 チーム2
goal ゴールテーブル ゴールの記録
matchid 試合id
teamid チームid
player プレイヤー
gtime ゴール時間
eteam 参加チームテーブル
id チームのid(国)
teamname チーム名前
coach コーチ
6-1
最初の例として Bender のゴール記録が示されている。
ドイツの matchid と player名 を表示
(ヒント) where の条件は teamid = 'GER'
(ヒント)
SELECT ????????????? FROM goal 
  WHERE teamid = 'GER'
6-2
上記のクエリ―では、Lars Benderのゴールは ゲーム 1012 で確認できる。
goalテーブルで試合番号は matchid だが、gameテーブルでは id となっていることに注意する。
game 1012 の id, stadium, team1, team2 を表示する。  
(ヒント)
SELECT id,stadium,team1,team2
  FROM game 
 WHERE ????????????????
6-3
JOIN を利用して、2ステップで単独のクエリ―を組み合わせる事が出来る。
gameとgoalの詳細を得るには、
(SQLの例文)
問題: player, teamid ,stadium, mdate を全てのドイツのゴールについて表示する。
表の結合の条件 と データの検索の条件 の2つのタイプの条件が必要。
 and で記述しても on と where に分けて記述してもよい。
(ヒント1)
SELECT ????????????????
  FROM game JOIN goal ON (id=matchid and ??????)
(ヒント2)
SELECT ????????????????
  FROM game JOIN goal ON (id=matchid)
  where ??????
6-4
上の問題と同様に JOIN を利用して、
Marioという名前の選手のゴールについて、team1, team2 , player を表示する。
(ヒント)
3の問題が解けたらコピーして条件の部分を修正
6-5
eteam には各参加国のコーチが記載されている。
JOIN で goal を eteam に結合する。
goal JOIN eteam on teamid=id を条件に使用する。
最初の10分間でゴールしたという条件で、 player, teamid, coach, gtime を表示。
6-6
team1のコーチが 'Fernando Santos' となる試合日とチーム名を表示。
6-7
 'National Stadium, Warsaw' スタジアムで開催された試合でゴールした選手を表示する。
6-8
ドイツと対戦して、ゴールした選手の名前を全て表示する。
(ヒント)
例として、ドイツ―ギリシャ戦の全試合を表示するクエリーが入力してある。
6-9
チーム名とゴール数の合計を表示する。
(ヒント)
select の項目で count(*)を使用し、 GROUP BY teamname を利用する。
6-10
スタジアムの名前と、そのスタジアムでのゴール数を各スタジアムごとに表示する。
6-11
ポーランド(POL)が参戦している全試合の matchid と 日程 とその試合のゴール数 を表示する。
6-12
ドイツが得点した試合の matchid と 日程 と ドイツの得点 を表示する。
6-13
この問題は、ここまでまだ未解説のSQL構文「CASE WHEN」 を使用する。
下に示す様に、各試合ごとに各チームの得点を表示する。
mdate team1 score1 team2 score2 日程 チーム名1 得点1 チーム名2 得点2
注意) 全得点状況が、記録されている。もし、チーム名が goal に記録されていれば、
その時点でチームが1得点していることにななり、チーム名が記載されていなければ、得点は0点である。
チーム名有り → 1
チーム名なし → 0
この、得点状況を 1と0 に CASE WHEN で変換した結果をSUM で集計すれば、そのチームの得点を集計できる。
結果は、日程順で並べ替えて出力する(日程が同じなら、idの順番)。
※難しいので長文で解説
例文のSQL:
SELECT mdate,  team1,
  CASE WHEN teamid=team1 THEN 1 ELSE 0 END score1
  FROM game JOIN goal ON matchid = id
SQLの解釈)
SELECT mdate,  game.team1, goal.teamid
  FROM game JOIN goal ON goal.matchid = game.id
で、game と goal が結合できる。
この結合方法では、team1 が参加した試合について、自国の得点と、対戦国の得点の両方が出力される。
そこで、出力結果が、自国の場合は 1点、 他国の場合は 0点に 出力を変換して表示する。
その為に、
以下の様に selectの表示項目で、CASE WHEN を利用する。
    CASE WHEN teamid=team1 THEN 1 ELSE 0 END score1
上記の条件部分で、  goal.teamid が  game.team1 に一致するかを判定して、  THEN 1 で一致の場合は ELSE 0 で不一致の場合は 0 に変換している。
ENDの後ろの score1 で、出力結果の見出しに score1 という名前を付けている。(AS score1 の省略形)
(ヒント)
自国の得点状況を集計するには、
SELECT mdate,
  team1,
  sum(
  CASE WHEN teamid=team1 THEN 1 ELSE 0 END
  )  as score1
 FROM game
 LEFT JOIN goal ON goal.matchid = game.id
 GROUP BY game.id,mdate,team1,team2
これに、team2 の国名と 得点の集計結果を同様に付け加える。
JOINではなく、 LEFT JOIN を使用する必要が有る。試合で両国とも無得点 0-0 の場合は、 goalには、記録がない。
eteamにチーム名が記載されていても、goalにゴールの記録がないチームは、JOIN でテーブルを結合すると、結合結果には残らない(inner join 内部結合のため)。
そのため、LEFT JOINで結合し、得点の欄は空欄(NULL)となるように結合する。
sumで集計した際に、得点の無い試合の集計結果は 0点になる。
集計結果は、最終的に日程順に並べ替える。 order by を使う。
7.More JOIN eperations
映画のデータベース
このチュートリアルで join の理解を深める。データベースは、3つのテーブルで構成される。
movie(映画) と actor(役者) と casting(出演) である。
テーブルの詳細は、以下である。

※ SQL の表記で id は movie.id と actor.id の2種類あるので注意。

※ movie.id = actor.id のようには使われない(映画のid と 役者のid なので 参照キーと外部キーの関係にはならない)

※ movie.id = casting.movieid や actor.id = casting.actorid の関係がテーブル間にある。

映画データベースの詳細は以下のリンクから表示。

More details about the database.

さぁ、とりかかろう。

柔軟体操(準備練習)

7-1. 1962 年の映画のリストを表示( id と title を表示)

(ヒント) 実行するだけ。文法の確認。

7-2. 「市民ケーン」の上映年は?

(ヒント) 1. を修正。 文字列の表記は ' と'で囲む必要に注意。

7-3. スタートレック('Star Trek')というシリーズ映画のリストを表示( id title yr )。 年の順に掲載。

Star Trek という語句を含む映画を where の条件に指定。

7-4. 女優 'Glenn Close' の id ナンバーは何ですか?(select で表示して調べる)

7-5. 映画 'Casablanca' カサブランカの id は何ですか?

過去問:

7-x. 11768, 11955, 21191 の id の映画の タイトル(title) を表示。

(ヒント) where 条件で in を利用

Get to the point(要点) のリンク先に、テーブルの結合のSQLの例が掲載されているので確認。

↑に2つのテーブルの結合例(casting と actor) と 3つの全テーブルの結合例が載っているので、回答に利用するとよい。

7-6. 映画カサブランカの出演者リスト(name)を出力する。

(ヒント) 上の問題で、カサブランカの id が分かっているので、単純なクエリーで回答してもよい。 casting と actor を結合 where で カサブランカの id を指定。

(別解1) カサブランカの id を調べるサブクエリーを利用する。

(別解2)全テーブルを結合してから、条件指定で検索結果を絞り込む。

7-7. 映画「エイリアン」 の出演者リストを表示。

(ヒント) 7-6の別解を修正して回答。

7-8. 'Harrison Ford' ハリソン=フォードが出演した映画のリストを表示する。

(ヒント) 全テーブルを結合し、条件指定。

7-9. 'Harrison Ford' ハリソン=フォードが出演した映画で、彼が主演していない(ord <> 1) のリストを表示。

ord は、映画の出演リスト順、1 が主演を意味する。

7-10. 1962年の全映画を、そのタイトルと主演と併記してリスト表示。

より難しい問題。 ここより先、オプションの選択課題とする。未回答でも 減点はなし 。 回答者には 加点 する。

7-11. 'John Travolta' ジョン=トラボルタが最も忙しかった年はいつですか? その年と出演した映画の本数を表示する。

彼が2本より多く出演した各年について表示する。

(ヒント) 実行するだけで正解が出てくる。SQLを解釈して、なぜそのような結果になるか考えること。

サブクエリの部分について解説。

(SELECT MAX(c) FROM
(SELECT yr,COUNT(title) AS c FROM
   movie JOIN casting ON movie.id=movieid
         JOIN actor   ON actorid=actor.id
 WHERE name='John Travolta'
 GROUP BY yr) AS t
)

上記クエリの解釈。

全テーブルを内部結合。

WHERE name='John Travolta' ジョン=トラボルタのレコードだけを抽出

group by yr で年ごとにグループ化し、

COUNT(title) AS c で、出演したタイトルの本数を数え、項目名を c とする。

外側の select 文で、 c の欄の最大値を求めている。

※ 末尾の AS t はサブクエリーの実行結果は複数値を含みテーブルとして利用するので、仮の名前 t を指定している。名前が必要となるのは mysqlの仕様のため。

上記クエリをサブクエリとして、 7-12 の HAVING の条件に使用する。

7-12. 'Julie Andrews' ジュリー=アンドリューズが出演した映画について、主演した役者の名前を調べ、その全てについて タイトル と 主演 を表示する。

(ヒント1)

入力済みのSQLは、1箇所エラーがある。存在しないフィールド名が指定されているので、まず修正して実行してみる。

(ヒント2)上記のクエリーで、'Julie Andrews' が出演した映画のidのリストを得ることができるので、これをサブクエリーとして、さらに外側の

select文(全テーブルを結合)を追加して組み合わせて回答する。

7-13. 少なくとも30タイトル以上に主演した役者の名前をアルファベット順に掲載。

(ヒント) actor.id でグループ化

7-14. 1978年の映画を、出演者数が多い順に、タイトルを表示。

(ヒント) movie.id でグループ化

7-15. 'Art Garfunkel' アート=ガーファンクルと一緒に仕事をした人々をすべて表示。

(ヒント) 一緒に出演、もしくは、彼が監督した映画に出演した という条件で検索する。

(ヒント)監督の名前とid も、actor テーブルに記録されている。

■エラーメッセージなど
You have an error in your SQL syntax 文法エラー
Too many rows 行が多すぎる(条件の指定ミス)
Too few rows 行が少なすぎる(条件の指定ミス)
Too many columnss 列が多すぎる(表示項目の指定ミス)
Too few columnss 列が少なすぎる(表示項目の指定ミス)
Unknown column 'xxxxxxxxx' in 'field list'  (xxxxxxxxx がフィールド名に無い。スペルミスか、テーブルの指定ミス)
Show correct result 正しい答えを表示する(表示内容が間違っていた場合表示される。クリックで正解の出力結果を表示)

■演習2:

6.の問題で、

select count(*) from goal group by player

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

結果の数値から、何人かのプレイヤーについて、この大会での得点記録が複数回記録されていることが分る。

この記録方法で、果たして効率的だろうか?

game , goal , eteam の3つのテーブルだけでは、テーブルの正規化が不足しているといえる。

適切に正規化を進める場合、どのテーブルを修正し、どの様なテーブルを追加するべきか考え、示せ。

回答に利用する、テーブル名やフィールド名は適当に決めて良い。

回答を演習1の回答と同じテキストファイルに記載して提出。