Skip to content

SQLModel

SQLModel は FastAPI の作者が作ってるライブラリなので、 FastAPI で DB を使うときはこれにしておけばコードを書く量も減らせるし、エディターのエラーサインもよく出してくれるはず。 FastAPI と同じでドキュメントも読みやすい。

SQLModel の Learn を読んでいって、 SQLModel で DB を扱うときにどう書けばいいのか、型のようなもの(お決まりの書き方)を見つけたい。ここではデータベースとして SQLite を使う。


DB とテーブルの作成

cf. Create the Database and Table

# app.py
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    mail: str
    age: Optional[int] = None

sqlite_file_name = "database.db"               # 使用するDBファイル名
sqlite_url = f"sqlite:///{sqlite_file_name}"   # sqliteで接続するよう指定

engine = create_engine(sqlite_url, echo=True)  # この"engine"を使ってDBに接続できる; 本番で動かすときは"echo=True"は削除する

SQLModel.metadata.create_all(engine)           # これでDBとテーブルが作成される; 2回目以降はDBとテーブルが同じ名前で存在しているので何もしない

SQLModel クラスを定義してテーブルを作る。上のコードだと User クラスの定義のとおり、下の 4 つがテーブルのカラムになる。

  • id — 数値型もしくは None の値を持つ。 default=None かつ primary_key=True としておけば、 Python コードで id を指定せずに DB に入れたときに自動で採番される。
  • name — 文字列型の値を持つ。
  • mail — 文字列型の値を持つ。
  • age — 数値型もしくは None の値を持つ。

DB を作るときは、 sqlite_url を指定 → create_engineSQLModel.metadata.create_all の順序で、このまま覚えておけばいい。


ユーザーの作成(Create)

cf. Create a Model Instance

User クラスの user_1 ~ user_3 インスタンスを作成して、 with Session(engine) as session の中で session.add する。

# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    mail: str
    age: Optional[int] = None

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

def create_users():
    user_1 = User(name="Alice", mail="alice@example.com")
    user_2 = User(name="Bob", mail="bob@example.com", age=44)
    user_3 = User(name="Ken", mail="ken@example.com", age=25)

    with Session(engine) as session:
        session.add(user_1)
        session.add(user_2)
        session.add(user_3)
        session.commit()

def main():
    create_users()

if __name__ == "__main__":
    main()

コードを実行するとデータが入る。 id は Python コードでは指定してないけど、 DB が勝手に 1, 2, 3 と付けてくれてる。

実行ログ
% uv run python app.py
2025-10-06 15:07:15,482 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 15:07:15,482 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("user")
2025-10-06 15:07:15,483 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-10-06 15:07:15,484 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("user")
2025-10-06 15:07:15,484 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-10-06 15:07:15,484 INFO sqlalchemy.engine.Engine
CREATE TABLE user (
        id INTEGER NOT NULL,
        name VARCHAR NOT NULL,
        mail VARCHAR NOT NULL,
        age INTEGER,
        PRIMARY KEY (id)
)


2025-10-06 15:07:15,485 INFO sqlalchemy.engine.Engine [no key 0.00004s] ()
2025-10-06 15:07:15,485 INFO sqlalchemy.engine.Engine COMMIT

create_user の実行結果


ユーザーの読み取り(Read)

cf. Read Data with SQLModel

上の図のようなテーブルから Bob のデータだけを読み取る。

# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    mail: str
    age: Optional[int] = None

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

def select_users():
    with Session(engine) as session:
        statement = select(User).where(User.name == "Bob")
        results = session.exec(statement)
        user = results.first()
        print(user)

def main():
    select_users()

if __name__ == "__main__":
    main()

コードを実行すると、 Bob のデータだけを取得できる。

実行ログ
% uv run python app.py
2025-10-06 15:29:34,458 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 15:29:34,460 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 15:29:34,460 INFO sqlalchemy.engine.Engine [generated in 0.00010s] ('Bob',)
mail='bob@example.com' name='Bob' id=2 age=44
2025-10-06 15:29:34,461 INFO sqlalchemy.engine.Engine ROLLBACK

