PostgreSQL 固有のデータベース制約

PostgreSQL は django.contrib.postgres.constraints モジュールから利用できる追加のデータ整合性制約をサポートしています。これらはモデル Meta.constraints オプションで追加されます。

ExclusionConstraint

class ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, violation_error_code=None, violation_error_message=None)

データベースに排他制約を作成します。内部的には、PostgreSQLはインデックスを使用して排他制約を実装しています。デフォルトのインデックス型は GiST です。インデックスを使用するには、PostgreSQLで btree_gist extension を有効にする必要があります。 BtreeGistExtension マイグレーションオペレーションを使ってインストールできます。

既存の行と競合する新しい行を挿入しようとすると、IntegrityError が発生します。更新が既存の行と競合する場合も同様です。

排他制約は モデル バリデーション の間にチェックされます。

name

ExclusionConstraint.name

BaseConstraint.name を参照。

expressions

ExclusionConstraint.expressions

2値タプルのイテラブル。最初の要素は式または文字列。2番目の要素は文字列で表現された SQL 演算子です。タイプミスを避けるために、演算子を文字列でマップする RangeOperators を使用できます。たとえば:

expressions = [
    ("timespan", RangeOperators.ADJACENT_TO),
    (F("room"), RangeOperators.EQUAL),
]

演算子の制約

排他制約には可換演算子のみ使用できます。

OpClass() 式は、制約式のカスタム operator class を指定するために使用できます。たとえば:

expressions = [
    (OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS),
]

これは circle_ops を使用して circle 上に排他制約を作成します。

index_type

ExclusionConstraint.index_type

制約のインデックスタイプです。受け入れられる値は GIST または SPGIST です。大文字小文字を区別しません。指定されていない場合、デフォルトのインデックスタイプは GIST です。

condition

ExclusionConstraint.condition

行の一部に制約をかける条件を指定する Q オブジェクト。たとえば、 condition=Q(cancelled=False)

これらの条件は、 django.db.models.Index.condition と同じデータベースの制約事項を持ちます。

deferrable

ExclusionConstraint.deferrable

遅延可能な排他制約を作成するには、このパラメータを設定します。使用可能な値は Deferrable.DEFERRED または Deferrable.IMMEDIATE です。例:

from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import RangeOperators
from django.db.models import Deferrable


ExclusionConstraint(
    name="exclude_overlapping_deferred",
    expressions=[
        ("timespan", RangeOperators.OVERLAPS),
    ],
    deferrable=Deferrable.DEFERRED,
)

デフォルトでは、制約は遅延 (DEFERRED) されません。遅延された制約は、トランザクションが終了するまで実行されません。即時 (IMMEDIATE) 制約は、すべてのコマンドの直後に実行されます。

警告

排他制約の遅延は、パフォーマンスの悪化 performance penalty につながるかも知れません。

include

ExclusionConstraint.include

非キー列としてカバリング排他制約に含めるフィールド名のリストまたはタプル。これにより、含まれるフィールドのみを選択し (include)、インデックス化されたフィールドのみでフィルタリングする (expressions) クエリにインデックスのみのスキャンを使用できます。

include は GiST インデックスでサポートされています。PostgreSQL 14+ では、SP-GiST インデックスでも include がサポートされています。

violation_error_code

New in Django 5.0.
ExclusionConstraint.violation_error_code

モデルのバリデーション 中に ValidationError が発生した場合に使用されるエラーコードです。デフォルトは None です。

violation_error_message

モデルのバリデーション 中に ValidationError が発生した場合に使用されるエラーメッセージです。デフォルトは BaseConstraint.violation_error_message です。

次の例では、キャンセルされた予約を考慮せずに、同じ部屋での重複する予約を制限しています。

from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
from django.db import models
from django.db.models import Q


class Room(models.Model):
    number = models.IntegerField()


class Reservation(models.Model):
    room = models.ForeignKey("Room", on_delete=models.CASCADE)
    timespan = DateTimeRangeField()
    cancelled = models.BooleanField(default=False)

    class Meta:
        constraints = [
            ExclusionConstraint(
                name="exclude_overlapping_reservations",
                expressions=[
                    ("timespan", RangeOperators.OVERLAPS),
                    ("room", RangeOperators.EQUAL),
                ],
                condition=Q(cancelled=False),
            ),
        ]

モデルが2つのフィールドを使用して範囲を定義する場合、PostgreSQLのネイティブな範囲型ではなく、同等の関数 (例えば TsTzRange()) を使用する式を記述し、フィールドの区切り文字を使用する必要があります。ほとんどの場合、区切り文字は '[)' となり、下限は包括的で上限は排他的であることを意味します。 範囲境界 の式マッピングを提供する RangeBoundary を使用できます。たとえば:

from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import (
    DateTimeRangeField,
    RangeBoundary,
    RangeOperators,
)
from django.db import models
from django.db.models import Func, Q


class TsTzRange(Func):
    function = "TSTZRANGE"
    output_field = DateTimeRangeField()


class Reservation(models.Model):
    room = models.ForeignKey("Room", on_delete=models.CASCADE)
    start = models.DateTimeField()
    end = models.DateTimeField()
    cancelled = models.BooleanField(default=False)

    class Meta:
        constraints = [
            ExclusionConstraint(
                name="exclude_overlapping_reservations",
                expressions=[
                    (
                        TsTzRange("start", "end", RangeBoundary()),
                        RangeOperators.OVERLAPS,
                    ),
                    ("room", RangeOperators.EQUAL),
                ],
                condition=Q(cancelled=False),
            ),
        ]
Back to Top