メインコンテンツにスキップ

リレーション

リレーションとは、Prismaスキーマ内の2つのモデル間の接続です。たとえば、1人のユーザーが多数のブログ投稿を持つことができるため、UserPostの間には1対多のリレーションがあります。

次のPrismaスキーマは、UserモデルとPostモデル間の1対多リレーションを定義しています。リレーションの定義に関与するフィールドが強調表示されています

model User {
id Int @id @default(autoincrement())
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int // relation scalar field (used in the `@relation` attribute above)

title String
}

Prisma ORMレベルでは、User / Postリレーションは以下で構成されています

  • 2つのリレーションフィールドauthorposts。リレーションフィールドは、Prisma ORMレベルでのモデル間の接続を定義し、データベースには存在しません。これらのフィールドは、Prisma Clientを生成するために使用されます。
  • @relation属性によって参照されるスカラーauthorIdフィールド。このフィールドはデータベースに存在します - これは、PostUserを接続する外部キーです。

Prisma ORMレベルでは、2つのモデル間の接続は常に、リレーションの両側リレーションフィールドによって表されます。

データベース内のリレーション

リレーショナルデータベース

次のエンティティ関係図は、リレーショナルデータベースUserテーブルとPostテーブル間の同じ1対多リレーションを定義しています

A one-to-many relationship between a user and posts table.

SQLでは、外部キーを使用して2つのテーブル間のリレーションを作成します。外部キーはリレーションの片側に格納されます。私たちの例は以下で構成されています

  • PostテーブルのauthorIdという名前の外部キー列。
  • Userテーブルのidという名前の主キー列。PostテーブルのauthorId列は、Userテーブルのid列を参照します。

Prismaスキーマでは、外部キー/主キーの関係は、authorフィールドの@relation属性によって表されます

author     User        @relation(fields: [authorId], references: [id])

:Prismaスキーマのリレーションは、データベース内のテーブル間に存在する関係を表します。関係がデータベースに存在しない場合、Prismaスキーマにも存在しません。

MongoDB

MongoDBの場合、Prisma ORMは現在、正規化されたデータモデル設計を使用しています。これは、ドキュメントがリレーショナルデータベースと同様の方法でIDによって相互参照することを意味します。

次のドキュメントは、UserUserコレクション内)を表しています

{ "_id": { "$oid": "60d5922d00581b8f0062e3a8" }, "name": "Ella" }

次のPostドキュメント(Postコレクション内)のリストには、それぞれ同じユーザーを参照するauthorIdフィールドがあります

[
{
"_id": { "$oid": "60d5922e00581b8f0062e3a9" },
"title": "How to make sushi",
"authorId": { "$oid": "60d5922d00581b8f0062e3a8" }
},
{
"_id": { "$oid": "60d5922e00581b8f0062e3aa" },
"title": "How to re-install Windows",
"authorId": { "$oid": "60d5922d00581b8f0062e3a8" }
}
]

このデータ構造は、複数のPostドキュメントが同じUserドキュメントを参照するため、1対多リレーションを表します。

IDおよびリレーションスカラーフィールドの@db.ObjectId

モデルのIDがObjectIdStringフィールドで表される)の場合、モデルのIDリレーションの反対側のリレーションスカラーフィールドに@db.ObjectIdを追加する必要があります

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[]
}

model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId // relation scalar field (used in the `@relation` attribute above)

title String
}

Prisma Clientのリレーション

Prisma ClientはPrismaスキーマから生成されます。次の例は、Prisma Clientを使用してレコードを取得、作成、および更新するときにリレーションがどのように現れるかを示しています。

レコードとネストされたレコードの作成

次のクエリは、Userレコードと2つの接続されたPostレコードを作成します

const userAndPosts = await prisma.user.create({
data: {
posts: {
create: [
{ title: 'Prisma Day 2020' }, // Populates authorId with user's id
{ title: 'How to write a Prisma schema' }, // Populates authorId with user's id
],
},
},
})

基盤となるデータベースでは、このクエリは

  1. 自動生成されたid(たとえば、20)を持つUserを作成します
  2. 2つの新しいPostレコードを作成し、両方のレコードのauthorId20に設定します

次のクエリは、idUserを取得し、関連するPostレコードをすべて含めます

