---
title: "複合主キー"
version: 6.1
locale: ja
source: https://docs.djangoproject.com/ja/6.1/topics/composite-primary-key/
canonical: https://djangodocs.dev/ja/6.1/topics/composite-primary-key/
---
# 複合主キー

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

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

複合主キーを使用するには、モデルを定義する際に `pk` 属性を [`CompositePrimaryKey`](/ja/6.1/ref/models/fields/#django.db.models.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` （タプル）で表現されます：

```pycon
>>> 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`](/ja/6.1/ref/models/instances/#django.db.models.Model.pk) 属性に `tuple` を代入することで、対応するフィールドの値をまとめて設定できます：

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

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

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

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

## 複合主キーへの移行

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

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

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

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

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

代わりに、 [`SeparateDatabaseAndState`](/ja/6.1/ref/migration-operations/#django.db.migrations.operations.SeparateDatabaseAndState) を使うことで、データベース固有のマイグレーションと Django による状態管理用マイグレーションを、1つの操作としてまとめて実行することもできます。

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

[リレーションシップフィールド](/ja/6.1/ref/models/fields/#relationship-fields) ([ジェネリックリレーション](/ja/6.1/ref/contrib/contenttypes/#generic-relations) を含む) は、複合主キーをサポートしていません。

たとえば先ほどの `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` 引数が無視される点が異なります。

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

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

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

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

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

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

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

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

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

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

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

複合主キーである `pk` をフォームフィールドとして指定しようとすると、存在しないフィールドとして [`FieldError`](/ja/6.1/ref/exceptions/#django.core.exceptions.FieldError) が発生します。

> **主キーフィールドは読み取り専用です**
>
> 既存のオブジェクトで主キーの値を変更してから保存すると、新しいオブジェクトが元のオブジェクトとは別に作成されます ([`Field.primary_key`](/ja/6.1/ref/models/fields/#django.db.models.Field.primary_key) を参照)。
>
> これは複合主キーの場合も同様です。そのため、すべての主キーフィールドに対して [`Field.editable`](/ja/6.1/ref/models/fields/#django.db.models.Field.editable) を `False` に設定し、ModelForm から除外することを検討してください。

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

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

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

Prior to the introduction of composite primary keys, the single field composing
the primary key of a model could be retrieved by introspecting the
[`primary_key`](/ja/6.1/ref/models/fields/#django.db.models.Field.primary_key) attribute of its fields:

```pycon
>>> 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>
```

Now that a primary key can be composed of multiple fields the
[`primary_key`](/ja/6.1/ref/models/fields/#django.db.models.Field.primary_key) attribute can no longer be relied
upon to identify members of the primary key as it will be set to `False` to
maintain the invariant that at most one field per model will have this
attribute set to `True`:

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

複合主キーに正しく対応するコードを書くためには、代わりに [`_meta.pk_fields`](/ja/6.1/ref/models/meta/#django.db.models.options.Options.pk_fields) 属性を使用してください：

```pycon
>>> 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>
]
```