1 つのデータだけを取得するときの first()one() の違いは下記: cf. Exactly One

条件にマッチ results.first() results.one()
1 件マッチ そのデータを返す そのデータを返す
複数マッチ 最初の 1 件だけ返す エラー
マッチなし None を返す エラー

ユーザーの更新(Update)

cf. Read from the Database

# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    mail: str
    age: Optional[int] = None

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

def update_users():
    with Session(engine) as session:
        statement = select(User).where(User.name == "Alice")
        results = session.exec(statement)
        user = results.one()
        print("User:", user)

        user.age = 16
        user.name = "Alice-Wonder"
        session.add(user)

        session.commit()
        session.refresh(user)

        print("Updated User:", user)

def main():
    update_users()

if __name__ == "__main__":
    main()

コードを実行すると、 Alice の情報を変更できる。

実行ログ
% uv run python app.py
2025-10-06 16:08:13,643 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:08:13,645 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 16:08:13,645 INFO sqlalchemy.engine.Engine [generated in 0.00010s] ('Alice',)
User: mail='alice@example.com' name='Alice' id=1 age=None
2025-10-06 16:08:13,646 INFO sqlalchemy.engine.Engine UPDATE user SET name=?, age=? WHERE user.id = ?
2025-10-06 16:08:13,646 INFO sqlalchemy.engine.Engine [generated in 0.00007s] ('Alice-Wonder', 16, 1)
2025-10-06 16:08:13,647 INFO sqlalchemy.engine.Engine COMMIT
2025-10-06 16:08:13,648 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:08:13,648 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.id = ?
2025-10-06 16:08:13,648 INFO sqlalchemy.engine.Engine [generated in 0.00006s] (1,)
Updated User: mail='alice@example.com' name='Alice-Wonder' id=1 age=16
2025-10-06 16:08:13,649 INFO sqlalchemy.engine.Engine ROLLBACK

update_user の実行結果


ユーザーの削除(Delete)

cf. Delete the Hero from the Session

# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    mail: str
    age: Optional[int] = None

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

def delete_users():
    with Session(engine) as session:
        statement = select(User).where(User.name == "Alice-Wonder")
        results = session.exec(statement)
        user = results.one()
        print("User: ", user)

        session.delete(user)
        session.commit()

        print("Deleted User:", user)

        statement = select(User).where(User.name == "Alice-Wonder")
        results = session.exec(statement)
        user = results.first()

        if user is None:
            print("There's no User named Alice-Wonder")

def main():
    delete_users()

if __name__ == "__main__":
    main()

コードを実行すると、 Alice-Wonder のデータは削除できる。

実行ログ
% uv run python app.py
2025-10-06 16:14:29,484 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:14:29,485 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 16:14:29,486 INFO sqlalchemy.engine.Engine [generated in 0.00010s] ('Alice-Wonder',)
User:  mail='alice@example.com' name='Alice-Wonder' id=1 age=16
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine DELETE FROM user WHERE user.id = ?
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine [generated in 0.00007s] (1,)
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine COMMIT
Deleted User: mail='alice@example.com' name='Alice-Wonder' id=1 age=16
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 16:14:29,490 INFO sqlalchemy.engine.Engine [cached since 0.004089s ago] ('Alice-Wonder',)
There's no User named Alice-Wonder
2025-10-06 16:14:29,490 INFO sqlalchemy.engine.Engine ROLLBACK

delete_user の実行結果


インデックス(Indexes)

cf. Indexes

読み込みのパフォーマンスを改善するために、インデックスを追加する方法がある。検索されるカラムに対してインデックスをつけるべき。 primary_key は自動でインデックスが作られる。

例えば User クラスを修正する場合は、下記のように index=True を追加するだけ。

from typing import Optional
from sqlmodel import Field, SQLModel

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    mail: str
    age: int | None = Field(default=None, index=True)