const getAuthor = await prisma.user.findUnique({
where: {
id: "20",
},
include: {
posts: true, // All posts where authorId == 20
},
});

基盤となるデータベースでは、このクエリは

  1. id20Userレコードを取得します
  2. authorId20のすべてのPostレコードを取得します

既存のレコードと別の既存のレコードの関連付け

次のクエリは、既存のPostレコードを既存のUserレコードに関連付けます

const updateAuthor = await prisma.user.update({
where: {
id: 20,
},
data: {
posts: {
connect: {
id: 4,
},
},
},
})

基盤となるデータベースでは、このクエリはネストされたconnectクエリを使用して、IDが4の投稿をIDが20のユーザーにリンクします。クエリは次の手順でこれを行います

  • クエリは最初に、id20のユーザーを探します。
  • 次に、クエリはauthorID外部キーを20に設定します。これにより、IDが4の投稿がIDが20のユーザーにリンクされます。

このクエリでは、authorIDの現在の値は関係ありません。クエリはauthorIDを現在の値に関係なく20に変更します。

リレーションのタイプ

Prisma ORMには、3つの異なるタイプ(またはカーディナリティ)のリレーションがあります

  • 1対1(1対1リレーションとも呼ばれます)
  • 1対多(1対nリレーションとも呼ばれます)
  • 多対多(m対nリレーションとも呼ばれます)

次のPrismaスキーマには、すべてのタイプのリレーションが含まれています

  • 1対1:UserProfile
  • 1対多:UserPost
  • 多対多:PostCategory
model User {
id Int @id @default(autoincrement())
posts Post[]
profile Profile?
}

model Profile {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @unique // relation scalar field (used in the `@relation` attribute above)
}

model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int // relation scalar field (used in the `@relation` attribute above)
categories Category[]
}

model Category {
id Int @id @default(autoincrement())
posts Post[]
}
情報

このスキーマは、データモデルの例と同じですが、すべてのスカラーフィールド(必須のリレーションスカラーフィールドを除く)が削除されているため、リレーションフィールドに集中できます。

情報

この例では、暗黙的な多対多リレーションを使用しています。これらのリレーションは、リレーションを明確にする必要がある場合を除き、@relation属性を必要としません。

構文は、特に多対多リレーションの場合、リレーショナルデータベースとMongoDBでわずかに異なることに注意してください。

リレーショナルデータベースの場合、次のエンティティ関係図は、サンプルPrismaスキーマに対応するデータベースを表しています

The sample schema as an entity relationship diagram

MongoDBの場合、Prisma ORMは正規化されたデータモデル設計を使用しています。これは、ドキュメントがリレーショナルデータベースと同様の方法でIDによって相互参照することを意味します。詳細については、MongoDBセクションを参照してください。

暗黙的および明示的な多対多リレーション

リレーショナルデータベースの多対多リレーションは、2つの方法でモデル化できます

暗黙的な多対多リレーションでは、両方のモデルに単一の@idが必要です。以下に注意してください

これらの機能のいずれかを使用するには、代わりに明示的な多対多リレーションを設定する必要があります。

暗黙的な多対多リレーションは、基盤となるデータベースのリレーションテーブルに依然として現れます。ただし、Prisma ORMはこのリレーションテーブルを管理します。

明示的な多対多リレーションの代わりに暗黙的な多対多リレーションを使用すると、Prisma Client APIが簡素化されます(たとえば、ネストされた書き込みの内部のネストレベルが1つ少なくなるため)。

Prisma Migrateを使用せずに、イントロスペクションからデータモデルを取得する場合でも、Prisma ORMのリレーションテーブルの規則に従うことで、暗黙的な多対多リレーションを利用できます。

リレーションフィールド

リレーションフィールドは、Prisma モデルのフィールドであり、スカラー型を持っていません。代わりに、それらの型は別のモデルです。

すべてのリレーションは、各モデルに1つずつ、正確に2つのリレーションフィールドを持つ必要があります。1対1および1対多リレーションの場合、追加のリレーションスカラーフィールドが必要であり、これは@relation属性の2つのリレーションフィールドの1つによってリンクされます。このリレーションスカラーフィールドは、基盤となるデータベースの外部キーの直接表現です。

