Pythonの基礎を一通り終えて、そろそろデータを保存するプログラムを書いてみたい。そう思い始めた頃に立ちはだかるのが、データベースという壁です。
しかもSQLという別の言語まで覚えないといけない。そこで気持ちが折れてしまう方は、けっして少なくありません。
実は、SQLをほとんど書かずにデータベースを扱う方法があります。その代表格がSQLAlchemyというライブラリです。
この記事では、その正体と、最初の一本を動かすまでの流れをほどいていきます。
そもそもORMは、何を肩代わりしてくれるのか¶
SQLAlchemyの説明に入る前に、ORMという言葉を先に片付けておきましょう。ここが分かると、あとの話がすんなり入ります。
ORMはObject-Relational Mappingの略です。Pythonのクラスやオブジェクトと、データベースの表を対応づける仕組みを指します。
たとえばユーザー情報を保存する場面を思い浮かべてください。データベースにはusersという表があり、Pythonの世界にはUserというクラスがあります。
この2つを橋渡しして、Pythonのオブジェクトを保存すれば行が増え、行を読み込めばオブジェクトになる。そういう翻訳をしてくれるのがORMです。
SQL文を組み立てる作業を、Pythonの書き方のまま済ませられるのが最大の利点です。文字列でSQLを連結する必要がなくなり、打ち間違いも減ります。
その手前にあるsqlite3モジュールを触ったことがあるなら、比較しながら読むと違いがはっきりします。【関連記事】Pythonからデータベースを操作するsqlite3の使い方を解説
SQLAlchemyは、二階建ての道具箱¶
では本題です。SQLAlchemyは2006年から続くPythonのデータベースライブラリで、公式にはデータベース抽象化ライブラリと名乗っています。
特徴は、性格の違う2つの層が積み重なっていることです。ここを知らないまま入門記事を読むと、書き方が2種類あるように見えて混乱します。
Coreは、SQLをPythonの式で組み立てる層¶
下の層はCoreと呼ばれます。テーブルや列をそのまま扱い、SELECTやINSERTをPythonの式として組み立てる部分です。
SQLの構造はそのまま残るので、SQLを知っている人ほど手に馴染みます。接続の管理や、データベースごとの方言の吸収もここが担当します。
ORMは、テーブルをクラスとして扱う層¶
上の層がORMです。先ほどのUserクラスのような書き方ができるのは、この層のおかげになります。
初学者がまず触るのは、こちらで問題ありません。Coreの知識は、複雑な集計が必要になった時点で足せば十分です。
インストールと、いまのバージョン事情¶
説明を読むより、手元で動かしたほうが早いはずです。準備はコマンド1行で終わります。
python -m venv venv
source venv/bin/activate
pip install SQLAlchemy
執筆時点の安定版は2.0.52で、2026年8月11日に公開されました。Python 3.7以降で動き、3.15まで対応が明記されています。
次の世代である2.1系は、2026年8月31日に2.1.0rc1が出た段階です。こちらはPython 3.11以降が必要になります。
学習で使うなら安定版の2.0系で構いません。ただし調べものをするときは、注意してほしいことがあります。
いまの主流である2.0の書き方は、2023年1月にリリースされた際に大きく整理されました。ネット上には古い1.x時代の記事も多く残っています。
私は10年ほどエンジニアとして開発に関わってきましたが、この移行期には何度も足をすくわれました。検索で出てきたコードをそのまま貼って動かず、原因が書き方の世代差だったと気づくまでに半日溶かしたこともあります。
最初の一本を書いてみる¶
ここからは実際のコードです。SQLiteのファイルを使うので、データベースの用意はいりません。
from sqlalchemy import String, create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
email: Mapped[str]
engine = create_engine("sqlite:///sample.db", echo=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(name="yukari", email="yukari@example.com"))
session.commit()
stmt = select(User).where(User.name == "yukari")
for user in session.scalars(stmt):
print(user.id, user.name, user.email)
短いですが、これだけで表の作成から保存、検索まで一周しています。SQL文は一度も出てきません。
登場人物を整理しておきましょう。名前だけ覚えれば、公式ドキュメントもぐっと読みやすくなります。
| 名前 | 役割 | ざっくり言うと |
|---|---|---|
create_engine |
接続先の設定と接続プールの管理 | データベースへの入り口 |
DeclarativeBase |
表と結びつくクラスの土台 | モデルの親クラス |
mapped_column |
列の定義 | 表の1列ぶんの設定 |
Session |
変更の記録とまとめ書き | 作業机のような入れ物 |
select |
検索条件の組み立て | SELECT文の代わり |
Mapped[int]という型ヒントが列の型を決めている点にも注目してください。型ヒントが飾りではなく、実際の動作に効いている珍しい例です。
create_engineに渡している文字列も見ておきましょう。ここを差し替えるだけで、接続先のデータベースを変えられます。
たとえばPostgreSQLならpostgresql+psycopg://user:pass@localhost/mydb、MySQLならmysql+pymysql://で始まる形になります。学習中はSQLiteで書いておき、本番でPostgreSQLに切り替えるといった移行が、コードをほとんど変えずに済むわけです。
型ヒントそのものがまだ曖昧なら、先にここを押さえておくと読みやすさが変わります。【関連記事】Pythonの型ヒントとは?型ヒントの基礎を解説
Sessionという入れ物の考え方¶
初学者がいちばん戸惑うのがSessionです。ここだけは、少し丁寧に見ておきましょう。
Sessionは作業机のようなものだと考えてください。オブジェクトを机に置き、変更を加え、最後にまとめてデータベースへ書き込みます。
session.add()はまだ保存ではありません。机に載せただけで、実際に書き込まれるのはsession.commit()のタイミングです。
途中でエラーが起きたときは、session.rollback()で机ごとひっくり返せます。一連の処理をまとめて成功か失敗かに揃えられるのが、Sessionの本当の価値です。
先ほどのコードでwithを使っているのは、机の後片付けを確実にするためです。処理を抜けるときに接続が返却されます。
定番のつまずき、N+1問題¶
もうひとつ、実務で必ず出会う話をしておきます。ORMを使い始めた人が最初にぶつかる性能の落とし穴です。
関連する表を持つデータ、たとえばユーザーとその投稿を扱うとします。ユーザーを100件取ってきて、それぞれの投稿を順に見ていく処理を書いたとしましょう。
このとき裏側では、ユーザー取得の1回に加えて、投稿取得のSELECTが100回走ります。合計101回。これがN+1問題と呼ばれる現象です。
公式ドキュメントでも、ORMでもっともよく話題になる問題として名前が挙がっています。SQLが見えないぶん、気づきにくいのが厄介なところです。
対策は、関連データをまとめて先に読み込むことです。selectinloadを使うと、追加のSELECTが1回で済みます。
from sqlalchemy.orm import selectinload
stmt = select(User).options(selectinload(User.posts))
users = session.scalars(stmt).all()
私が過去に引き継いだ管理画面は、一覧を開くだけで数秒待たされる状態でした。原因はまさにこれで、ログを流して初めてSELECTが数百回走っていると分かった経験があります。
echo=Trueを付けると実行されたSQLがすべて表示されます。遅いと感じたら、まずここを疑ってください。
WebフレームワークとSQLAlchemy¶
多くの人がSQLAlchemyに出会うのは、Webアプリを作り始めたときです。組み合わせ方には、だいたいの型があります。
FlaskではFlask-SQLAlchemyという拡張が定番で、設定やSessionの管理をアプリに合わせて面倒みてくれます。FastAPIでは、依存性注入の仕組みでSessionを受け取る書き方がよく使われます。
一方でDjangoは、独自のORMを最初から内蔵しています。そのためDjangoを使う場合、SQLAlchemyを持ち込む場面はほとんどありません。
この違いは、フレームワークの性格そのものの違いでもあります。【関連記事】PythonのDjangoとは?管理画面まで最初から付いてくるWebフレームワークをFlaskと比べて初心者向けに解説
もうひとつ、忘れがちな話があります。クラスの定義を変えても、既にあるデータベースの表は自動では変わりません。
列を追加したいときは、マイグレーションという手順が必要になります。SQLAlchemyの世界ではAlembicという道具がその役目を担います。
マイグレーションの考え方はフレームワークが違っても共通なので、先に読んでおくと理解が早いはずです。【関連記事】マイグレーションってどういう意味?flask db migrateで使う時に理解できるように解説
それでもSQLを学ぶ必要はあるのか¶
最後に、よく聞かれる疑問に答えておきます。ORMがあればSQLは不要なのか、という問いです。
結論から言うと、基本は知っておいたほうが得です。理由は単純で、遅い原因を調べるときにSQLを読むことになるからです。
とはいえ、順番は逆でも構いません。まずSQLAlchemyで動くものを作り、詰まったところでSQLを覗く。そのほうが、学ぶ理由がはっきりしていて続きます。
学習の入口としては、小さなツールを1つ完成させるのがおすすめです。データを保存して一覧で出す、それだけでも仕組みは体に入ります。
Web APIの形で作ってみたい方は、こちらから始めると流れがつかみやすいでしょう。【関連記事】PythonのFastAPIとは?型ヒントだけで動くWeb APIの作り方をFlaskと比べながら初心者向けに解説
SQLAlchemyは機能が多く、全体を見ると圧倒されます。ですが入口は、engineとSessionとselectの3つだけです。
まずはこの記事のコードをそのまま動かしてみてください。ファイルが1つできて、中身が読み出せた瞬間に、データベースはぐっと身近になります。
ここまでお読みいただきありがとうございました。