MENU

PostgreSQL 正規化 結合 副問合せ

目次

正規化

  • データを適切な単位で分割し複数の冗長なデータを持たなくする
  • 複数のテーブルを更新する必要がなくなり、データの整合性を担保できる
  • 検索時に複数のテーブルを関連付ける必要がある

正規形

  • 参考URL:https://zenn.dev/keisuke90/articles/66ecb7956a6816
  • 第1正規形:一つのセルの中には一つの値しか含まない
  • 第2正規形:部分従属を排除して完全関数従属のみのテーブルを作る
    • 何とかコードとか、何とかIDが一つのテーブルに2つ以上ある場合、社員コードと部署コードが存在し社員名や、部署名もあるような場合が部分関数従属が存在すると言えます。
  • 第3正規形:推移的関数従属を排除すること
    • 例えば、部署に属する担当があるとすれば推移的関数従属が存在することになり、それを別のテーブルに分割することです
  • ボイス・コッド正規形:非キーからキーへの関数従属性をなくした状態
  • 第4正規形:多値従属性が複数存在するテーブルを分割する
  • 第5正規形:第4正規形の発展形

内部結合(INNER JOIN ON句)

  • 一般的な結合
  • 複数のテーブルから一致する要素がある行のみを結合して表示します。

2種類の書き方

INNER JOIN型

FROM 
 table1 AS t1
INNER JOIN
 table2 AS t2
ON
 ti.id = t2.id

WHERE型

FROM
 table1 AS t1,
 table2 AS t2,
WHERE
 ti.id=t2.id

社員テーブル(employee)と所属部署テーブル(depart)テーブルを内部結合し、在職している(retired<>1)社員の、氏(l_name)、名(f_name)、役職(class)を所属部署名(depart_name)を取得せよ。その際、所属部署コード(depart_id)、社員コード(s_id)の昇順で出力せよ。

sql
SELECT
 l_name,
 f_name,
 class,
 depart_name
FROM
 employee
;
result
テーブルの確認
employee
s_idl_namef_namel_name_kanaf_name_kanasexclassdepart_idb_idlast_updateretired
AI00001相沢アイザワサトシ1部長S022012/02/240
DA00001大門一郎ダイモンイチロウ1担当J01AI000012011/08/100
FU00001藤井雄太フジイユウタ1主任E01SE000012011/04/100
FU00002藤岡幸太郎フジオカコウタロウ1担当E01FU000012011/06/120
HA00001速水和幸ハヤミカズユキ1部長E012007/02/031
HA00002葉山俊輔ハヤマシュンスケ1アシスタントE01FU000022012/12/110
KA00001川口裕子カワグチユウコ2アシスタントS01KI000012012/12/281
KA00002加藤昭雄カトウアキオ1アシスタントS01KI000012011/11/110
KA00003神田佐知子カンダサチコ1アシスタントJ01KA000032012/01/280
KI00001木村一郎キムライチロウ1担当S01TA000012011/07/120
NA00001中澤康代ナカザワヤスヨ2アシスタントS02TO000022012/04/171
NO00001野上利江ノガミリエ2アシスタントS02TO000022012/06/300
SA00001佐藤智子サトウトモコ2部長S012010/02/010
SE00001妹尾春樹セノオハルキ1課長E01TE000012010/12/100
SI00001清水春子シミズハルコ2主任J01SI000012011/12/110
TA00001田中真由子タナカマユコ2主任S01YA000012011/05/260
TA00002田辺正一タナベショウイチ1アシスタントE01HA000022012/12/190
TE00001寺岡陽一テラオカヨウイチ1部長E01HA000012009/12/110
TI00001近田晃子チカダアキコ2課長S02AI000012012/03/010
TO00001遠山真一トオヤマシンイチ1課長J01UE000012010/12/160
TO00002戸川よしみトガワヨシミ2担当S02TU000012012/04/010
TU00001津村知美ツムラカズミ2主任S02TI000012012/03/160
UE00001上田一也ウエダカズヤ1部長J012011/11/120
YA00001山田奈美ヤマダナミ2課長S01SA000012012/10/150
NI00001西ニシユウイチ1アシスタントB012013/01/100
depart
depart_iddepart_name
S01経営企画部
E01第一営業部
E02第二営業部
J01人事部
S02総務部
スキーマの確認
depart
No.論理名物理名データ型Not Null
1所属部署IDdepart_idcharacter(3)Yes (PK)
2所属部署名depart_namecharacter varying(20)
employee
No.論理名物理名データ型Not Null
1社員IDs_idcharacter(7)Yes (PK)
2l_namecharacter varying(20)
3f_namecharacter varying(20)
4氏(カナ)l_name_kanacharacter varying(100)
5名(カナ)f_name_kanacharacter varying(100)
6性別sexsmallint
7役職classcharacter varying(20)
8所属部署IDdepart_idcharacter(3)
9上司IDb_idcharacter(7)
10最終更新日last_updatedate
11退職フラグretiredsmallint
ER

前の問題に別名をつけたり、WHEREでも書いてみる

INNER JOIN
sql
SELECT
  e.l_name,
  e.f_name,
  e.class
FROM
  employee AS e
;
result
l_namef_nameclass
相沢部長
大門一郎担当
藤井雄太主任
藤岡幸太郎担当
速水和幸部長
葉山俊輔アシスタント
川口裕子アシスタント
加藤昭雄アシスタント
神田佐知子アシスタント
木村一郎担当
中澤康代アシスタント
野上利江アシスタント
佐藤智子部長
妹尾春樹課長
清水春子主任
田中真由子主任
田辺正一アシスタント
寺岡陽一部長
近田晃子課長
遠山真一課長
戸川よしみ担当
津村知美主任
上田一也部長
山田奈美課長
西雄一アシスタント
新渡戸康治アシスタント
WHERE
sql
SELECT
  e.l_name,
  e.f_name,
  d.depart_name,
  e.class
