3.SQLによる結合
概要
リレーショナルデータベースは複数のテーブルから構成される。リレーショナルデータベースでは,関連するデータが複数のテーブルに分散して格納されているため,複数のテーブルにまたがる情報を得るにはテーブルの結合が必要となる。この演習では,複数のテーブルを結合して1つのテーブルを生成する操作について説明し,SQLite3データベースとPythonを用いた結合操作の演習を行う。
外部キー
テーブル R(…, Ai, …) と S(…, Bj, …) について,属性 Bj がテーブル S の主キーであるとする。以下の条件が成り立つとき,属性 Ai をテーブル R の外部キーという。
R の任意の行における Ai の値は,S の Bj のいずれかの値と等しいか,または空値(NULL)である。
結合演算では,通常,外部キーとそれが参照する主キーを結合条件として使用する。
結合演算
R(A1, A2, …, An) と S(B1, B2, …, Bm) をテーブルとする。R の属性 Ai と S の属性 Bj に対する結合演算「R[Ai θ Bj]S」は次のように定義される(θ は比較演算子である)。
結合演算「R[Ai θ Bj]S」は,SQLを用いて次のように記述できる。
SELECT * FROM R, S WHERE R.Ai θ S.Bj
結合問い合わせ
結合問い合わせとは,SQL問い合わせにおける WHERE <式> の中に結合演算を含む問い合わせのことである。
SQLite3のデータベースファイル
この授業では,リレーショナルデータベース管理システム SQLite3 を使用する。SQLite3 では,リレーショナルデータベースが1つのファイルに格納される。そのため,データベースファイルのコピーや配布が容易である。
UML図
ここでのUML(Unified Modeling Language)図は,リレーショナルデータベースのテーブル名,各テーブルの属性名,各属性のデータ型,主キーなどを図示したものである。この授業で使用するデータベースの構造を以下に示す。
凡例 — PK: 主キー,FK: 外部キー,UNIQUE: 一意制約
| employees | |
|---|---|
| emp_no : INT | PK |
| birth_date : DATE | |
| first_name : VARCHAR(14) | |
| last_name : VARCHAR(16) | |
| gender : ENUM('M','F') | |
| hire_date : DATE | |
| departments | |
|---|---|
| dept_no : CHAR(4) | PK |
| dept_name : VARCHAR(40) | UNIQUE |
| dept_emp | |
|---|---|
| emp_no : INT | PK, FK → employees |
| dept_no : CHAR(4) | PK, FK → departments |
| from_date : DATE | |
| to_date : DATE | |
| dept_manager | |
|---|---|
| emp_no : INT | PK, FK → employees |
| dept_no : CHAR(4) | PK, FK → departments |
| from_date : DATE | |
| to_date : DATE | |
| titles | |
|---|---|
| emp_no : INT | PK, FK → employees |
| title : VARCHAR(50) | PK |
| from_date : DATE | PK |
| to_date : DATE | |
| salaries | |
|---|---|
| emp_no : INT | PK, FK → employees |
| salary : INT | |
| from_date : DATE | PK |
| to_date : DATE | |
テーブル間の関連: employees ←1:N→ dept_emp ←N:1→ departments,employees ←1:N→ dept_manager ←N:1→ departments,employees ←1:N→ titles,employees ←1:N→ salaries
出典: MySQL Employees Sample Database
https://dev.mysql.com/doc/employee/en/sakila-structure.html より引用
演習準備
この演習では Python を使用する。Python がインストールされていない場合は,下記の「Python 3.12 のインストール(Windows 上)」を展開し,手順に従いインストールすること。
Python 3.12 のインストール(Windows 上) [クリックして展開]
以下のいずれかの方法で Python 3.12 をインストールする。Python がインストール済みの場合、この手順は不要である。
方法1:winget によるインストール
管理者権限のコマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトを起動するには、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択する。
winget install -e --id Python.Python.3.12 --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 AssociateFiles=1 InstallLauncherAllUsers=1"
--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動すると PATH が自動的に設定される。
方法2:インストーラーによるインストール
- Python 公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンから Windows 用インストーラーをダウンロードする。
- ダウンロードしたインストーラーを実行する。
- 初期画面の下部に表示される「Add python.exe to PATH」に必ずチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから
pythonコマンドを実行できない。 - 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。
インストールの確認
コマンドプロンプトで以下を実行する。
python --version
バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。
演習
ステップ1:データベースの準備
以下の手順で,Employees Sample データベース(SQLite版)を準備する。
このデータベースは,MySQL Employees Sample Database を SQLite 形式に変換したものであり,GitHub 上の bytebase/employee-sample-database リポジトリで公開されている。
コマンドプロンプトで以下のコマンドを実行し,データベースファイルをカレントディレクトリにダウンロードする。python -c は,引用符内の Python コードをコマンドライン上で直接実行するオプションである。urllib.request.urlretrieve は,第1引数に指定した URL からファイルを取得し,第2引数に指定したファイル名で保存する関数である。
python -c "import urllib.request; urllib.request.urlretrieve('https://raw.githubusercontent.com/bytebase/employee-sample-database/main/sqlite/dataset_small/employee.db', 'employee.db')"
この授業で使用するデータベースの出典と著作権表示
データの出典: MySQL Employees Sample Database(原著者: Fusheng Wang, Carlo Zaniolo。リレーショナルスキーマ: Giuseppe Maxia。データ変換: Patrick Crews)
Original data is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
SQLite adaptation: bytebase/employee-sample-database (MIT License)
ステップ2:SQL演習
Pythonの対話型シェルを使って,SQLによるデータ操作を実行する。
Pythonの対話型シェルは,コマンドプロンプト(Windows)またはターミナル(macOS / Linux)で python と入力して起動する。sqlite3 モジュールはPythonの標準ライブラリに含まれるため,追加のインストールは不要である。
① コマンドプロンプトまたはターミナルを開き,python と入力してPythonの対話型シェルを起動する。
python
② 次のPythonプログラムを実行し,SQLite3データベースに接続する。この演習で使用するデータベースファイルは,ステップ1でカレントディレクトリにダウンロードした employee.db である。
import sqlite3
c = sqlite3.connect('employee.db')
③ データベースに含まれるテーブルの一覧を確認する。sqlite_master は SQLite が内部的に保持するシステムテーブルであり,データベース内のテーブルやインデックスなどの定義情報が格納されている。type='table' の条件を指定することで,テーブル名のみを取得できる。
result = c.execute("select name from sqlite_master where type='table'").fetchall()
print(result)
④ 各テーブルの列情報を確認する。PRAGMA table_info(テーブル名) は,指定したテーブルの列名,データ型,主キーの有無などの構造情報を返す SQLite 固有のコマンドである。
for table_name in ['department', 'dept_manager', 'dept_emp', 'employee', 'title', 'salary']:
print(table_name)
result = c.execute(f"PRAGMA table_info({table_name})").fetchall()
print(result)
print()
⑤ 次のPythonプログラムを実行し,テーブルの全データを読み出す。データの読み出しには,SQL文の実行と結果の取得を行うオブジェクトであるカーソルを使用する。
result = c.execute("select * from department").fetchall()
print(result)
続けて,以下も実行する。
result = c.execute("select * from dept_manager").fetchall()
print(result)
実行結果から,例えば部門番号 d001 のマネージャが交替した時期などが確認できる。
⑥ 次のPythonプログラムを実行し,条件に合致する行を読み出す。salary テーブルの amount 列は給与額を表す。
result = c.execute("select * from salary where amount > 155000").fetchall()
print(result)
⑦ 結合問い合わせを実行する。amount > 155000 を満たす従業員の氏名などを取得する。ここでは,salary テーブルと employee テーブルを従業員番号(emp_no)で結合する。SQL文を複数行で記述するため,Pythonの三重引用符 """ を使用する。
result = c.execute("""select *
from salary, employee
where salary.emp_no = employee.emp_no
and amount > 155000""").fetchall()
print(result)
続けて,高給与の従業員の職位情報を得るために,salary テーブルと title テーブルを従業員番号(emp_no)で結合する以下の問い合わせも実行する。
result = c.execute("""select *
from salary, title
where salary.emp_no = title.emp_no
and amount > 155000""").fetchall()
print(result)
⑧ 接続を閉じる
c.close()