SQLAlchemyで始めるPythonのデータベース操作 - ORMの基礎を学ぶ

Pythonでデータベースを扱うなら必須のSQLAlchemy。ORMの基礎からCRUD操作まで、初心者向けに会話形式でわかりやすく解説します。

SQLAlchemyで始めるPythonのデータベース操作 - ORMの基礎を学ぶ

Pythonでアプリケーションを作っていると、データを保存する必要が出てきます。そんな時に役立つのがデータベース。でも、SQL文を書くのは大変...そこで登場するのがSQLAlchemyです。今回は、Pythonでデータベースをもっと簡単に扱えるようになるSQLAlchemyの基本を、実際に動かしながら学んでいきましょう!

著者
著者: Sera
大学院でAI作曲に関して研究中!
来春からデータサイエンティストとして働く予定の技術オタク。
初心者
登場人物: あかり
流行りのAIやWeb技術に興味津々!
『知りたい』気持ちで質問を止められない、好奇心旺盛な学生。

なぜSQLAlchemyなのか?

normalの表情
初心者

ねえねえ、SQLAlchemyって何?データベースを使うのに必要なの?

専門家

SQLAlchemyは、Pythonでデータベースをもっと簡単に扱えるようにするライブラリなんだ。正式には「ORM」と呼ばれる種類のツールなんだよ。

confusedの表情
初心者

ORM?なにそれ...

専門家

ORMは「Object-Relational Mapping」の略で、「オブジェクトとデータベースを紐付ける」という意味なんだ。普通、データベースを使う時はSQL文という専用の言語で操作するんだけど、ORMを使うとPythonのコードだけでデータベースを扱えるようになるんだよ。

confusedの表情
初心者

SQL文って何?

専門家

例えば、ユーザー情報をデータベースから取得する時、通常はこんな文を書くんだ。

SELECT * FROM users WHERE age >= 25;

これがSQL文。でもSQLAlchemyを使うと、同じことをPythonっぽく書けるんだよ。

users = session.query(User).filter(User.age >= 25).all()

どう?Pythonのコードっぽいでしょ?

surprisedの表情
初心者

本当だ!Pythonで書けるんだ!

専門家

そうなんだ。SQLAlchemyを使うメリットは主に3つあるよ。

  1. Pythonで完結: SQL文を覚えなくても、Pythonの知識だけでデータベース操作ができる
  2. タイプセーフ: IDEの補完が効くから、タイプミスを防げる
  3. データベースの切り替えが簡単: SQLite、PostgreSQL、MySQLなど、設定を変えるだけで別のデータベースに切り替えられる
happyの表情
初心者

便利そう!早く使ってみたい!

SQLAlchemyの3つの柱

normalの表情
初心者

SQLAlchemyって、どういう仕組みになってるの?

専門家

SQLAlchemyは、主に3つの重要な要素で構成されてるんだ。Engine(エンジン)Base(ベース)Session(セッション) の3つだよ。

confusedの表情
初心者

3つ...覚えられるかな...

専門家

大丈夫、それぞれの役割を理解すれば簡単だよ。

まず Engine(エンジン) は、データベースへの接続を管理する部分。車のエンジンみたいに、データベースとの通信を動かす役割なんだ。

次に Base(ベース) は、テーブルの設計図を作るための基礎クラス。これを使って「ユーザーテーブル」とか「商品テーブル」とかを定義するんだよ。

最後に Session(セッション) は、データベースとの実際のやり取りを管理する部分。データの追加、検索、更新、削除といった操作は、すべてSessionを通して行うんだ。

normalの表情
初心者

エンジンが接続、ベースが設計図、セッションが操作、ってことか

専門家

そうそう!いい感じに理解できてるね。この3つが揃って、初めてSQLAlchemyが使えるようになるんだ。

環境構築とインストール

normalの表情
初心者

じゃあ、実際に使ってみたい!どうやってインストールするの?

専門家

SQLAlchemyは、pipで簡単にインストールできるよ。

pip install sqlalchemy

これだけでOK。

surprisedの表情
初心者

え、それだけ!?

専門家