FROM
  employee AS e,
  depart AS d
;
result
l_namef_namedepart_nameclass
相沢経営企画部部長
相沢第一営業部部長
相沢第二営業部部長
相沢人事部部長
相沢総務部部長
大門一郎経営企画部担当
大門一郎第一営業部担当
大門一郎第二営業部担当
大門一郎人事部担当
大門一郎総務部担当
藤井雄太経営企画部主任
藤井雄太第一営業部主任
藤井雄太第二営業部主任
藤井雄太人事部主任
藤井雄太総務部主任
藤岡幸太郎経営企画部担当
藤岡幸太郎第一営業部担当
藤岡幸太郎第二営業部担当
藤岡幸太郎人事部担当
藤岡幸太郎総務部担当

社員テーブル(employee)とタイムカードテーブル(time_card)から2012年12月の氏名(l_name,f_name)と勤務時間(work_time)を、社員コード(s_id)、日付(r_date)の昇順で取り出すSQL文を完成させろ。

最後に幽霊社員を外部結合で確認します。

sql
SELECT
  e.l_name,
  e.f_name
FROM
  employee AS e
;
result
l_namef_name
相沢
大門一郎
藤井雄太
藤岡幸太郎
速水和幸
葉山俊輔
川口裕子
加藤昭雄
神田佐知子
木村一郎
中澤康代
野上利江
佐藤智子
妹尾春樹
清水春子
田中真由子
田辺正一
寺岡陽一
近田晃子
遠山真一
戸川よしみ
津村知美
上山田美也
山田奈美
西雄一
新渡戸康治
(26 行)
テーブル確認用
employee
s_idl_namef_namel_name_kanaf_name_kanasexclassdepart_idb_idlast_updateretired
AI00001相沢アイザワサトシ1部長S022012/02/240
DA00001大門一郎ダイモンイチロウ1担当J01AI000012011/08/100
FU00001藤井雄太フジイユウタ1主任E01SE000012011/04/100
FU00002藤岡幸太郎フジオカコウタロウ1担当E01FU000012011/06/120
HA00001速水和幸ハヤミカズユキ1部長E012007/02/031
HA00002葉山俊輔ハヤマシュンスケ1アシスタントE01FU000022012/12/110
KA00001川口裕子カワグチユウコ2アシスタントS01KI000012012/12/281
KA00002加藤昭雄カトウアキオ1アシスタントS01KI000012011/11/110
KA00003神田佐知子カンダサチコ1アシスタントJ01KA000032012/01/280
KI00001木村一郎キムライチロウ1担当S01TA000012011/07/120
NA00001中澤康代ナカザワヤスヨ2アシスタントS02TO000022012/04/171
NO00001野上利江ノガミリエ2アシスタントS02TO000022012/06/300
SA00001佐藤智子サトウトモコ2部長S012010/02/010
SE00001妹尾春樹セノオハルキ1課長E01TE000012010/12/100
SI00001清水春子シミズハルコ2主任J01SI000012011/12/110
TA00001田中真由子タナカマユコ2主任S01YA000012011/05/260
TA00002田辺正一タナベショウイチ1アシスタントE01HA000022012/12/190
TE00001寺岡陽一テラオカヨウイチ1部長E01HA000012009/12/110
TI00001近田晃子チカダアキコ2課長S02AI000012012/03/010
TO00001遠山真一トオヤマシンイチ1課長J01UE000012010/12/160
TO00002戸川よしみトガワヨシミ2担当S02TU000012012/04/010
TU00001津村知美ツムラカズミ2主任S02TI000012012/03/160
UE00001上田一也ウエダカズヤ1部長J012011/11/120
YA00001山田奈美ヤマダナミ2課長S01SA000012012/10/150
NI00001西ニシユウイチ1アシスタントB012013/01/100
time_card
s_idr_datework_time
TA000012012/12/0110
TA000012012/12/0211
TA000012012/12/0510
TA000012012/12/069
TA000012012/12/0710
TA000012012/12/088
TA000012012/12/098
TA000012012/12/128
TA000012012/12/138
TA000012012/12/144
TA000012012/12/158
TA000012012/12/168
TA000012012/12/1910
TA000012012/12/209
TA000012012/12/219
TA000012012/12/228
TA000012012/12/2311
TA000012012/12/269
TA000012012/12/278
TA000012012/12/286
KI000012012/12/018
KI000012012/12/0210
KI000012012/12/058
KI000012012/12/069
KI000012012/12/078
KI000012012/12/088
KI000012012/12/099
スキーマの確認
employee
No.論理名物理名データ型Not Null
1社員IDs_idcharacter(7)Yes (PK)
2l_namecharacter varying(20)
3f_namecharacter varying(20)
4氏(カナ)l_name_kanacharacter varying(100)
5名(カナ)f_name_kanacharacter varying(100)
6性別sexsmallint
7役職classcharacter varying(20)
8所属部署IDdepart_idcharacter(3)
9上司IDb_idcharacter(7)
10最終更新日last_updatedate
11退職フラグretiredsmallint
time_card
No.物理名データ型Not Null
1s_idcharacter(7)Yes (PK)
2r_datedateYes (PK)
3work_timenumeric
ER

店舗テーブル(shop)と月間売り上げテーブル(sales)から2012年12月のs_nameごとのs_valueを売上高(s_value)の高い順で取り出すSQL文を完成させろ。

sql
SELECT
  sh.s_name
FROM
  shop sh
