SQLAlchemyで1件取得の処理を書いていたとき、次のエラーに遭遇しました。
AttributeError:
'ScalarResult' object has no attribute
'scalar_one_or_none'
原因は、ResultとScalarResultで使えるメソッドを混同していたことでした。
execute()はResultを返す
AsyncSession.execute()を使うと、
result = await session.execute(
select(User)
)
Resultが返ります。
Resultは基本的にRowを扱う結果オブジェクトです。
ORMモデルを1件だけ取り出したい場合は、
user = result.scalar_one_or_none()
と書けます。
scalar_one_or_none()は、
0件 → None
1件 → Userオブジェクト
複数件 → MultipleResultsFound
という動作になります。
scalars()するとScalarResultになる
一方、
result = await session.scalars(
select(User)
)
では、返ってくるのはScalarResultです。
ScalarResultは、Rowではなくその中の単一要素を直接扱うための結果オブジェクトです。
そのため1件取得では、
user = result.one_or_none()
と書きます。
ここで、
result.scalar_one_or_none()
とすると、ScalarResultにはそのメソッドがないためエラーになります。
なぜ名前が違うのか?
ResultからORMオブジェクトを取り出すには、まずscalar値へ変換する必要があります。
実際、
result.scalar_one_or_none()
は、SQLAlchemy公式では次と同等の処理として説明されています。
result.scalars().one_or_none()
つまり、
Result
↓ scalars()
ScalarResult
↓ one_or_none()
User
という流れです。
すでにsession.scalars()を使っている場合は、最初からScalarResultになっているので、もう一度scalarへ変換する必要はありません。
どちらで書けばよい?
ORMモデルを取得するだけなら、
stmt = select(User).where(
User.id == user_id
)
user = (
await session.scalars(stmt)
).one_or_none()
と書くとシンプルです。
一方、
result = await session.execute(stmt)
user = result.scalar_one_or_none()
でも同じ目的を実現できます。
まずは次の対応を覚えておくと分かりやすいです。
| 取得方法 | 結果 | 1件または0件 |
|---|---|---|
execute() |
Result |
scalar_one_or_none() |
scalars() |
ScalarResult |
one_or_none() |
まとめ
-
execute()はResultを返す -
scalars()はScalarResultを返す -
Result.scalar_one_or_none()はscalar値を0件または1件取得する -
ScalarResultではone_or_none()を使う -
scalar_one_or_none()はscalars().one_or_none()と同等
メソッド名だけ暗記するより、今扱っているオブジェクトがResultなのかScalarResultなのかを見ると、エラーの原因を判断しやすくなります。