model User {
id Int @id @default(autoincrement())
email String @unique
role Role @default(USER)
posts Post[] // relation field (defined only at the Prisma ORM level)
}

model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id]) // relation field (uses the relation scalar field `authorId` below)
authorId Int // relation scalar field (used in the `@relation` attribute above)
}

postsauthorの両方がリレーションフィールドです。なぜなら、それらの型はスカラー型ではなく、他のモデルであるからです。

また、注釈付きリレーションフィールドauthorは、@relation属性内のPostモデルのリレーションスカラーフィールドauthorIdをリンクする必要があることにも注意してください。リレーションスカラーフィールドは、基盤となるデータベースの外部キーを表します。

リレーションフィールド(つまり、postsauthor)の両方は、純粋にPrisma ORMレベルで定義されており、データベースには現れません。

注釈付きリレーションフィールド

リレーションの一方の側を@relation属性で注釈する必要があるリレーションは、注釈付きリレーションフィールドと呼ばれます。これには以下が含まれます

  • 1対1リレーション
  • 1対多リレーション
  • MongoDBのみの多対多リレーション

@relation属性で注釈が付けられたリレーションの側は、基盤となるデータベースに外部キーを格納する側を表します。外部キーを表す「実際」のフィールドも、リレーションのその側で必要であり、リレーションスカラーフィールドと呼ばれ、@relation属性内で参照されます

author     User    @relation(fields: [authorId], references: [id])
authorId Int

スカラーフィールドは、@relation属性のfieldsで使用されると、リレーションスカラーフィールドになります

リレーションスカラーフィールド

リレーションスカラーフィールドの命名規則

リレーションスカラーフィールドは常にリレーションフィールドに属するため、次の命名規則が一般的です

  • リレーションフィールド:author
  • リレーションスカラーフィールド:authorId(リレーションフィールド名 + Id

@relation属性

@relation属性は、リレーションフィールドにのみ適用でき、スカラーフィールドには適用できません。

@relation属性は、次の場合に必須です

  • 1対1または1対多リレーションを定義する場合、リレーションの片側(対応するリレーションスカラーフィールドを使用)で必須です
  • リレーションを明確にする必要がある場合(たとえば、同じモデル間に2つのリレーションがある場合など)
  • 自己リレーションを定義する場合
  • MongoDBの多対多リレーションを定義する場合
  • リレーションテーブルが基盤となるデータベースでどのように表現されるかを制御する必要がある場合(例:リレーションテーブルに特定の名前を使用するなど)

:リレーショナルデータベースの暗黙的な多対多リレーションは、@relation属性を必要としません。

リレーションの明確化

同じ2つのモデル間に2つのリレーションを定義する場合、@relation属性にname引数を追加して、それらを明確にする必要があります。なぜそれが必要なのかの例として、次のモデルを考えてみましょう

// NOTE: This schema is intentionally incorrect. See below for a working solution.

model User {
id Int @id @default(autoincrement())
name String?
writtenPosts Post[]
pinnedPost Post?
}

model Post {
id Int @id @default(autoincrement())
title String?
author User @relation(fields: [authorId], references: [id])
authorId Int
pinnedBy User? @relation(fields: [pinnedById], references: [id])
pinnedById Int?
}

その場合、リレーションはあいまいであり、それらを解釈する方法は4つあります

  • User.writtenPostsPost.author + Post.authorId
  • User.writtenPostsPost.pinnedBy + Post.pinnedById
  • User.pinnedPostPost.author + Post.authorId
  • User.pinnedPostPost.pinnedBy + Post.pinnedById

これらのリレーションを明確にするには、@relation属性でリレーションフィールドに注釈を付け、name引数を指定する必要があります。任意のname(空文字列""を除く)を設定できますが、リレーションの両側で同じである必要があります

model User {
id Int @id @default(autoincrement())
name String?
writtenPosts Post[] @relation("WrittenPosts")
pinnedPost Post? @relation("PinnedPost")
}

model Post {
id Int @id @default(autoincrement())
title String?
author User @relation("WrittenPosts", fields: [authorId], references: [id])
authorId Int
pinnedBy User? @relation("PinnedPost", fields: [pinnedById], references: [id])
pinnedById Int? @unique
}