;
result
s_name
三吉町店
三吉町南店
東桜町店
北花町駅前店
北花町東店
テーブル確認用
shop
s_ids_name
M0001三吉町店
M0002三吉町南店
H0001東桜町店
K0001北花町駅前店
K0002北花町東店
sales
s_ids_dates_value
M00012012-1211145
H00012012-129861
N00012012-1211023
K00012012-1211234
K00022012-1210456
M00012012-1110784
M00022012-1112032
H00012012-119975
N00012012-119965
K00012012-1110142
K00022012-1110124
スキーマの確認
shop
No.物理名データ型Not Null
1s_idcharacter(5)Yes (PK)
2s_namecharacter varying(30)
sales
No.物理名データ型Not Null
1s_idcharacter(5)Yes (PK)
2s_datecharacter(7)Yes (PK)
3s_valueinteger
ER

店舗テーブル(shop)と月間売り上げテーブル(sales)から店名ごとに2012年の売上高を売上高の低い順で取り出せ。

sql
SELECT
  sh.s_name
FROM
  shop sh
;
result
s_name
三吉町店
三吉町南店
東桜町店
北花町駅前店
北花町東店
テーブル確認用
shop
s_ids_name
M0001三吉町店
M0002三吉町南店
H0001東桜町店
K0001北花町駅前店
K0002北花町東店
sales
s_ids_dates_value
M00012012-1211145
H00012012-129861
N00012012-1211023
K00012012-1211234
K00022012-1210456
M00012012-1110784
M00022012-1112032
H00012012-119975
N00012012-119965
K00012012-1110142
K00022012-1110124
スキーマの確認
shop
No.物理名データ型Not Null
1s_idcharacter(5)Yes (PK)
2s_namecharacter varying(30)
sales
No.物理名データ型Not Null
1s_idcharacter(5)Yes (PK)
2s_datecharacter(7)Yes (PK)
3s_valueinteger
ER

Q2-1-5:貸し出し記録テーブル(rental)とユーザテーブル(usr)からユーザについて、現在何冊の貸出を行っているかをユーザーID、氏名、貸し出し数を貸し出し数の多い順で取り出せ。

sql
SELECT
  u.user_id,
  u.l_name||u.f_name AS 氏名
FROM
  usr AS u
;
result
user_id氏名
B200505根岸裕子
A200403江本聡
A200402上原幸一
A200501山田太郎
A200404小野雄二
A200401井上一郎
B200503森本絵里
A200505坂口駿
B200403吉岡遥
aaa井上次郎
B200402有木茉莉
B200401矢口亜由美
A200502木下一樹
B200504西島文子
B200501南沙恵
A200405河合泰治
B200502村井佐知子
A200504児玉毅
B200405松本博美
A200506井上一郎
B200404和田修子
A200503久保田守
(22 ?s)
テーブル確認用
usr
user_idl_namef_namel_name_kanaf_name_kanaprefecturecityo_addresstelemail
A200401井上一郎イノウエイチロウ千葉県東千葉市北町2-4-1040-111-0001inoue@wings.msn.to
A200402上原幸一ウエハラコウイチ神奈川県神奈川市南町1-5-2040-222-0002uehara@wings.msn.to
A200403江本エモトサトシ茨城県茨城市西町2-1-12040-333-0003emoto@wings.msn.to
A200404小野雄二オノユウジ東京都東京市南町3-2-6040-111-0004ono@examples.com
A200405河合泰治カワィヤスハル神奈川県神奈川市北町4-2-12040-222-0005kawai@wings.msn.to
B200401矢口亜由美ヤグチアユミ千葉県東千葉市南町3-4-6040-111-0011yaguchi@wings.msn.to
B200402有木茉莉アリキマリ神奈川県神奈川市北町1-4-5040-222-0012yuki@wings.msn.to
B200403吉岡ヨシオカハルカ茨城県茨城市東町7-2-1040-333-0013yoshi@examples.com
B200404和田修子ワダシュウコ東京都東京市北町6-1-11040-111-0014wada@wings.msn.to
B200405松本博美マツモトヒロミ神奈川県神奈川市南町3-4-21040-222-0015matsu@wings.msn.to
A200501山田太郎ヤマダタロウ東京都東京市西町0-0-0040-111-0031taro_yama@wings.msn.to
A200502木下一樹キノシタカズキ茨城県茨城市東町5-1-10040-333-0006kino@wings.msn.to
A200503久保田クボタマモル千葉県東千葉市南町2-1-21040-111-0007kubota@wings.msn.to
A200504児玉コダマツヨシ埼玉県西埼玉市東町3-4-11040-444-0008kodama@wings.msn.to
A200505坂口駿サカグチシュン栃木県北栃木市西町6-2-4040-555-0009saka@examples.com
A200506井上一郎イノウエイチロウ静岡県南静岡市花町4-2-1040-666-0010shimi@wings.msn.to
B200501沙恵ミナミサエ茨城県茨城市西町3-3-2040-333-0016minami@wings.msn.to
B200502村井佐知子ムライサチコ千葉県東千葉市北町4-1-13040-111-0017murai@wings.msn.to
B200503森本絵里モリモトエリ埼玉県西埼玉市西町2-1-4040-444-0018morimo@examples.com
B200504西島文子ニシジマフミコ栃木県北栃木市東町2-5-7040-555-0019nishi@wings.msn.to
B200505根岸裕子ネギシユウコ静岡県南静岡市寺町3-4-8040-666-0020negi@wings.msn.to
aaa(22行)井上次郎
rental
iduser_idisbnrental_datereturned
1A2005014-0010-0000-02011-12-011
2A2004034-8833-0000-22010-12-031
3A2004054-8833-0000-22011-03-201
4B2004034-8833-0000-22011-04-161
5B2004054-0010-0000-02012-01-161
6A2005024-8833-0000-22011-07-221
7A2005054-8833-0000-22011-08-231
8B2005014-8833-0000-22011-11-101
9B2005034-8833-0000-22012-01-201
10B2005044-0010-0000-02012-03-200
11A2004014-8833-0000-22012-02-111
12A2004024-8833-0000-22012-02-220
13A2004044-8833-0000-22012-03-191
14A2004054-0010-0000-02012-06-140
15B2004024-8833-0000-22012-08-271
16B2004044-8833-0000-22012-12-100
17A2005024-7980-0522-32011-10-251
18A2005064-7981-0722-02012-03-261
19B2005014-0010-0000-02012-09-151
20B2005034-8833-0000-12013-03-160
21B2005044-7980-0945-82013-01-140
22B2005054-7981-0959-22013-01-120
23A2004054-8833-0000-32012-12-220
24B2004034-0010-0000-92012-12-270
25B2004054-0010-0000-12013-03-100
スキーマの確認
usr
No.物理名データ型Not Null
1user_idcharacter(7)Yes (PK)
2I_namecharacter varying(20)
3f_namecharacter varying(20)
4I_name_kanacharacter varying(100)
5f_name_kanacharacter varying(100)
6prefecturecharacter varying(15)
7citycharacter varying(20)
8o_addresscharacter varying(100)
9telcharacter varying(20)
10emailcharacter varying(255)
sales
No.物理名データ型Not Null
1s_idcharacter(5)Yes (PK)
2s_datecharacter(7)Yes (PK)
3s_valueinteger
ER

