◀ 9.

get() returned more than one ... -- it returned ...!

▶
この記事の要点
  • Django ORM の Model.objects.get(...) で条件に合うレコードが2 件以上あったときの例外(MultipleObjectsReturned)
  • get は「必ず 1 件だけ」取る前提のメソッド。0 件なら DoesNotExist、2 件以上ならこのエラー
  • 対処 1: 主キーやユニークな項目で条件を絞る
  • 対処 2: 複数件が正しいなら filter()、1 件だけ欲しいなら filter(...).first()
  • 本来 1 件のはずのデータが重複しているなら、重複を調べて UniqueConstraint で再発を防ぐ

エラー内容

get() returned more than one MynumberRegist -- it returned 2!

「MynumberRegist」の部分にはモデル名、「2」の部分には見つかった件数が入ります。例外クラスは MynumberRegist.MultipleObjectsReturned(共通の親は django.core.exceptions.MultipleObjectsReturned)です。件数が多い場合は「it returned more than 20!」のように上限付きで表示されます。

発生条件と原因

以下のように get を使用して、条件に合うレコードが複数存在すると発生するエラーです。

Users.objects.get(...)

get は「条件に合うレコードがちょうど 1 件」であることを前提にしたメソッドです。0 件でも 2 件以上でも例外になります。

該当件数get() の結果
0 件Model.DoesNotExist 例外
1 件そのモデルインスタンスを返す
2 件以上Model.MultipleObjectsReturned 例外(このエラー)

よくある原因は次の 2 つです。

  • 条件が一意になっていない: 名前やメールアドレス、外部キーなど、重複しうる項目だけで get している
  • 本来 1 件のはずのデータが重複している: DB にユニーク制約が無く、二重送信や同時実行で同じデータが 2 回登録された

対処法

1. 主キーやユニークキーで 1 件に絞る

get は主キー(pk)やユニーク制約のある項目を条件にして、1 レコードに絞り込んで使うのが基本です。

user = Users.objects.get(pk=user_id)
regist = MynumberRegist.objects.get(user=user, year=2026)  # 組み合わせで一意になる条件

2. 複数件が正しいなら filter を使う

複数レコードがあり得る場合は filter などにすれば、QuerySet として全件を扱えます。

regists = MynumberRegist.objects.filter(user=user)
for r in regists:
    print(r.id)

# 1 件だけ欲しい場合は並び順を決めて first()(0 件なら None)
latest = (MynumberRegist.objects
          .filter(user=user)
          .order_by('-created_at', '-id')
          .first())

first() は並び順を指定しないと主キー順になりますが、「どれを取るか」を意図して order_by() を書いておくと安全です。

3. 例外を捕まえて処理を分ける

try:
    regist = MynumberRegist.objects.get(user=user)
except MynumberRegist.DoesNotExist:
    regist = None
except MynumberRegist.MultipleObjectsReturned:
    # データ不整合としてログに残すなど
    raise

例外を握りつぶして先頭の 1 件を使う、といった処理は重複データを放置することになるので、ログに残して原因を調べるようにします。

重複データの調べ方と再発防止

本来 1 件のはずなのに 2 件あった場合は、重複しているデータを特定します。

from django.db.models import Count

dups = (MynumberRegist.objects
        .values('user_id')
        .annotate(cnt=Count('id'))
        .filter(cnt__gt=1))
print(list(dups))

重複を整理したら、モデルにユニーク制約を追加してマイグレーションし、DB 側で二重登録を防ぎます。

class MynumberRegist(models.Model):
    user = models.ForeignKey(User, models.CASCADE)  # 第 2 引数は削除時の動作
    year = models.IntegerField()

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['user', 'year'], name='uniq_regist_user_year'),
        ]

重複データが残ったままだと、制約を追加するマイグレーションが失敗します。先に重複を解消してから適用してください。

get_or_create でこのエラーが出る場合

get_or_create() も内部で get を使うため、すでに重複がある条件では同じエラーになります。また、ユニーク制約が無い状態で複数のリクエストが同時に get_or_create を実行すると、どちらも「無い」と判断して 2 件作ってしまい、次回以降このエラーが出ることがあります。get_or_create や update_or_create() の条件に使う項目には、ユニーク制約を付けておくのが Django の公式ドキュメントでも推奨されている使い方です。

確認方法

  1. python manage.py shell で MynumberRegist.objects.filter(条件).count() を実行し、該当件数を確認する
  2. 2 以上なら、条件を見直すか重複データを整理する
  3. 修正後、同じ処理を実行して例外が出ないことを確認する

関連

子ページ

子ページはありません

同階層のページ
  1. Invalid HTTP_HOST header: '...'. You may need to add '...' to ALLOWED_HOSTS
  2. CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
  3. django.utils.datastructures.MultiValueDictKeyError
  4. Forbidden (403) CSRF verification failed. Request aborted.
  5. ModuleNotFoundError: No module named 'MySQLdb'
  6. WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for database connection
  7. Unknown column 'table_name.id' in 'field list'
  8. RuntimeError: Model class ~ doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
  9. get() returned more than one MynumberRegist -- it returned 2!
  10. django.db.utils.OperationalError: (2006, "Can't connect to MySQL server")
  11. 'include' is not defined