🐍
SQLModelで親と一緒に子テーブルを削除する(cascade_delete, ondelete)
始めに
外部キー制約があるレコードを削除するとき、参照元テーブルよりも参照先テーブルを先に削除する必要があります。
Railsの場合、次のようにdependent
に定義しておくと、Parent
テーブルを削除したタイミングでChild
テーブルも削除されます。
class Parent < ApplicationRecord
has_one :child, dependent: :destroy
end
class Child < ApplicationRecord
belongs_to :parent
end
今回の記事では、PythonのSQLModelを使用したときに、Parent
テーブルを削除したタイミングでChild
テーブルも削除されるようにします。
環境
- Python
- 3.12.4
- FastAPI
- 0.112.1
- SQLModel
- 0.0.21
実装
Relationship
のcascade_delete
属性を付与するだけです。また、外部キー制約がないDBを使用していた際に、DBで直接削除された時用にField
のondelete
属性を付与しておくとより安全に処理できます。
from sqlmodel import SQLModel, Field, Relationship
class Parent(SQLModel, table=True):
__tablename__ = "parents"
id: int = Field(primary_key=True)
child: "Child" = Relationship(back_populates="parent", cascade_delete=True, sa_relationship_kwargs={"uselist": False})
class Child(SQLModel, table=True):
__tablename__ = "childs"
id: int = Field(foreign_key="parents.id", primary_key=True, ondelete="CASCADE")
parent: "Parent" = Relationship(back_populates="child")
こちらを定義するだけで、Parent
のテーブルのモデルを削除したタイミングで一緒に削除してくれます。
async def test_01(self, db: AsyncSession) -> None:
parent = Parent(id=1)
db.add(task1)
child = Child(id=1)
db.add(done1)
await db.commit()
# WHEN
print("XXXXXX")
await db.delete(parent)
await db.commit()
print("XXXXXX")
# THEN
query: select = select(Child)
actual = (await db.execute(query)).scalars().all()
assert len(actual) == 0
ログを見ればParent
を削除したタイミングでChild
もforeign_keyをもとに検索をかけていることがわかり、Child
, Parent
の順番で削除されていることがわかります。
2024-08-25 21:52:39,968 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-08-25 21:52:39,970 INFO sqlalchemy.engine.Engine SELECT parents.id
FROM parents
WHERE parents.id = ?
2024-08-25 21:52:39,970 INFO sqlalchemy.engine.Engine [generated in 0.00021s] (1,)
2024-08-25 21:52:39,971 INFO sqlalchemy.engine.Engine SELECT childs.id FROM childs WHERE childs.id = ?
2024-08-25 21:52:39,971 INFO sqlalchemy.engine.Engine [generated in 0.00016s] (1,)
2024-08-25 21:52:39,973 INFO sqlalchemy.engine.Engine DELETE FROM childs WHERE childs.id = ?
2024-08-25 21:52:39,973 INFO sqlalchemy.engine.Engine [generated in 0.00017s] (1,)
2024-08-25 21:52:39,974 INFO sqlalchemy.engine.Engine DELETE FROM parents WHERE parents.id = ?
2024-08-25 21:52:39,974 INFO sqlalchemy.engine.Engine [generated in 0.00014s] (1,)
2024-08-25 21:52:39,975 INFO sqlalchemy.engine.Engine COMMIT
ソースコード
終わりに
こういう処理ができると直接SQLを発行しないでORMを経由するメリットがありますね。また、SQLAlchemy
ではできなかったDB
側で直接削除された時用のondelete
オプションがあるのも面白いです。
こういう細かい部分を知っていくのは面白いですね。
Discussion