外部結合(RIGHT JOIN ON句/LEFT JOIN ON 句)

LEFT OUTER JOIN (左外部結合)

  • 目的: 左テーブルの全レコードを必ず出力。右テーブルで結合条件に合うレコードがあれば結合。
  • 結果:
    • 左テーブルの全レコードが出力される。
    • 右テーブルに結合条件が合うレコードがない場合、右テーブルのカラムは NULL になる。
  • イメージ: 左側を基準に右側をくっつける。右になくても左は必ず出す。
  • 使い分け: 「左テーブルの情報を網羅的に見たい。関連する右テーブルの情報があれば付加したい」場合。
  • 注意: WHERE句で右テーブルのカラムに IS NOT NULL 条件をつけると、内部結合に近くなる場合がある。
  • RIGHT OUTER JOIN (右外部結合)は上記が入れ替わる

FULL OUTER JOIN (完全外部結合):

  • 左側と右側の両方のテーブルの全てのレコードを結果に含めます。
  • 両方のテーブルで結合条件に一致するレコードがあれば結合し、片方のテーブルにしか一致するレコードがない場合は、もう片方のテーブルのカラムは NULL になります。
  • イメージ: 両方のテーブルの情報を全て表示し、一致する部分はくっつける。どちらかのテーブルにしか情報がない場合でも、それぞれの情報を NULL で補完して表示する。

共通ポイント

  • ON句: 結合条件は必須。どのカラムで結合するか指定。
  • NULL値: 結合できない場合、相手テーブルのカラムは NULL になる。
  • 可読性: LEFT JOIN を基本とし、RIGHT JOIN は必要最低限に留める方が可読性向上に繋がる場合がある。 (LEFT JOIN でテーブル順序を入れ替えれば RIGHT JOIN と同じ結果になるため)

使い分けの目安

  • LEFT OUTER JOIN: 「Aテーブルの情報を全て見たい。Bテーブルに関連情報があれば一緒に表示したい」という場合に利用します。(例:顧客情報と注文履歴。顧客全員を表示し、注文履歴がある顧客には注文履歴も表示する)
  • RIGHT OUTER JOIN: 「Bテーブルの情報を全て見たい。Aテーブルに関連情報があれば一緒に表示したい」という場合に利用します。(LEFT OUTER JOIN の左右を入れ替えた場合と同じ結果になることが多い)
  • FULL OUTER JOIN: 「AテーブルとBテーブルの情報を全て見たい。両方のテーブルに存在する情報を繋ぎ合わせ、片方にしかない情報も全て表示したい」という場合に利用します。(例:従業員情報と部署情報。従業員全員と部署全員を表示し、所属している従業員は部署情報も表示する)

Q2-2-1-1:注文明細テーブル(order_desc)と商品テーブル(product)から商品コード(p_id)毎に累計購入数(quantityの合計)と商品名(p_name),価格(price)を取得せよ。

sql
SELECT
 p.p_id,
 p.p_name,
 p.price
FROM
 product AS p
;
product
p_idp_nameprice
PB00000001黒ボールペン100
PB00000002赤ボールペン100
DE00000001電卓600
TO00000001トナー黒1000
TO00000002トナー赤1000
TU0000000115000
IS00000001椅子10000
MA00000000マウスパッド1500
Q2-2-1-2:前頁の結果の価格(price)を累計購入額(quantity×price)として購入額の高い順で取得せよ。
sql
SELECT
 p.p_id,
 p.p_name,
 p.price,
 SUM(o.quantity) sum_q,
 SUM(o.quantity * p.price) sum_qp
FROM
 product AS p
INNER JOIN
 order_desc AS o
ON
 o.p_id = p.p_id
GROUP BY
 p.p_id
ORDER BY
 sum_qp DESC
;
result
p_idp_namepricesum_qsum_qp
PB00000002赤ポールペン10060060000
DE00000001電卓6009054000
PB00000001黒ポールペン10040140100
TU0000000115000230000
IS00000001椅子10000330000
TO00000001トナー黒10002020000
TO00000002トナー赤10002020000
Q2-2-1-3:前頁の結果に商品テーブルに登録されているが注文明細に記録されていない商品も取得せよ。
sql
SELECT
 p.p_id,
 p.p_name,
 p.price,
 SUM(o.quantity) sum_q,
 SUM(o.quantity * p.price) sum_qp
FROM
 product AS p
LEFT JOIN
 order_desc AS o
ON
 o.p_id = p.p_id
GROUP BY
 p.p_id
ORDER BY
 sum_qp DESC