そう!SQLAlchemyだけインストールすれば、すぐ使い始められるんだ。今回は SQLite というデータベースを使うんだけど、これはPythonに最初から入ってるから、追加でインストールする必要がないんだよ。

confusedの表情
初心者

SQLiteって何?PostgreSQLとかMySQLとかじゃないの?

専門家

いい質問だね。SQLiteは、ファイルベースの軽量なデータベースなんだ。PostgreSQLやMySQLは別途サーバーを立てる必要があるけど、SQLiteはファイル一つで完結するから、学習や小規模アプリに最適なんだよ。

そして、SQLAlchemyのいいところは、最初SQLiteで開発して、後からPostgreSQLに切り替えるのも簡単ってこと。コードはほとんど変えなくていいんだ。

happyの表情
初心者

なるほど!じゃあまずはSQLiteで練習するのがいいんだね

最初のテーブルを作ってみよう

excitedの表情
初心者

早速コード書いてみたい!

専門家

OK、じゃあ最初のテーブルを作ってみよう。ユーザー情報を保存するusersテーブルを作るよ。

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker

# 1. エンジンの作成(SQLiteファイル: users.db)
engine = create_engine("sqlite:///users.db", echo=True)

# 2. Baseクラスの作成
Base = declarative_base()

# 3. Userモデルの定義
class User(Base):
    __tablename__ = "users"  # テーブル名

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return f"<User(id={self.id}, name='{self.name}')>"

# 4. テーブルの作成
Base.metadata.create_all(engine)

# 5. Sessionの作成
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()

これで準備完了!

confusedの表情
初心者

えーと、いろいろ出てきた...順番に教えて!

専門家

OK、一つずつ見ていこう。

1. エンジンの作成

engine = create_engine("sqlite:///users.db", echo=True)

これでusers.dbというファイルにデータが保存されるようになる。echo=Trueは、実行されるSQL文を表示するオプションで、学習中は便利なんだ。

normalの表情
初心者

sqlite:///の部分は?

専門家

これは「データベースURL」と呼ばれるもので、どのデータベースを使うか指定してるんだ。sqlite:///はSQLiteを使う時の書き方。PostgreSQLならpostgresql://になるよ。

happyの表情
初心者

なるほど!

専門家

2. Baseクラスの作成

Base = declarative_base()

これは、すべてのモデルクラスの親になるクラス。これから作るテーブルは、全部このBaseを継承するんだ。

normalの表情
初心者

継承?

専門家

Pythonのクラスの仕組みだね。class User(Base):って書くことで、UserクラスがBaseの機能を受け継ぐんだ。これによって、SQLAlchemyがUserをテーブルとして認識できるようになるんだよ。

happyの表情
初心者

あー、クラスの継承か!

専門家

3. Userモデルの定義

これが一番重要な部分だね。

class User(Base):
    __tablename__ = "users"  # データベースのテーブル名

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    age = Column(Integer)

各カラム(列)を定義してるんだ。

confusedの表情
初心者

primary_keyとかnullableって何?

専門家

いい質問だね!

  • primary_key=True: このカラムが主キー(各行を識別するためのユニークなID)
  • autoincrement=True: 自動で番号を振ってくれる(1, 2, 3...)
  • nullable=False: 空(NULL)を許可しない、つまり必須項目
  • unique=True: 重複を許さない(メールアドレスが被ったらダメ)

ageには何も指定してないから、省略してもOKなんだ。

happyの表情
初心者

わかった!idは自動で番号が振られて、nameとemailは必須、emailは重複NG、ageは任意ってことだね

専門家

完璧!そして__repr__メソッドは、オブジェクトをprint()した時に見やすく表示するためのものなんだ。

4. テーブルの作成

Base.metadata.create_all(engine)

これで、定義したモデルに基づいて、実際にデータベースにテーブルが作成されるんだ。

normalの表情
初心者

これを実行すると、本当にテーブルができるの?

専門家

そう!実行するとusers.dbというファイルができて、その中にusersテーブルが作られるんだよ。

excitedの表情
初心者

すごい!Pythonのコードだけでテーブルが作れるんだ!

専門家

5. Sessionの作成

SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()

最後に、データベース操作のためのSessionを作成。これで、データの追加や検索ができるようになるんだ。

CRUD操作の基本

normalの表情
初心者

テーブルができたら、次は何するの?

専門家

次は、データの操作だね。CRUD操作って聞いたことある?

confusedの表情
初心者

CRUD?なにそれ

専門家

CRUDは、データベース操作の4つの基本を表す略語なんだ。

  • Create: データの追加
  • Read: データの取得
  • Update: データの更新
  • Delete: データの削除

この4つができれば、基本的なデータベース操作は全部できるようになるんだよ。

happyの表情
初心者

なるほど!じゃあ一つずつやってみよう!

Create: データの追加

専門家

まずは、ユーザーを追加してみよう。

# ユーザーオブジェクトを作成
user1 = User(name="田中太郎", email="tanaka@example.com", age=25)
user2 = User(name="佐藤花子", email="sato@example.com", age=30)
user3 = User(name="鈴木一郎", email="suzuki@example.com", age=28)

# Sessionに追加
session.add(user1)
session.add(user2)
session.add(user3)

# データベースに反映(コミット)
session.commit()

これで3人のユーザーがデータベースに保存されたよ。

confusedの表情
初心者

add()commit()って何が違うの?

専門家

いい質問だね!add()は「予約」みたいなもので、commit()で初めて「確定」されるんだ。

例えば、10人のユーザーを追加したいとき、10回add()してから1回commit()すればいい。こうすることで、途中でエラーが起きても、全部まとめてキャンセルできるんだよ。これを「トランザクション」って呼ぶんだ。

surprisedの表情
初心者

なるほど!まとめて予約して、最後に一気に確定するんだね

専門家

そういうこと!ちなみに、複数のオブジェクトを一度に追加するなら、add_all()も使えるよ。

session.add_all([user1, user2, user3])
session.commit()

Read: データの取得

normalの表情
初心者

次は、保存したデータを取り出してみたい!

専門家

データの取得は、query()メソッドを使うんだ。いくつかパターンを見てみよう。

# すべてのユーザーを取得
users = session.query(User).all()
print(users)
# [<User(id=1, name='田中太郎')>, <User(id=2, name='佐藤花子')>, ...]

all()は、条件に合うすべてのレコードをリストで返すんだ。

normalの表情
初心者

特定のユーザーだけ取得したい時は?

専門家

条件を指定するには、filter_by()filter()を使うんだよ。

# 年齢が28歳のユーザーを取得
user = session.query(User).filter_by(age=28).first()
print(user)
# <User(id=3, name='鈴木一郎')>

# 年齢が25歳以上のユーザーを取得
users = session.query(User).filter(User.age >= 25).all()
for user in users:
    print(user.name, user.age)

first()は、最初の1件だけを返すメソッドなんだ。

confusedの表情
初心者

filter_by()filter()の違いは?

専門家

いいところに気づいたね!

  • filter_by(age=28): イコール条件の時に使う、シンプルな書き方
  • filter(User.age >= 25): 比較演算子が使える、柔軟な書き方

イコールだけならfilter_by()、大小比較や複雑な条件ならfilter()って覚えておくといいよ。

happyの表情
初心者

わかった!使い分けるんだね

専門家

他にも、並び替えや件数制限もできるよ。

# 年齢順に並び替え(昇順)
users = session.query(User).order_by(User.age).all()

# 年齢順に並び替え(降順)
users = session.query(User).order_by(User.age.desc()).all()

# 最初の2件だけ取得
users = session.query(User).limit(2).all()
excitedの表情
初心者

いろんな取得方法があるんだね!

Update: データの更新

normalの表情
初心者

データを変更したい時はどうするの?

専門家

取得したオブジェクトの属性を変更して、commit()するだけなんだ。

# 田中太郎を検索
user = session.query(User).filter_by(name="田中太郎").first()
print(f"更新前: {user.name}, 年齢 {user.age}")

# 年齢を更新
user.age = 26

# データベースに反映
session.commit()

# 確認
user = session.query(User).filter_by(name="田中太郎").first()
print(f"更新後: {user.name}, 年齢 {user.age}")
surprisedの表情
初心者

