SQLAlchemyで始めるPythonのデータベース操作 - ORMの基礎を学ぶ
Pythonでアプリケーションを作っていると、データを保存する必要が出てきます。そんな時に役立つのがデータベース。でも、SQL文を書くのは大変...そこで登場するのがSQLAlchemyです。今回は、Pythonでデータベースをもっと簡単に扱えるようになるSQLAlchemyの基本を、実際に動かしながら学んでいきましょう!
来春からデータサイエンティストとして働く予定の技術オタク。
『知りたい』気持ちで質問を止められない、好奇心旺盛な学生。
なぜSQLAlchemyなのか?
ねえねえ、SQLAlchemyって何?データベースを使うのに必要なの?
SQLAlchemyは、Pythonでデータベースをもっと簡単に扱えるようにするライブラリなんだ。正式には「ORM」と呼ばれる種類のツールなんだよ。
ORM?なにそれ...
ORMは「Object-Relational Mapping」の略で、「オブジェクトとデータベースを紐付ける」という意味なんだ。普通、データベースを使う時はSQL文という専用の言語で操作するんだけど、ORMを使うとPythonのコードだけでデータベースを扱えるようになるんだよ。
SQL文って何?
例えば、ユーザー情報をデータベースから取得する時、通常はこんな文を書くんだ。
SELECT * FROM users WHERE age >= 25;
これがSQL文。でもSQLAlchemyを使うと、同じことをPythonっぽく書けるんだよ。
users = session.query(User).filter(User.age >= 25).all()
どう?Pythonのコードっぽいでしょ?
本当だ!Pythonで書けるんだ!
そうなんだ。SQLAlchemyを使うメリットは主に3つあるよ。
- Pythonで完結: SQL文を覚えなくても、Pythonの知識だけでデータベース操作ができる
- タイプセーフ: IDEの補完が効くから、タイプミスを防げる
- データベースの切り替えが簡単: SQLite、PostgreSQL、MySQLなど、設定を変えるだけで別のデータベースに切り替えられる
便利そう!早く使ってみたい!
SQLAlchemyの3つの柱
SQLAlchemyって、どういう仕組みになってるの?
SQLAlchemyは、主に3つの重要な要素で構成されてるんだ。Engine(エンジン)、Base(ベース)、Session(セッション) の3つだよ。
3つ...覚えられるかな...
大丈夫、それぞれの役割を理解すれば簡単だよ。
まず Engine(エンジン) は、データベースへの接続を管理する部分。車のエンジンみたいに、データベースとの通信を動かす役割なんだ。
次に Base(ベース) は、テーブルの設計図を作るための基礎クラス。これを使って「ユーザーテーブル」とか「商品テーブル」とかを定義するんだよ。
最後に Session(セッション) は、データベースとの実際のやり取りを管理する部分。データの追加、検索、更新、削除といった操作は、すべてSessionを通して行うんだ。
エンジンが接続、ベースが設計図、セッションが操作、ってことか
そうそう!いい感じに理解できてるね。この3つが揃って、初めてSQLAlchemyが使えるようになるんだ。
環境構築とインストール
じゃあ、実際に使ってみたい!どうやってインストールするの?
SQLAlchemyは、pipで簡単にインストールできるよ。
pip install sqlalchemy
これだけでOK。
え、それだけ!?
そう!SQLAlchemyだけインストールすれば、すぐ使い始められるんだ。今回は SQLite というデータベースを使うんだけど、これはPythonに最初から入ってるから、追加でインストールする必要がないんだよ。
SQLiteって何?PostgreSQLとかMySQLとかじゃないの?
いい質問だね。SQLiteは、ファイルベースの軽量なデータベースなんだ。PostgreSQLやMySQLは別途サーバーを立てる必要があるけど、SQLiteはファイル一つで完結するから、学習や小規模アプリに最適なんだよ。
そして、SQLAlchemyのいいところは、最初SQLiteで開発して、後からPostgreSQLに切り替えるのも簡単ってこと。コードはほとんど変えなくていいんだ。
なるほど!じゃあまずはSQLiteで練習するのがいいんだね
最初のテーブルを作ってみよう
早速コード書いてみたい!
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()
これで準備完了!
えーと、いろいろ出てきた...順番に教えて!
OK、一つずつ見ていこう。
1. エンジンの作成
engine = create_engine("sqlite:///users.db", echo=True)
これでusers.dbというファイルにデータが保存されるようになる。echo=Trueは、実行されるSQL文を表示するオプションで、学習中は便利なんだ。
sqlite:///の部分は?
これは「データベースURL」と呼ばれるもので、どのデータベースを使うか指定してるんだ。sqlite:///はSQLiteを使う時の書き方。PostgreSQLならpostgresql://になるよ。
なるほど!
2. Baseクラスの作成
Base = declarative_base()
これは、すべてのモデルクラスの親になるクラス。これから作るテーブルは、全部このBaseを継承するんだ。
継承?
Pythonのクラスの仕組みだね。class User(Base):って書くことで、UserクラスがBaseの機能を受け継ぐんだ。これによって、SQLAlchemyがUserをテーブルとして認識できるようになるんだよ。
あー、クラスの継承か!
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)
各カラム(列)を定義してるんだ。
primary_keyとかnullableって何?
いい質問だね!
primary_key=True: このカラムが主キー(各行を識別するためのユニークなID)autoincrement=True: 自動で番号を振ってくれる(1, 2, 3...)nullable=False: 空(NULL)を許可しない、つまり必須項目unique=True: 重複を許さない(メールアドレスが被ったらダメ)
ageには何も指定してないから、省略してもOKなんだ。
わかった!idは自動で番号が振られて、nameとemailは必須、emailは重複NG、ageは任意ってことだね
完璧!そして__repr__メソッドは、オブジェクトをprint()した時に見やすく表示するためのものなんだ。
4. テーブルの作成
Base.metadata.create_all(engine)
これで、定義したモデルに基づいて、実際にデータベースにテーブルが作成されるんだ。
これを実行すると、本当にテーブルができるの?
そう!実行するとusers.dbというファイルができて、その中にusersテーブルが作られるんだよ。
すごい!Pythonのコードだけでテーブルが作れるんだ!
5. Sessionの作成
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
最後に、データベース操作のためのSessionを作成。これで、データの追加や検索ができるようになるんだ。
CRUD操作の基本
テーブルができたら、次は何するの?
次は、データの操作だね。CRUD操作って聞いたことある?
CRUD?なにそれ
CRUDは、データベース操作の4つの基本を表す略語なんだ。
- Create: データの追加
- Read: データの取得
- Update: データの更新
- Delete: データの削除
この4つができれば、基本的なデータベース操作は全部できるようになるんだよ。
なるほど!じゃあ一つずつやってみよう!
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人のユーザーがデータベースに保存されたよ。
add()とcommit()って何が違うの?
いい質問だね!add()は「予約」みたいなもので、commit()で初めて「確定」されるんだ。
例えば、10人のユーザーを追加したいとき、10回add()してから1回commit()すればいい。こうすることで、途中でエラーが起きても、全部まとめてキャンセルできるんだよ。これを「トランザクション」って呼ぶんだ。
なるほど!まとめて予約して、最後に一気に確定するんだね
そういうこと!ちなみに、複数のオブジェクトを一度に追加するなら、add_all()も使えるよ。
session.add_all([user1, user2, user3])
session.commit()
Read: データの取得
次は、保存したデータを取り出してみたい!
データの取得は、query()メソッドを使うんだ。いくつかパターンを見てみよう。
# すべてのユーザーを取得
users = session.query(User).all()
print(users)
# [<User(id=1, name='田中太郎')>, <User(id=2, name='佐藤花子')>, ...]
all()は、条件に合うすべてのレコードをリストで返すんだ。
特定のユーザーだけ取得したい時は?
条件を指定するには、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件だけを返すメソッドなんだ。
filter_by()とfilter()の違いは?
いいところに気づいたね!
filter_by(age=28): イコール条件の時に使う、シンプルな書き方filter(User.age >= 25): 比較演算子が使える、柔軟な書き方
イコールだけならfilter_by()、大小比較や複雑な条件ならfilter()って覚えておくといいよ。
わかった!使い分けるんだね
他にも、並び替えや件数制限もできるよ。
# 年齢順に並び替え(昇順)
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()
いろんな取得方法があるんだね!
Update: データの更新
データを変更したい時はどうするの?
取得したオブジェクトの属性を変更して、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}")
え、属性を変更してcommitするだけ!?
そう!SQLAlchemyがオブジェクトの変更を追跡してくれるから、変更があった部分だけデータベースに反映してくれるんだ。これがORMの便利なところなんだよ。
めっちゃ簡単!
Delete: データの削除
最後に、削除の仕方を教えて!
削除は、delete()メソッドを使うんだ。
# 佐藤花子を検索
user = session.query(User).filter_by(name="佐藤花子").first()
# 削除
session.delete(user)
# データベースに反映
session.commit()
# 削除されたか確認
users = session.query(User).all()
print(f"残りのユーザー数: {len(users)}")
これもcommit()が必要なんだね
そう!追加、更新、削除、すべてcommit()するまでは確定されないんだ。これによって、間違って削除しちゃった時も、commit()する前ならrollback()でキャンセルできるんだよ。
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()で全部なかったことにできるんだ。
それは安心だね!間違えても大丈夫!
クエリの書き方を深掘り
もうちょっとクエリの書き方について教えて!複雑な条件も書けるの?
もちろん!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()
startswith()とかlike()とか、いろいろあるんだね
そう!SQLAlchemyのカラムには、便利なメソッドがたくさん用意されてるんだ。
startswith("田中"): 「田中」で始まるendswith(".com"): 「.com」で終わるcontains("example"): 「example」を含むlike("%pattern%"): SQL のLIKE文in_([1, 2, 3]): リストのいずれかに一致is_(None): NULLかどうか
覚えきれないけど、必要な時に調べればいいよね!
そうそう!最初は基本的なfilter_by()とfilter()だけ覚えておけば十分。必要になったら調べればいいんだ。
実践例: ユーザー管理システム
じゃあ、全部まとめて実際に動かしてみたい!
いいね!じゃあ、簡単なユーザー管理システムを作ってみよう。
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}さんが見つかりました")
おお!関数にまとめるとすっきりするね
そうなんだ。実際のアプリケーションでは、こうやって関数やクラスにまとめて使うことが多いんだよ。
ただ、一つ注意点があって、関数ごとにsession.close()を呼んでるでしょ?これ、忘れるとメモリリークの原因になるから注意が必要なんだ。
メモリリーク?
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()を忘れる心配がないんだ。
なるほど!自動で閉じてくれるんだね
よくあるエラーと対処法
使ってると、エラーが出ることもあるよね?
そうだね。よくあるエラーをいくつか紹介しておこう。
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のカラムで値が重複した時のエラー。メールアドレスが既に使われてる時に起きるんだ。
どうやって防げばいいの?
追加する前に、既に存在するかチェックするといいよ。
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()
なるほど、先にチェックするんだね
2. DetachedInstanceError
これは、Sessionを閉じた後にオブジェクトにアクセスしようとした時のエラーなんだ。
session = SessionLocal()
user = session.query(User).first()
session.close()
print(user.name) # OK(既に読み込まれてる)
print(user.posts) # エラー!(リレーションシップはまだ読み込まれてない)
必要なデータは、Sessionを閉じる前に全部読み込んでおく必要があるんだよ。
難しそう...
大丈夫、最初は基本的なCRUD操作だけ覚えれば十分。リレーションシップは応用編で詳しく説明するよ。
まとめ
今回学んだことをまとめてみよう。
- SQLAlchemy: PythonでデータベースをPythonっぽく扱えるORMライブラリ
- 3つの柱: Engine(接続)、Base(設計図)、Session(操作)
- CRUD操作: Create(add)、Read(query)、Update(属性変更)、Delete(delete)
- commit(): 変更を確定する、忘れずに!
- close(): Sessionを閉じる、メモリリーク防止
これで基本的なデータベース操作ができるようになったよ。
SQLAlchemy、思ったより簡単だった!
そうでしょ?SQLを覚えなくても、Pythonの知識だけでデータベースが使えるのがSQLAlchemyの魅力なんだ。
次の応用編では、もっと実務的な内容を学ぶよ。複数のテーブルを関連付ける「リレーションシップ」や、パフォーマンスを改善する「N+1問題の解決」なんかを扱う予定。楽しみにしててね!
うん!次も楽しみ!