DjangoBrothers BLOG ✍️

2020/11/06

このエントリーをはてなブックマークに追加
Django モデル

【Django】複数フィールドにユニーク制約(複合ユニーク)をつける方法

Djangoのモデルでは、name = models.CharField(max_length=50, unique=True)のようにフィールドのオプションとしてunique=Trueを指定することで、そのフィールドはユニークな(一意な)値しか入れれない制約がつきます。

1つのフィールドだけではなく、複数フィールドの組み合わせでユニーク制約をつけたい場合は、UniqueConstraintを使います。

バージョン

  • Python 3.8.4
  • Django 3.1.2

UniqueConstraintの使い方

以下のようにconstraintsリストの中に、ユニーク制約を定義することができます。

以下の例では、Lessonモデルに(teacher、student、date)の組み合わせでユニークになる設定をしています。

models.py

class Lesson(models.Model):
    """レッスンモデル(生徒が同日に同じ先生のレッスンを受講できるのは1回まで。)"""

    teacher = models.CharField("先生", max_length=50)
    student = models.CharField("生徒", max_length=50)
    date = models.DateField("レッスン実施日")

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["teacher", "student", "date"],
                name="lesson_unique"
            ),
        ]

上記の設定で、同じ先生・生徒・レッスン実施日の組み合わせでデータを複数登録することはできなくなりました。

(teacher, student, date)のユニーク制約

>>> Lesson.objects.create(teacher="Jobs", student="高橋", date="2020-11-06")
<Lesson: Lesson object (1)>

# 重複したデータは登録できない
>>> Lesson.objects.create(teacher="Jobs", student="高橋", date="2020-11-06")
sqlite3.IntegrityError: UNIQUE constraint failed: app_lesson.teacher, app_lesson.student, app_lesson.date

# 生徒名が異なれば登録できる
>>> Lesson.objects.create(teacher="Jobs", student="山田", date="2020-11-06")
<Lesson: Lesson object (2)>

ユニーク制約に違反したデータを登録しようとするとIntegrityErrorが発生します。

複数のペアを作る

複数の複合ユニーク制約を追加したい場合は、以下のように実装できます。

models.py

class Lesson(models.Model):
    """レッスンモデル(先生・生徒は同日に1レッスンのみ実施可能)"""

    teacher = models.CharField("先生", max_length=50)
    student = models.CharField("生徒", max_length=50)
    date = models.DateField("レッスン実施日")

    class Meta:
        constraints = [
            # 先生-レッスン実施日のペアでユニーク制約
            models.UniqueConstraint(
                fields=["teacher", "date"],
                name="teacher_date_unique"
            ),
            # 生徒-レッスン実施日のペアでユニーク制約
            models.UniqueConstraint(
                fields=["student", "date"],
                name="student_date_unique"
            ),
        ]

上記の実装によって、先生・生徒はそれぞれ1日に1回しかレッスンを実施できない設定になりました。

(teacher, date), (student, date)のユニーク制約

# データ(先生: Jobs, 生徒: 高橋, 日付:11月6日)を登録
>>> Lesson.objects.create(teacher="Jobs", student="高橋", date="2020-11-06")
<Lesson: Lesson object (1)>

# データ登録不可(Jobsは11月6日にレッスンがあるため)
>>> Lesson.objects.create(teacher="Jobs", student="山田", date="2020-11-06")
sqlite3.IntegrityError: UNIQUE constraint failed: app_lesson.teacher, app_lesson.date

# データ登録不可(高橋は11月6日にレッスンがあるため)
>>> Lesson.objects.create(teacher="Rola", student="高橋", date="2020-11-06")
sqlite3.IntegrityError: UNIQUE constraint failed: app_lesson.student, app_lesson.date

# 11月7日のレッスンは登録可能
>>> Lesson.objects.create(teacher="Jobs", student="高橋", date="2020-11-07")
<Lesson: Lesson object (1)>

unique_togetherは非推奨

ユニーク制約をつけたい時、これまではunique_togetherという書き方を利用するのが一般的だったと思いますが、この書き方は将来使えなくなる可能性があるとのことです。

また、UniqueConstraintの方が提供している機能も豊富とのことで、今後はこちらを使うと良いでしょう。参照