え、属性を変更してcommitするだけ!?

専門家

そう!SQLAlchemyがオブジェクトの変更を追跡してくれるから、変更があった部分だけデータベースに反映してくれるんだ。これがORMの便利なところなんだよ。

happyの表情
初心者

めっちゃ簡単!

Delete: データの削除

normalの表情
初心者

最後に、削除の仕方を教えて!

専門家

削除は、delete()メソッドを使うんだ。

# 佐藤花子を検索
user = session.query(User).filter_by(name="佐藤花子").first()

# 削除
session.delete(user)

# データベースに反映
session.commit()

# 削除されたか確認
users = session.query(User).all()
print(f"残りのユーザー数: {len(users)}")
normalの表情
初心者

これもcommit()が必要なんだね

専門家

そう!追加、更新、削除、すべてcommit()するまでは確定されないんだ。これによって、間違って削除しちゃった時も、commit()する前ならrollback()でキャンセルできるんだよ。

surprisedの表情
初心者

rollback()?取り消しができるの!?

専門家

そう!例えば、こんな感じ。

# ユーザーを削除
user = session.query(User).filter_by(name="田中太郎").first()
session.delete(user)

# やっぱり取り消し!
session.rollback()

# 削除されていない
user = session.query(User).filter_by(name="田中太郎").first()
print(user)  # ちゃんと存在する

commit()してない変更は、rollback()で全部なかったことにできるんだ。

excitedの表情
初心者

それは安心だね!間違えても大丈夫!

クエリの書き方を深掘り

normalの表情
初心者

もうちょっとクエリの書き方について教えて!複雑な条件も書けるの?

専門家

もちろん!SQLAlchemyは、かなり複雑な条件も書けるんだ。

from sqlalchemy import and_, or_

# AND条件: 年齢が25以上で、名前が「田中」で始まる
users = session.query(User).filter(
    and_(
        User.age >= 25,
        User.name.startswith("田中")
    )
).all()

# OR条件: 年齢が25歳か30歳
users = session.query(User).filter(
    or_(
        User.age == 25,
        User.age == 30
    )
).all()

# IN条件: 年齢が25, 28, 30のいずれか
users = session.query(User).filter(User.age.in_([25, 28, 30])).all()

# LIKE条件: メールアドレスに「example」が含まれる
users = session.query(User).filter(User.email.like("%example%")).all()
confusedの表情
初心者

startswith()とかlike()とか、いろいろあるんだね

専門家

そう!SQLAlchemyのカラムには、便利なメソッドがたくさん用意されてるんだ。

  • startswith("田中"): 「田中」で始まる
  • endswith(".com"): 「.com」で終わる
  • contains("example"): 「example」を含む
  • like("%pattern%"): SQL のLIKE文
  • in_([1, 2, 3]): リストのいずれかに一致
  • is_(None): NULLかどうか
happyの表情
初心者

覚えきれないけど、必要な時に調べればいいよね!

専門家

そうそう!最初は基本的なfilter_by()filter()だけ覚えておけば十分。必要になったら調べればいいんだ。

実践例: ユーザー管理システム

excitedの表情
初心者

じゃあ、全部まとめて実際に動かしてみたい!

専門家

いいね!じゃあ、簡単なユーザー管理システムを作ってみよう。

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker

# セットアップ
engine = create_engine("sqlite:///users.db", echo=False)
Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return f"<User(id={self.id}, name='{self.name}', email='{self.email}', age={self.age})>"

Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)

# ユーザー追加関数
def add_user(name, email, age=None):
    session = SessionLocal()
    user = User(name=name, email=email, age=age)
    session.add(user)
    session.commit()
    session.close()
    print(f"✓ {name}さんを追加しました")

# ユーザー検索関数
def find_user_by_email(email):
    session = SessionLocal()
    user = session.query(User).filter_by(email=email).first()
    session.close()
    return user

# 全ユーザー表示関数
def list_all_users():
    session = SessionLocal()
    users = session.query(User).all()
    session.close()
    return users