;
result
p_idp_namepricesum_qsum_qp
MA00000000マウスパッド1500NULLNULL
PB00000002赤ポールペン10060060000
DE00000001電卓6009054000
PB00000001黒ポールペン10040140100
TU0000000115000230000
IS00000001椅子10000330000
TO00000002トナー赤10002020000
TO00000001トナー黒10002020000
テーブル確認用
order_desc
po_idp_idquantity
1PB00000001200
2PB00000002300
3DE0000000120
4TO0000000110
5TO0000000210
6TU000000012
7IS000000013
8PB00000001100
9PB00000002150
10DE0000000110
11TO000000015
12TO000000025
13PB000000011
14PB0000000250
15DE0000000150
16TO000000015
17TO000000025
18PB00000001100
19PB00000002100
20DE0000000110
product
p_idp_nameprice
PB00000001黒ボールペン100
PB00000002赤ボールペン100
DE00000001電卓600
TO00000001トナー黒1000
TO00000002トナー赤1000
TU0000000115000
IS00000001椅子10000
MA00000000マウスパッド1500
スキーマの確認

Q2-2-2ユーザテーブル(usr)と貸し出し記録テーブル(rental)からユーザごとの貸し出し数が多い順で取得するSQL文を完成させろ。なお、貸し出しがないユーザの貸し出し数は「0」として出力せよ。

sql
SELECT
  r.id
FROM
  rental AS r
result
id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
スキーマの確認

Q2-2-3:社員テーブル(employee)と所属部署テーブル(depart)を結合し所属社員が一人もいない「幽霊」部署を洗い出すSQL文を完成させろ。

sql
SELECT
  d.*
FROM
  depart AS d
;
result
depart_iddepart_name
S01経営企画部
E01第一営業部
E02第二営業部
J01人事部
S02総務部

Q2-2-4:書籍情報テーブル(books)と貸し出し記録テーブル(rental)を結合し、書籍毎の貸し出し累計数を多い順に取り出せ。この時貸し出しが1回もないものも取り出せ。

sql
SELECT
  r.*
FROM
  rental AS r
;
result
iduser_idisbnrental_datereturned
1A2005014-0010-0000-02011/12/011
2A2004034-8833-0000-22010/12/031
3A2004054-8833-0000-22011/03/201
4B2004034-8833-0000-22011/04/161
5B2004054-0010-0000-02012/01/161
6A2005024-8833-0000-22011/07/221
7A2005054-8833-0000-22011/08/231
8B2005014-8833-0000-22011/11/101
9B2005034-8833-0000-22012/01/201
10B2005044-0010-0000-02012/03/200
11A2004014-8833-0000-22012/02/111
12A2004024-8833-0000-22012/02/220
13A2004044-8833-0000-22012/03/191
14A2004054-0010-0000-02012/06/140
15B2004024-8833-0000-22012/08/271
16B2004044-8833-0000-22012/12/100
17A2005024-7980-0522-32011/10/251
18A2005064-7981-0722-02012/03/261
19B2005014-0010-0000-02012/09/151
20B2005034-8833-0000-12013/03/160
21B2005044-7980-0945-82013/01/140
22B2005054-7981-0959-22013/01/120
23A2004054-8833-0000-32012/12/220
24B2004034-0010-0000-92012/12/270
25B2004054-0010-0000-12013/03/100

Q2-2-5:社員テーブル(employee)とタイムカードテーブル(time_card)を結合し、社員ごとの勤務時間平均を多い順に取り出せ。この時記録のない社員ないものも取り出せ。

sql
SELECT
 *
FROM
  employee AS e
;
result
s_idl_namef_namel_name_kanaf_name_kanasexclassdepart_idb_idlast_updateretired
AI00001相沢アイザワサトシ1部長S022012/02/240
DA00001大門一郎ダイモンイチロウ1担当J01AI000012011/08/100
FU00001藤井健太フジイユウタ1主任E01SE000012011/04/100
FU00002福岡幸太郎フジオカコウタロウ1担当E01FU000012011/06/120
HA00001速水和幸ハヤミカズユキ1部長E012007/02/031
HA00002葉山俊輔ハヤマシュンスケ2アシスタントE01FU000022012/12/110
KA00001川口裕子カワグチユウコ2アシスタントS01KI000012012/12/281
KA00002加藤昭雄カトウアキオ1アシスタントS01KI000012011/11/110
KA00003神田佐知子カンダサチコ1アシスタントJ01KA000032012/01/280
KI00001木村一郎キムライチロウ1担当S01TA000012011/07/120
NA00000中澤康代ナカザワヤスコ2アシスタントS02TO000022012/04/171
NO0000野上利江ノガミリエ2アシスタントS02TO000022012/06/300
SA00001佐藤智子サトウトモコ2部長S012010/02/010
SE00001妹尾春樹セノオハルキ1課長E01TE000012010/12/100
SI00001清水春子シミズハルコ2主任J01SI000012011/12/110
TA00001田中真由子タナカマユコ2主任S01YA000012011/05/260
TA00002田辺正一タナベショウイチ1アシスタントE01HA000022012/12/190
TE00001寺岡陽一テラオカヨウイチ1部長E01HA000012009/12/110
TI00001近田晃子チカダアキコ2課長S02AI000012012/03/010
TO00001遠山真一トオヤマシンイチ1課長J01UE000012010/12/160
TO00002戸川よしみトガワヨシミ2担当S02TU000012012/04/010
TU00001津村知美ツムラカズミ2主任S02TI000012012/03/160
UE00001上田一也ウエダカズヤ1部長J012011/11/120
YA00001山田奈美ヤマダナミ2課長S01SA000012012/10/150
NI00001西雄一ニシユウイチ1アシスタントB012013/01/100
NI00002新渡戸康治ニトベコウジ1アシスタントB012013/01/150

自己結合(JOIN句+別名定義)

自己結合とは、同じテーブル同士の結合のことをいいます。

sql
FROM 
 table as t1
INNER JOIN
 table AS t2
ON
 t1.id=t2.n_id

