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
¶
- 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),
),
]