SQLAlchemy==1.4.37
N+1のlazy fetch
をしたくない場合、eager load
というJOIN時に予めデータを持ってくる方式があります。SQLAlchemyでも、Railsでも、この辺は一緒。Alchemyの場合、孫のテーブルをJOINした時もそれらが出来る。
数時間苦戦したので、Eager Loadingの設定をメモる。
filterする対象のテーブルが自分だけ
この場合は、joinedload
でいけた。
session.query(UserTable)
.options(
joinedload(UserTable.attention),
joinedload(UserTable.friend_type),
joinedload(UserTable.bookmark),
)
.filter(UserTable.user_id == user_id)
UserTableを軸に、Where句で引いてくるのがUserTableのカラムのみ。
filterする対象のテーブルが複数ある
この場合は、以下のようなjoinedload
を使うと、えらいことになった。
session.query(UserTable)
.options(
joinedload(UserTable.attention),
joinedload(UserTable.friend_type),
joinedload(UserTable.bookmark),
)
.filter(UserTable.user_id != user_id)
.filter(UserFriendTypeTable.friend_type.in_(["AAA","BBB"])
えらいことになるというのは、filterで指定したテーブルが OUTER JOINではなくCROSS JOINされていたため。FROM user_table, user_friend_type_table というSQLになるので、激重になった。
複雑な検索条件に基づいてフェッチする場合は、join
とcontains_eager
の合わせ技が良いみたい。
session.query(UserTable)
.outerjoin(UserBookmarkTable, UserBookmarkTable.user_id == UserTable.id)
.join(UserFriendTypeTable, UserFriendTypeTable.user_id == UserTable.id)
.options(
contains_eager(UserTable.bookmark),
contains_eager(UserTable.friend_type),
)
.filter(UserTable.user_id != user_id)
.filter(UserFriendTypeTable.friend_type.in_(["AAA","BBB"])
自分はいつもwith_entities
でカラムを指定してSELECT文を投げることが多かった。一覧画面に表示するためのSQLが多いので、JOIN先がN件でも親に合わせるためにカラムを指定してた。ただ、今回は、要件が特殊でカラムを指定するのではなく、N+1せずに結合先のレコードをリストで保持して置く必要があったので、苦戦したのだった。
これでだいぶRelationshipには慣れてきたな。ドキュメント、じっくり読もう。どこかで。