最も一般的な用途は循環リレーションシップ(再帰)への対応と言われています。自分のテーブルに存在する列を外部キーとして指定するような、テーブルに循環型の外部キーが含まれる場合があるときに、自己結合はよく用いられます。

Q2-3-1-1:書類目次テーブル(contents)からコンテンツコード(c_id)順に取得せよ。

sql
SELECT
 *
FROM
 contents
ORDER BY
 c_id
;
result
c_idc_titlenext_id
A001SQLについてA011
A011データの取得1A012
A012カラムの選択A013
A013WHERE句の記述A014
A014NULLと空白の扱いA015
A015あいまい検索A016
A016複数条件の連結(論理演算子)A017
A017重複レコードの除去A021
A021データの取得2A022
A022順位付けA023
A023ソートA024
A024グルーピングA025
A025グルーピング結果の絞り込みA026
A026クロス集計A027
A027サブクエリーA028
A028演算子による集計A029
A029複数表の結合A031
A031更新系SQLA032
A032レコードを1件挿入するA033
A033サブクエリで複数レコードを挿入するA034
A034全レコード一律の更新を行うA035
A035特定行の更新を行うA036
A036特定レコードの削除を行うA037
A037トランザクションA041
A041データベースの作成A042
A042データベースの作成/削除A043
A043制約条件A044
A044テーブルの作成/削除A045
A045テーブル情報の変更A046
A046インデックスの作成/削除A047
Q2-3-1-2:前頁の結果に次のコンテンツコード(next_id)が示す次のコンテンツ名(c_title)を追加して取得せよ。
sql
SELECT
  cc.c_title title,
  cn.c_title next
FROM
  contents AS cc
INNER JOIN
  contents AS cn
ON
  cc.next_id = cn.c_id
ORDER BY
  cc.c_id
;
result
titlenext
SQLについてデータの取得1
データの取得1カラムの選択
カラムの選択WHERE句の記述
WHERE句の記述NULLと空白の扱い
NULLと空白の扱いあいまい検索
あいまい検索複数条件の連結(論理演算子)
複数条件の連結(論理演算子)重複レコードの除去
重複レコードの除去データの取得2
データの取得2順位付け
順位付けソート
ソートグルーピング
グルーピンググルーピング結果の絞り込み
グルーピング結果の絞り込みクロス集計
クロス集計サブクエリー
サブクエリー演算子による集計
演算子による集計複数表の結合
複数表の結合更新系SQL
更新系SQLレコードを1件挿入する
レコードを1件挿入するサブクエリで複数レコードを挿入する
サブクエリで複数レコードを挿入する全レコード一律の更新を行う
全レコード一律の更新を行う特定行の更新を行う
特定行の更新を行う特定レコードの削除を行う
特定レコードの削除を行うトランザクション
トランザクションデータベースの作成
データベースの作成データベースの作成/削除
データベースの作成/削除制約条件
制約条件テーブルの作成/削除
テーブルの作成/削除テーブル情報の変更
テーブル情報の変更インデックスの作成/削除

(29 行)

schema

Q2-3-3:メニューテーブル(menu)からそれぞれのメニュー名(title)と対応する親メニュー名(title)をページID(page_id)の昇順で取得するSQL文を完成せよ。

sql
SELECT
  mc.title AS メニュー名
FROM
  menu AS mc
ORDER BY
  mc.page_id
;
result
メニュー名
ホーム
WINGSについて
WINGSの歴史
WINGSメンバー紹介
WINGSメンバ募集
技術記事オンライン公開
最新インターネット記事
PHP関連記事
Java関連記事
ASP.NET関連記事
新刊案内
PHP関連新刊
Java関連新刊
ASP.NET関連新刊

(14 rows)

schema

Q2-3-4: 社員テーブル(employee)からそれぞれ社員氏名(l_nameとf_name)と上司氏名(b_idが上司のs_id)、社員コード(s_id)の昇順で取得せよ。上司のいない社員についても情報を取り出せ。

sql
SELECT
  s.l_name||s.f_name AS 社員氏名
FROM
  employee AS s
ORDER BY
  s.s_id ASC
;
result
社員氏名
相沢聡
大門一郎
藤井雄太
藤岡幸太郎
速水和幸
葉山俊輔
川口裕子
加藤昭雄
神田佐知子
木村一郎
中澤康代
西雄一
新渡戸康治
野上利江
佐藤智子
妹尾春樹
清水春子
田中真由子
田辺正一
寺岡陽一
近田晃子
遠山真一
戸川よしみ
津村知美
上田一也
山田奈美

(26 rows)

schema

3つ以上の結合(JOIN句の入れ子)

ここまでは、2つのテーブルの結合を学習してきましたが、RDBでは正規化されているため、3つ以上の結合が必要となります。
このような結合を実施するためには、結合した結果と、さらに結合する書き方(入れ子)を行います。

Q2-4-1: 書籍情報テーブル(books)と、著者情報テーブル(author_books)、著者テーブル(author)を結合して出版社(publish)が「山田出版」である、刊行日の新しい順で書名(title)、著者名(name)と刊行日(publish_date)を取得せよ。

sql
SELECT
 b.title,
 b.publish_date
FROM
 books AS b
;
result
titlepublish_date
PEAR入門2012-09-08
PHP5サンプル集2012-11-01
SQLリファレンス2013-02-15
XML辞典2011-09-16
JSPリファレンス2010-04-19
SQLプチブック2010-11-30
XMLリファレンス2012-11-24
ハムスターの観察2010-11-01
フェレットの観察2012-10-26
SQL入門2012-10-30
PHPドリル2013-01-14
らくだの観察日記2012-12-24
あひるの観察日記2012-11-15
かえるの観察日記2013-01-15

(14 rows)

schema

