Day 65:JOIN ― 「別々のテーブルを、ひとつの景色として眺める」
Day 65では、データベースの世界で一気に視界が広がるテーマ、 JOIN(ジョイン) を扱っていきます。
キーワードはこの2つです。
- INNER JOIN
- LEFT JOIN
これまで、users テーブルや orders テーブルをそれぞれ単体で扱ってきました。 でも現実のシステムでは、「ユーザーと注文を一緒に見たい」「顧客と売上をまとめて眺めたい」といった場面が当たり前のように出てきます。
JOINは、そんなときに 「別々のテーブルを、ひとつの結果として結びつける」ための仕組み です。 ここを理解できると、データベースの世界がぐっと立体的に見えてきます。
JOINの世界観をつかむ ― 「2つの表を横にくっつける」イメージ
まずはテーブルを2つ用意する
Day 64で作った users と orders を使います。
usersテーブル(ユーザー情報)
| id | name | |
|---|---|---|
| 1 | Taro | taro@example.com |
| 2 | Hanako | hanako@example.com |
| 3 | Ken | ken@example.com |
ordersテーブル(注文情報)
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Book | 1200 |
| 2 | 1 | Pen | 200 |
| 3 | 2 | Notebook | 500 |
ここで orders.user_id が、users.id を参照する 外部キー になっています。
「ユーザー名と注文内容を一緒に見たい」という欲求
例えば、こんな情報が欲しくなる場面を想像してみてください。
誰が、どの商品を、いくらで買ったのかを一覧で見たい
つまり、
- ユーザー名(
users.name) - 商品名(
orders.product) - 価格(
orders.price)
を、ひとつの表として眺めたいわけです。
ここで登場するのが JOIN です。
INNER JOIN ― 「両方に共通しているものだけ結びつける」
INNER JOINの基本イメージ
INNER JOIN は、
両方のテーブルに「対応するレコードが存在するものだけ」を結びつける
という動きをします。
図でイメージすると、
usersの丸ordersの丸
があって、その 重なっている部分だけを取り出す 感じです。
INNER JOINの基本SQL
ユーザーと注文を結びつける INNER JOIN は、こう書きます。
SELECT
users.id,
users.name,
orders.product,
orders.price
FROM users
INNER JOIN orders
ON users.id = orders.user_id;
FROM users- ベースとなるテーブルを指定
INNER JOIN ordersordersテーブルを結びつける
ON users.id = orders.user_id- 「どのカラム同士を結びつけるか」を指定(結合条件)
Python+SQLiteで INNER JOIN を実行してみる
# day65_join_demo.py
import sqlite3
from pathlib import Path
def get_connection(db_name: str = "day65_example.db") -> sqlite3.Connection:
"""
SQLiteデータベースに接続するための関数です。
Day 64 で作ったテーブルを再利用しても構いません。
"""
return sqlite3.connect(Path(db_name))
def create_sample_tables(conn: sqlite3.Connection) -> None:
"""
users と orders のテーブルを作成し、
サンプルデータを投入する関数です。
JOINの動きを確認しやすいように、少しだけデータを入れます。
"""
cur = conn.cursor()
# users テーブル
cur.execute(
"""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
"""
)
# orders テーブル
cur.execute(
"""
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
product TEXT NOT NULL,
price REAL NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
"""
)
conn.commit()
# 既存データを一旦削除して、毎回同じ状態にします。
cur.execute("DELETE FROM orders;")
cur.execute("DELETE FROM users;")
conn.commit()
# users にサンプルデータを追加
cur.execute("INSERT INTO users (name, email) VALUES (?, ?);", ("Taro", "taro@example.com"))
cur.execute("INSERT INTO users (name, email) VALUES (?, ?);", ("Hanako", "hanako@example.com"))
cur.execute("INSERT INTO users (name, email) VALUES (?, ?);", ("Ken", "ken@example.com"))
conn.commit()
# orders にサンプルデータを追加
cur.execute("INSERT INTO orders (user_id, product, price) VALUES (?, ?, ?);", (1, "Book", 1200))
cur.execute("INSERT INTO orders (user_id, product, price) VALUES (?, ?, ?);", (1, "Pen", 200))
cur.execute("INSERT INTO orders (user_id, product, price) VALUES (?, ?, ?);", (2, "Notebook", 500))
conn.commit()
def inner_join_users_orders(conn: sqlite3.Connection) -> None:
"""
INNER JOIN を使って、ユーザーと注文を結びつけて表示する関数です。
"""
print("=== INNER JOIN: ユーザーと注文を結びつけて表示します ===")
sql = """
SELECT
users.id,
users.name,
orders.product,
orders.price
FROM users
INNER JOIN orders
ON users.id = orders.user_id;
"""
cur = conn.cursor()
cur.execute(sql)
rows = cur.fetchall()
for row in rows:
user_id, name, product, price = row
print(f"user_id={user_id}, name={name}, product={product}, price={price}")
def main():
conn = get_connection()
create_sample_tables(conn)
inner_join_users_orders(conn)
conn.close()
if __name__ == "__main__":
main()
Pythonこのスクリプトを実行すると、 「注文を持っているユーザーだけ」が表示されます。
- Taro(id=1)は Book と Pen を購入
- Hanako(id=2)は Notebook を購入
- Ken(id=3)は注文なし → INNER JOINでは出てこない
ここが、INNER JOINの大事なポイントです。
LEFT JOIN ― 「左側のテーブルを全部残しつつ、右側をくっつける」
LEFT JOINの基本イメージ
LEFT JOIN は、
左側のテーブルのレコードを「全部」残しつつ、 右側のテーブルに対応するものがあれば結びつける。 なければ右側は NULL にする。
という動きをします。
図でイメージすると、
- 左側の丸(
users)は全部残す - 右側の丸(
orders)と重なっている部分は結びつける - 重なっていない左側の部分も、そのまま出てくる(右側は空)
LEFT JOINの基本SQL
ユーザーを基準にして、注文があるかどうかを見たい場合は、こう書きます。
SELECT
users.id,
users.name,
orders.product,
orders.price
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
INNER JOINとの違いは、INNER か LEFT かだけ。 でも、結果はかなり違ってきます。
Python+SQLiteで LEFT JOIN を実行してみる
def left_join_users_orders(conn: sqlite3.Connection) -> None:
"""
LEFT JOIN を使って、ユーザーを基準に注文を結びつけて表示する関数です。
注文がないユーザーも、NULL付きで表示されます。
"""
print("=== LEFT JOIN: ユーザーを基準に注文を結びつけて表示します ===")
sql = """
SELECT
users.id,
users.name,
orders.product,
orders.price
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
"""
cur = conn.cursor()
cur.execute(sql)
rows = cur.fetchall()
for row in rows:
user_id, name, product, price = row
print(f"user_id={user_id}, name={name}, product={product}, price={price}")
Pythonmain() に追加して、INNER JOINとの違いを見てみましょう。
def main():
conn = get_connection()
create_sample_tables(conn)
inner_join_users_orders(conn)
print()
left_join_users_orders(conn)
conn.close()
Python結果の違いをしっかり感じてみる
- INNER JOIN
- 注文があるユーザーだけが表示される
- Ken(id=3)は出てこない
- LEFT JOIN
- 全ユーザーが表示される
- Ken(id=3)は、
productとpriceがNULLで表示される
LEFT JOINは、
「左側のテーブルを基準に、右側の情報があればくっつける。なければ空欄でいいから、とにかく左側は全部見たい」
というときに使います。
INNER JOINとLEFT JOINの使い分けを感覚でつかむ
INNER JOINが向いている場面
- 「両方に対応するデータがあるものだけ見たい」
- 「注文があるユーザーだけ」「売上がある商品だけ」
- 「紐づいていないデータは、今回の分析には不要」
たとえば、売上集計や「購入者だけの一覧」を作るときなどに向いています。
LEFT JOINが向いている場面
- 「左側のテーブルを基準に、右側の状況を知りたい」
- 「全ユーザーのうち、誰が注文していて、誰がしていないかを知りたい」
- 「全商品リストに、売上があるかどうかをくっつけたい」
「抜け漏れなく全体を見たいけれど、右側の情報はあれば嬉しい」という場面で活躍します。
Day 65ミニテンプレート:JOINの基本セット
最後に、今日の内容をコンパクトにまとめた JOIN基礎テンプレート を載せておきます。
# day65_join_template.py
import sqlite3
from pathlib import Path
def get_connection(db_name: str = "day65_example.db") -> sqlite3.Connection:
return sqlite3.connect(Path(db_name))
def setup(conn: sqlite3.Connection) -> None:
cur = conn.cursor()
cur.execute(
"""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
"""
)
cur.execute(
"""
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
product TEXT NOT NULL,
price REAL NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
"""
)
conn.commit()
# データを初期化
cur.execute("DELETE FROM orders;")
cur.execute("DELETE FROM users;")
conn.commit()
# users
cur.execute("INSERT INTO users (name, email) VALUES (?, ?);", ("Taro", "taro@example.com"))
cur.execute("INSERT INTO users (name, email) VALUES (?, ?);", ("Hanako", "hanako@example.com"))
cur.execute("INSERT INTO users (name, email) VALUES (?, ?);", ("Ken", "ken@example.com"))
conn.commit()
# orders
cur.execute("INSERT INTO orders (user_id, product, price) VALUES (?, ?, ?);", (1, "Book", 1200))
cur.execute("INSERT INTO orders (user_id, product, price) VALUES (?, ?, ?);", (1, "Pen", 200))
cur.execute("INSERT INTO orders (user_id, product, price) VALUES (?, ?, ?);", (2, "Notebook", 500))
conn.commit()
def inner_join(conn: sqlite3.Connection) -> None:
sql = """
SELECT users.id, users.name, orders.product, orders.price
FROM users
INNER JOIN orders
ON users.id = orders.user_id;
"""
cur = conn.cursor()
cur.execute(sql)
print("=== INNER JOIN ===")
for row in cur.fetchall():
print(row)
def left_join(conn: sqlite3.Connection) -> None:
sql = """
SELECT users.id, users.name, orders.product, orders.price
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
"""
cur = conn.cursor()
cur.execute(sql)
print("=== LEFT JOIN ===")
for row in cur.fetchall():
print(row)
def main():
conn = get_connection()
setup(conn)
inner_join(conn)
print()
left_join(conn)
conn.close()
if __name__ == "__main__":
main()
PythonDay 65のまとめ ― テーブル同士がつながると、データが「物語」を持ち始める
今日の主役は、
- INNER JOIN:両方に対応するデータがあるものだけ結びつける
- LEFT JOIN:左側のテーブルを全部残しつつ、右側をくっつける
という2つのJOINでした。
JOINを理解すると、
- 「ユーザーと注文」
- 「商品と売上」
- 「記事とタグ」
といった、複数テーブルにまたがる情報を、 ひとつの結果として自然に扱えるようになります。
データがただの「点」ではなく、 関係性を持った「物語」 として見えてくる瞬間です。
