# Django 5.2.8 ドキュメント

# 複合主キー

New in Django 5.2.

Django では、すべてのモデルに主キーが存在します。デフォルトでは、この主キーは単一のフィールドで構成されます。

ほとんどの場合、単一の主キーで十分です。しかし、データベース設計においては、複数のフィールドからなる主キーが必要になることもあります。

複合主キーを使用するには、モデルを定義する際に `pk` 属性を `CompositePrimaryKey` に設定します：

    class Product(models.Model):
        name = models.CharField(max_length=100)

    class Order(models.Model):
        reference = models.CharField(max_length=20, primary_key=True)

    class OrderLineItem(models.Model):
        pk = models.CompositePrimaryKey("product_id", "order_id")
        product = models.ForeignKey(Product, on_delete=models.CASCADE)
        order = models.ForeignKey(Order, on_delete=models.CASCADE)
        quantity = models.IntegerField()

これにより、Django はテーブル作成時に複合主キー（ `PRIMARY KEY (product_id, order_id)` ）を作成するようになります。

複合主キーは `tuple` （タプル）で表現されます：

    >>> product = Product.objects.create(name="apple")
    >>> order = Order.objects.create(reference="A755H")
    >>> item = OrderLineItem.objects.create(product=product, order=order, quantity=1)
    >>> item.pk
    (1, "A755H")

`pk` 属性に `tuple` を代入することで、対応するフィールドの値をまとめて設定できます：

    >>> item = OrderLineItem(pk=(2, "B142C"))
    >>> item.pk
    (2, "B142C")
    >>> item.product_id
    2
    >>> item.order_id
    "B142C"

複合主キーは、 `tuple` を使ってフィルタリングできます：

    >>> OrderLineItem.objects.filter(pk=(1, "A755H")).count()
    1

リレーションシップフィールド (`GenericForeignKey` を含む) や Django 管理サイトにおける複合主キーのサポートは、現在も開発中です。現時点では、複合主キーを持つモデルは Django の管理サイトに登録できません。将来のリリースで対応が追加される予定です。

## 複合主キーへの移行

Django は、テーブル作成後に複合主キーへマイグレーションしたり、それを元に戻したりする操作をサポートしていません。また、複合主キーにフィールドを追加・削除することもサポートされていません。

既存のテーブルを単一の主キーから複合主キーに移行したい場合は、利用中のデータベースバックエンドの手順にしたがって操作してください。

複合主キーを設定できたら、モデルに `CompositePrimaryKey` フィールドを追加してください。これにより、Django はその複合主キーを正しく認識し、適切に扱えるようになります。

主キーフィールドに対するマイグレーション操作（たとえば `AddField` や `AlterField` ）はサポートされていませんが、 `makemigrations` は変更を検出します。

エラーを避けるために、上記のようなマイグレーションは `--fake` オプションを付けて適用することを推奨します。

代わりに、 `SeparateDatabaseAndState` を使うことで、データベース固有のマイグレーションと Django による状態管理用マイグレーションを、1つの操作としてまとめて実行することもできます。

## 複合主キーとリレーション

リレーションシップフィールド (ジェネリックリレーション を含む) は、複合主キーをサポートしていません。

たとえば先ほどの `OrderLineItem` モデルにおいて、次のような使い方はサポートされていません：

    class Foo(models.Model):
        item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)

これは、現在 `ForeignKey` が複合主キーを持つモデルを参照できないためです。

この制限を回避するには、代わりに `ForeignObject` が使えます：

    class Foo(models.Model):
        item_order_id = models.CharField(max_length=20)
        item_product_id = models.IntegerField()
        item = models.ForeignObject(
            OrderLineItem,
            on_delete=models.CASCADE,
            from_fields=("item_order_id", "item_product_id"),
            to_fields=("order_id", "product_id"),
        )

`ForeignObject` は `ForeignKey` によく似ていますが、データベース上にカラム (例: `item_id`)、外部キー制約、インデックスなどを作成しない点、 `on_delete` 引数が無視される点が異なります。

警告

`ForeignObject` は内部 API です。つまり、 非推奨ポリシー の対象外です。

## 複合主キーとデータベース関数

多くのデータベース関数は、単一の式しか受け付けません。

    MAX("order_id")  -- OK
    MAX("product_id", "order_id")  -- ERROR

複合主キーは複数のカラム表現を含むため、このような関数に渡すと、 `ValueError` が発生します。 ただし `Count` だけは例外で、複合主キーでも利用できます。

    Max("order_id")  # OK
    Max("pk")  # ValueError
    Count("pk")  # OK

## フォーム内での複合主キー

複合主キーは仮想フィールドであり、単一のデータベースカラムを表さないため、ModelForm には含まれません。

たとえば、次のようなフォームを考えてみましょう：

    class OrderLineItemForm(forms.ModelForm):
        class Meta:
            model = OrderLineItem
            fields = "__all__"

このフォームには、複合主キーのための `pk` フィールドは含まれません：

    >>> OrderLineItemForm()
    <OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>

複合主キーである `pk` をフォームフィールドとして指定しようとすると、存在しないフィールドとして `FieldError` が発生します。

主キーフィールドは読み取り専用です

既存のオブジェクトで主キーの値を変更してから保存すると、新しいオブジェクトが元のオブジェクトとは別に作成されます (`Field.primary_key` を参照)。

これは複合主キーの場合も同様です。そのため、すべての主キーフィールドに対して `Field.editable` を `False` に設定し、ModelForm から除外することを検討してください。

## モデルバリデーションと複合主キー

`pk` は仮想フィールドなので、 `Model.clean_fields()` の `exclude` 引数に `pk` を指定しても効果はありません。複合主キーの各フィールドをバリデーションから除外したい場合は、それぞれのフィールド名を個別に指定してください。一方、 `Model.validate_unique()` に対しては `exclude={"pk"}` を指定することで、一意性のチェックをスキップできます。

## 複合主キーに対応したアプリケーションをつくる

複合主キーが導入される前は、モデルの主キーとなる単一フィールドを見つけたいとき、各フィールドの `primary_key` 属性を確認するだけで済みました：

    >>> pk_field = None
    >>> for field in Product._meta.get_fields():
    ...     if field.primary_key:
    ...         pk_field = field
    ...         break
    ...
    >>> pk_field
    <django.db.models.fields.AutoField: id>

しかし `primary key` 属性はひとつのモデルにつき最大ひとつのフィールドしか `True` を取れないという制約があるため、複合主キーのもとではすべて `False` になってしまい、これでは主キーを構成するフィールドを特定するためには使えません。

    >>> pk_fields = []
    >>> for field in OrderLineItem._meta.get_fields():
    ...     if field.primary_key:
    ...         pk_fields.append(field)
    ...
    >>> pk_fields
    []

複合主キーに正しく対応するコードを書くためには、代わりに `_meta.pk_fields` 属性を使用してください：

    >>> Product._meta.pk_fields
    [<django.db.models.fields.AutoField: id>]
    >>> OrderLineItem._meta.pk_fields
    [
        <django.db.models.fields.ForeignKey: product>,
        <django.db.models.fields.ForeignKey: order>
    ]