# 使ってみる
add_user("田中太郎", "tanaka@example.com", 25)
add_user("佐藤花子", "sato@example.com", 30)
add_user("鈴木一郎", "suzuki@example.com", 28)

print("\n全ユーザー:")
for user in list_all_users():
    print(f"  {user.name} ({user.email}) - {user.age}歳")

print("\nメールで検索:")
user = find_user_by_email("tanaka@example.com")
print(f"  {user.name}さんが見つかりました")
happyの表情
初心者

おお!関数にまとめるとすっきりするね

専門家

そうなんだ。実際のアプリケーションでは、こうやって関数やクラスにまとめて使うことが多いんだよ。

ただ、一つ注意点があって、関数ごとにsession.close()を呼んでるでしょ?これ、忘れるとメモリリークの原因になるから注意が必要なんだ。

confusedの表情
初心者

メモリリーク?

専門家

Sessionを開きっぱなしにすると、メモリを消費し続けちゃうんだ。だから、使い終わったら必ずclose()する必要がある。

もっと安全な書き方として、コンテキストマネージャーを使う方法もあるよ。

from contextlib import contextmanager

@contextmanager
def get_session():
    session = SessionLocal()
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

# 使い方
with get_session() as session:
    user = User(name="山田太郎", email="yamada@example.com")
    session.add(user)
    # 自動でcommitとcloseされる

これなら、close()を忘れる心配がないんだ。

happyの表情
初心者

なるほど!自動で閉じてくれるんだね

よくあるエラーと対処法

normalの表情
初心者

使ってると、エラーが出ることもあるよね?

専門家

そうだね。よくあるエラーをいくつか紹介しておこう。

1. IntegrityError: UNIQUE constraint failed

user1 = User(name="田中太郎", email="tanaka@example.com")
user2 = User(name="田中次郎", email="tanaka@example.com")  # 同じメール!
session.add(user1)
session.add(user2)
session.commit()  # エラー!

これは、unique=Trueのカラムで値が重複した時のエラー。メールアドレスが既に使われてる時に起きるんだ。

confusedの表情
初心者

どうやって防げばいいの?

専門家

追加する前に、既に存在するかチェックするといいよ。

existing_user = session.query(User).filter_by(email="tanaka@example.com").first()
if existing_user:
    print("このメールアドレスは既に使われています")
else:
    user = User(name="田中太郎", email="tanaka@example.com")
    session.add(user)
    session.commit()
happyの表情
初心者

なるほど、先にチェックするんだね

専門家

2. DetachedInstanceError

これは、Sessionを閉じた後にオブジェクトにアクセスしようとした時のエラーなんだ。

session = SessionLocal()
user = session.query(User).first()
session.close()

print(user.name)  # OK(既に読み込まれてる)
print(user.posts)  # エラー!(リレーションシップはまだ読み込まれてない)

必要なデータは、Sessionを閉じる前に全部読み込んでおく必要があるんだよ。

sadの表情
初心者

難しそう...

専門家

大丈夫、最初は基本的なCRUD操作だけ覚えれば十分。リレーションシップは応用編で詳しく説明するよ。

まとめ

専門家

今回学んだことをまとめてみよう。

  • SQLAlchemy: PythonでデータベースをPythonっぽく扱えるORMライブラリ
  • 3つの柱: Engine(接続)、Base(設計図)、Session(操作)
  • CRUD操作: Create(add)、Read(query)、Update(属性変更)、Delete(delete)
  • commit(): 変更を確定する、忘れずに!
  • close(): Sessionを閉じる、メモリリーク防止

これで基本的なデータベース操作ができるようになったよ。

excitedの表情
初心者

SQLAlchemy、思ったより簡単だった!

専門家

そうでしょ?SQLを覚えなくても、Pythonの知識だけでデータベースが使えるのがSQLAlchemyの魅力なんだ。

次の応用編では、もっと実務的な内容を学ぶよ。複数のテーブルを関連付ける「リレーションシップ」や、パフォーマンスを改善する「N+1問題の解決」なんかを扱う予定。楽しみにしててね!

happyの表情
初心者

うん!次も楽しみ!

参考リンク

← ブログ一覧に戻る