SQLAlchemy Tips¶
SQLAlchemyとは¶
SQLAlchemyはPythonで使用されるオープンソースのSQLツールキットおよびオブジェクトリレーショナルマッピング(ORM)ライブラリです。データベース操作を効率化し、オブジェクト指向プログラミングとリレーショナルデータベースを橋渡しする役割を果たします。2006年にリリースされ、現在も広く利用されています [2] [9] [16]。
主な特徴¶
ORM(Object Relational Mapping)
SQLAlchemyは、テーブルとPythonクラスを1対1で対応させ、SQL文を直接記述することなくデータベースを操作できます。これにより、開発者はPythonのオブジェクト指向的な方法でデータベース操作が可能になります [6] [16] [20]。柔軟性と拡張性
MySQL、PostgreSQL、SQLite、Oracleなど、多くのデータベースエンジンに対応しており、異なるデータベース間の違いを吸収する抽象化レイヤーを提供します。また、開発段階ではSQLite、本番環境ではMySQLやPostgreSQLなどに切り替えることも容易です [13] [16].データマッパーパターン
SQLAlchemyはデータマッパーパターンを採用し、高度なクエリやトランザクションの管理も可能です。他のORMライブラリが採用するアクティブレコードパターンよりも柔軟性が高い設計です [9] [7].
利点¶
注意点¶
適用例¶
大規模アプリケーション開発(例: Yelp, Reddit, Dropboxなどで利用実績あり)
リレーショナルデータベースを使用するWebアプリケーション
SQLAlchemyはその柔軟性と強力な機能から、多様なプロジェクトで活用されています。Pythonプログラマーにとって非常に有用なツールと言えるでしょう。
Citations:
公式ドキュメント¶
Tips¶
宣言ベースでのテーブル定義¶
SQLAlchemy 2.0からは、宣言ベースでのテーブル定義が推奨されている。
from datetime import datetime
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy.orm import DeclarativeBase
# declarative base class
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
name: Mapped[str]
fullname: Mapped[Optional[str]]
nickname: Mapped[Optional[str]] = mapped_column(String(64))
create_date: Mapped[datetime] = mapped_column(insert_default=func.now())
addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
__tablename__ = "address"
id = mapped_column(Integer, primary_key=True)
user_id = mapped_column(ForeignKey("user.id"))
email_address: Mapped[str]
user: Mapped["User"] = relationship(back_populates="addresses")
参考: Declarative Mapping Styles — SQLAlchemy 2.0 Documentation
Mapped[]はテーブルのカラムのPythonの型とSQLAlchemyの型をマッピングするためのクラスです。
mapped_column()は、具体的にSQLでのテーブル定義を行う関数です。インデックスの有無指定や、デフォルト値の指定などができます。
カラムの定義に関しては以下も参考にしてください。
参考: Table Configuration with Declarative — SQLAlchemy 2.0 Documentation
テーブルのリレーションシップ¶
参考: Relationship Configuration — SQLAlchemy 2.0 Documentation
1:1のリレーションシップの場合
class Parent(Base):
__tablename__ = "parent_table"
id: Mapped[int] = mapped_column(primary_key=True)
children: Mapped[List["Child"]] = relationship(back_populates="parent")
class Child(Base):
__tablename__ = "child_table"
id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
parent: Mapped["Parent"] = relationship(back_populates="children")
1:nのリレーションシップの場合
class Parent(Base):
__tablename__ = "parent_table"
id: Mapped[int] = mapped_column(primary_key=True)
children: Mapped[List["Child"]] = relationship(back_populates="parent")
class Child(Base):
__tablename__ = "child_table"
id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
parent: Mapped["Parent"] = relationship(back_populates="children")
n:1のリレーションシップの場合
from typing import Optional
class Parent(Base):
__tablename__ = "parent_table"
id: Mapped[int] = mapped_column(primary_key=True)
child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id"))
child: Mapped[Optional["Child"]] = relationship(back_populates="parents")
class Child(Base):
__tablename__ = "child_table"
id: Mapped[int] = mapped_column(primary_key=True)
parents: Mapped[List["Parent"]] = relationship(back_populates="child")
n:nのリレーションシップの場合
from __future__ import annotations
from sqlalchemy import Column
from sqlalchemy import Table
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
# note for a Core table, we use the sqlalchemy.Column construct,
# not sqlalchemy.orm.mapped_column
association_table = Table(
"association_table",
Base.metadata,
Column("left_id", ForeignKey("left_table.id")),
Column("right_id", ForeignKey("right_table.id")),
)
class Parent(Base):
__tablename__ = "left_table"
id: Mapped[int] = mapped_column(primary_key=True)
children: Mapped[List[Child]] = relationship(secondary=association_table)
class Child(Base):
__tablename__ = "right_table"
id: Mapped[int] = mapped_column(primary_key=True)
リレーションに関連する事項としてカスケードが有ります。
参考: Cascades — SQLAlchemy 2.0 Documentation
カスケードの指示はrelationship()
の引数にcascade
を指定することで行えます。
参考: Relationships API — SQLAlchemy 2.0 Documentation
以下は、カスケードの機能を使って、親テーブルのデータを削除すると子テーブルのデータも削除される例です。
class Profile(Base):
"""ユーザープロフィール"""
__tablename__ = "user_profiles"
id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(unique=True, index=True)
date_of_birth: Mapped[Optional[datetime]]
gender: Mapped[Optional[str]]
occupation: Mapped[Optional[str]]
interests: Mapped[list["Interest"]] = relationship(
cascade="all, delete", order_by="Interest.id"
)
class Interest(Base):
"""ユーザー興味事項"""
__tablename__ = "user_interests"
id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
interest: Mapped[str]
user_profile_id: Mapped[int] = mapped_column(ForeignKey("user_profiles.id"))
profile: Mapped["Profile"] = relationship(back_populates="interests")
リレーションシップを結んだテーブルのデータを取得する場合のサンプルコードです。
session = Session(engine)
stmt = (
select(Profile)
.where(Profile.user_id == user_id)
.options(joinedload(Profile.interests))
)
selected_profile = session.scalar(stmt)
joinedload()
はリレーションシップを結んだテーブルのデータを取得する際に使用します。joinedload()
は親子のデータをいっぺんにメモリ上にデータを取得するので、メモリの使用量やDBアクセスのパフォーマンスには注意が必要です。