Q2-4-2: 社員テーブル(employee)と所属部署テーブル(depart)、タイムカードテーブル(time_card)を結合し、社員コード(s_id)’DA00001’における2012年12月分の「所属部署名」「社員氏名」「勤務時間」を日付の昇順で取得するSQL文を完成させよ。

sql
SELECT
  d.depart_name AS 所属部署名
FROM
  depart AS d
;
result
所属部署名
経営企画部
第一営業部
第二営業部
人事部
総務部

(5 rows)

schema

Q2-4-3:書籍目次テーブル(contents)から前後のコンテンツを「前コンテンツ」「現コンテンツ」「次コンテンツ」として現ページのコンテンツコードの昇順で取り出すSQL文を完成させよ。

sql
SELECT
  cc.c_title AS 現コンテンツ
FROM
 contents cc
ORDER BY
 cc.c_id;
result
現コンテンツ
SQLについて
データの取得1
カラムの選択
WHERE句の記述
NULLと空白の扱い
あいまい検索
複数条件の連結(論理演算子)
重複レコードの除去
データの取得2
順位付け
ソート
グルーピング
グルーピング結果の絞り込み
クロス集計
サブクエリー
演算子による集計
複数表の結合
更新系SQL
レコードを1件挿入する
サブクエリで複数レコードを挿入する
全レコード一律の更新を行う
特定行の更新を行う
特定レコードの削除を行う
トランザクション
データベースの作成
データベースの作成/削除
制約条件
テーブルの作成/削除
テーブル情報の変更
インデックスの作成/削除

(30 rows)

schema

Q2-4-4:注文書テーブル(order_main)と注文明細テーブル(order_desc)、ユーザテーブル(usr)、商品テーブル(product)を結合し、未納の注文について、発注日、注文コード、利用者氏名、商品名、商品単価、購入数を発注日、注文コード、商品コードの昇順で取り出せ。

sql
SELECT
  om.order_date AS 発注日,
  om.po_id AS 注文コード
FROM
 order_main om
WHERE
 om.delivery_date IS NULL
ORDER BY
 om.order_date,
 om.po_id
;
result
発注日注文コード
2012-12-2219
2012-12-2520

(2 rows)

schema

副問い合わせ/サブクエリ(SELECT文の入れ子)

ここまでは、テーブルのカラムを結合する方法を学習してきましたが、検索結果をもとにさらに検索しなくてはならないことがあります。
それを実現するのが、サブクエリと言います。

EXISTS

EXISTS 演算子は、サブクエリ内のレコードの存在をテストするために使用されます。

サブクエリが 1 つ以上のレコードを返す場合、EXISTS 演算子は TRUE を返します。

sql
SELECT
WHERE
 EXISTS (
  SELECT
  FROM
   shop
)
;
result
bool
TRUE
(1 row)
sql
SELECT
WHERE
 NOT EXISTS (
  SELECT
  FROM
   shop
)
;
result
bool
FALSE
(0 rows)

Q2-5-1月間売上テーブル(sales)にて2012年12月分(2012-12)の売り上げが登録されていない店舗の店舗コード(s_id)と店舗名(s_name)を取得せよ。

salesにありshopに登録されていない

sql
SELECT
 *
FROM
 shop s
FULL JOIN
 sales l
ON
 s.s_id=l.s_id
ORDER BY
 l.s_id
result
s_ids_names_ids_dates_value
H0001東桜町店H00012012-119975
H0001東桜町店H00012012-129861
K0001北花町駅前店K00012012-1110142
K0001北花町駅前店K00012012-1211234
K0002北花町東店K00022012-1110124
K0002北花町東店K00022012-1210456
M0001三吉町店M00012012-1110784
M0001三吉町店M00012012-1211145
M0002三吉町南店M00022012-1112032
“NULL”“NULL”N00012012-119965
“NULL”“NULL”N00012012-1211023
(11 rows)
schema

Q2-5-2:アンケート回答テーブル(quest)から回答者平均年齢を超えている人の回答(answer1、answer2列)を評価の低い順に取得せよ。

回答者のデータ

sql
SELECT
 answer1,
 answer2,
 age
FROM
 quest
ORDER BY
 answer1 ASC;
result
answer1answer2age
1“NULL”41
1“NULL”19
1絵が少ない。22
1ちょっと難しいです。26
2“NULL”26
2面白いです。25
2“NULL”56
236
2わかりやすいです。40
3絵がかわいい。34
3次回作に期待しています。32
3買ってよかった。35
3“NULL”18
3文字が小さい。64
3読みやすいです。24
3重宝しています。30
(16 rows)

Q2-5-3:所属部署テーブル(depart)から社員テーブル(employee)で使われていない部署コード部署名を取り出すSQL文を完成させよ。

外部結合の場合で検出

sql
SELECT
 d.depart_id,
 d.depart_name,
 e.depart_id
FROM
 depart d
LEFT JOIN
 employee e
ON
 d.depart_id = e.depart_id;
result
depart_iddepart_namedepart_id
S02総務部S02
J01人事部J01
E01第一営業部E01
E01第一営業部E01
E01第一営業部E01
E01第一営業部E01
S01経営企画部S01
S01経営企画部S01
J01人事部J01
S01経営企画部S01
S02総務部S02
S02総務部S02
S01経営企画部S01
E01第一営業部E01
J01人事部J01
S01経営企画部S01
E01第一営業部E01
E01第一営業部E01
S02総務部S02
J01人事部J01
S02総務部S02
S02総務部S02
J01人事部J01
S01経営企画部S01
E02第二営業部“NULL”
(25 rows)
schema

Q2-5-4:店舗テーブル(shop)と月間売り上げテーブル2012年12月の売上高が店舗ごとの売り上げ平均(全ての月総平均)を下回っている店舗名とその売上高を取得せよ。

店舗名の確認

sql
SELECT
 a.s_name
FROM
 shop a;
result
s_name
三吉町店
三吉町南店
東桜町店
北花町駅前店
北花町東店
(5 rows)

Q2-5-5:貸し出し記録テーブル(rental)を検索し、これまで一度も書籍の貸し出しを行ったことのないユーザの氏名をユーザテーブル(usr)から取得せよ

usr 氏名

sql
SELECT
 u.l_name || u.f_name AS name
FROM
 usr AS u;
result
name
井上一郎
上原幸一
江本聡
小野雄二
河合泰治
矢口亜由美
有木茉莉
吉岡遥
和田修子
松本博美
山田太郎
木下一樹
久保田守
児玉毅
坂口駿
井上一郎
南沙恵
村井佐知子
森本絵里
西島文子
根岸裕子
(21 rows)
schema

縦結合(UNION)

複数のSELECT文の結果を統合し、重複行を除いて全ての行を結果として返します。

  • 重複保持: UNION ALL は、重複する行を含むすべての行を結合された結果セットに含めます。つまり、複数の SELECT ステートメントが全く同じ行を返した場合、UNION ALL はそれらの行をすべて残します。
  • ソートなし: UNION ALL は通常、結果セットをソートしません。結果の順序は、SELECT ステートメントの順序と、各テーブル内のデータの順序に依存します。
  • パフォーマンス: 重複排除の処理を行わないため、一般的に UNION ALL は UNION よりも高速です。

UNION (および UNION ALL) を使用する際の注意点

  • カラム数とデータ型の一致: UNION で結合する SELECT ステートメントは、同じ数のカラムを持ち、対応するカラムのデータ型が互換性を持っている必要があります。互換性がない場合、エラーが発生します。
  • カラム名の扱い: UNION 後の結果セットのカラム名は、最初の SELECT ステートメントのカラム名が使用されます。
  • ORDER BY の位置: 結果セット全体をソートする場合は、ORDER BY 句を最後の SELECT ステートメントの後ろに記述します。個々のSELECTの結果を並び替えたい場合は、それぞれのSELECTの中でORDER BYを利用します。(ただし、一部のデータベースシステムでは、サブクエリ内での ORDER BY が無視されることがあります。)
  • 括弧の使用: 複雑なクエリを作成する場合、括弧を使用して SELECT ステートメントをグループ化し、UNION の適用順序を明確にすることができます。

Q2-6-5著者情報テーブル(author)とユーザテーブル(usr)からそれぞれ氏名の情報を重複を含まない氏名(カナ)の降順で取り出すSQL文の誤りを正せ。

sql
SELECT
 a.name_kana
FROM
 author AS a
ORDER BY
 1 DESC;
result
name_kana
ヤマダヨシヒロ
ヤマダアイコ
モリグチヤスオ
タナカタロウ
サトウイチロウ
カワナカトモキ
アリヨシハルミ
(7 rows)

Q2-6-1-3:ユーザテーブル(usr)と社員テーブル(employee)の検索結果を足した氏名カナ(l_name_kanaとf_name_kana)を取得せよ。

sql
SELECT
 u.l_name_kana,
 u.f_name_kana
FROM
 usr AS u
ORDER BY
 1,
 2;
result
l_name_kanaf_name_kana
アリキマリ
イノウエイチロウ
イノウエイチロウ
ウエハラコウイチ
エモトサトシ
オノユウジ
カワイヤスハル
キノシタカズキ
クボタマモル
コダマツヨシ
サカグチシュン
ニシジマフミコ
ネギシユウコ
マツモトヒロミ
ミナミサエ
ムライサチコ
モリモトエリ
ヤグチアユミ
ヤマダタロウ
ヨシオカハルカ
ワダシュウコ
(21 rows)

Q2-6-2:アンケート回答テーブル(quest)と社員テーブル(employee)から女性の氏名(カナ)だけを、氏(カナ)名(カナ)昇順で重複データをそのまま取り出すSQL文を完成させよ。

sql

氏名カナが一列、sexが男女

SELECT
 q.name_kana,
 q.sex
FROM
 quest q;
result
name_kanasex
ヤマダタロウ
イノウエマリ
ウエハラハルカ
エモトシュウコ
オノヒロミ
カワイタロウ
ヤグチカズキ
アリキマモル
ヨシオカツヨシ
ワダシュン
シミズイチロウ
ミナミコウイチ
ムライサトシ
モリモトユウジ
ニシジマヤスハル
ネギシアユミ
(16 rows)

縦結合(EXCEPT)

一方のSELECT文の結果から、他方に存在する行を除く演算子です。(データベースによって名称が異なり、SQL ServerであればEXCEPT、OracleではMINUSが使われます)
例: あるテーブルには存在するが、別のテーブルには存在しない行を取得したい場合。

Q2-6-3ユーザテーブル(usr)とアンケート回答テーブル(quest)のユーザ名を比較しユーザテーブルにしか登録されていないもののみを取り出すSQL文を完成させよ

sql
SELECT
 u.l_name || u.f_name name
FROM
 usr AS u
ORDER BY
 1 DESC;
result
name
和田修子
有木茉莉
矢口亜由美
木下一樹
南沙恵
村井佐知子
西島文子
森本絵里
上原幸一
松本博美
小野雄二
児玉毅
山田太郎
坂口駿
根岸裕子
江本聡
久保田守
吉岡遥
河合泰治
井上一郎
井上一郎
(21 rows)

縦結合(INTERSECT)

両方のSELECT文で共通して存在する行のみを求めるため、より厳密なデータの共通部分をチェックする場合に利用します。

Q2-6-6-1:ユーザテーブル(usr)とアンケート回答テーブル(quest)からそれぞれに含まれる氏名を比較し双方に含まれるデータの氏名を取り出せ。

sql
SELECT
 u.l_name || u.f_name AS name
FROM
 usr AS u
INTERSECT
SELECT
 q.name
FROM
 quest AS q;
result
name
山田太郎
(1 row)

この記事を書いた人

目次