リレーション
リレーションとは、Prismaスキーマ内の2つのモデル間の接続です。たとえば、1人のユーザーが多数のブログ投稿を持つことができるため、User
とPost
の間には1対多のリレーションがあります。
次のPrismaスキーマは、User
モデルとPost
モデル間の1対多リレーションを定義しています。リレーションの定義に関与するフィールドが強調表示されています
- リレーショナルデータベース
- MongoDB
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
}
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 ORMレベルでは、User
/ Post
リレーションは以下で構成されています
- 2つのリレーションフィールド:
author
とposts
。リレーションフィールドは、Prisma ORMレベルでのモデル間の接続を定義し、データベースには存在しません。これらのフィールドは、Prisma Clientを生成するために使用されます。 @relation
属性によって参照されるスカラーauthorId
フィールド。このフィールドはデータベースに存在します - これは、Post
とUser
を接続する外部キーです。
Prisma ORMレベルでは、2つのモデル間の接続は常に、リレーションの両側のリレーションフィールドによって表されます。
データベース内のリレーション
リレーショナルデータベース
次のエンティティ関係図は、リレーショナルデータベースのUser
テーブルとPost
テーブル間の同じ1対多リレーションを定義しています
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によって相互参照することを意味します。
次のドキュメントは、User
(User
コレクション内)を表しています
{ "_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がObjectId
(String
フィールドで表される)の場合、モデルの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
],
},
},
})
基盤となるデータベースでは、このクエリは
- 自動生成された
id
(たとえば、20
)を持つUser
を作成します - 2つの新しい
Post
レコードを作成し、両方のレコードのauthorId
を20
に設定します
レコードの取得と関連レコードのインクルード
次のクエリは、id
でUser
を取得し、関連するPost
レコードをすべて含めます
const getAuthor = await prisma.user.findUnique({
where: {
id: "20",
},
include: {
posts: true, // All posts where authorId == 20
},
});
基盤となるデータベースでは、このクエリは
id
が20
のUser
レコードを取得しますauthorId
が20
のすべてのPost
レコードを取得します
既存のレコードと別の既存のレコードの関連付け
次のクエリは、既存のPost
レコードを既存のUser
レコードに関連付けます
const updateAuthor = await prisma.user.update({
where: {
id: 20,
},
data: {
posts: {
connect: {
id: 4,
},
},
},
})
基盤となるデータベースでは、このクエリはネストされたconnect
クエリを使用して、IDが4の投稿をIDが20のユーザーにリンクします。クエリは次の手順でこれを行います
- クエリは最初に、
id
が20
のユーザーを探します。 - 次に、クエリは
authorID
外部キーを20
に設定します。これにより、IDが4
の投稿がIDが20
のユーザーにリンクされます。
このクエリでは、authorID
の現在の値は関係ありません。クエリはauthorID
を現在の値に関係なく20
に変更します。
リレーションのタイプ
Prisma ORMには、3つの異なるタイプ(またはカーディナリティ)のリレーションがあります
次のPrismaスキーマには、すべてのタイプのリレーションが含まれています
- 1対1:
User
↔Profile
- 1対多:
User
↔Post
- 多対多:
Post
↔Category
- リレーショナルデータベース
- MongoDB
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[]
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[]
profile Profile?
}
model Profile {
id String @id @default(auto()) @map("_id") @db.ObjectId
user User @relation(fields: [userId], references: [id])
userId String @unique @db.ObjectId // relation scalar field (used in the `@relation` attribute above)
}
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)
categories Category[] @relation(fields: [categoryIds], references: [id])
categoryIds String[] @db.ObjectId
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[] @relation(fields: [postIds], references: [id])
postIds String[] @db.ObjectId
}
このスキーマは、データモデルの例と同じですが、すべてのスカラーフィールド(必須のリレーションスカラーフィールドを除く)が削除されているため、リレーションフィールドに集中できます。
この例では、暗黙的な多対多リレーションを使用しています。これらのリレーションは、リレーションを明確にする必要がある場合を除き、@relation
属性を必要としません。
構文は、特に多対多リレーションの場合、リレーショナルデータベースとMongoDBでわずかに異なることに注意してください。
リレーショナルデータベースの場合、次のエンティティ関係図は、サンプルPrismaスキーマに対応するデータベースを表しています
MongoDBの場合、Prisma ORMは正規化されたデータモデル設計を使用しています。これは、ドキュメントがリレーショナルデータベースと同様の方法でIDによって相互参照することを意味します。詳細については、MongoDBセクションを参照してください。
暗黙的および明示的な多対多リレーション
リレーショナルデータベースの多対多リレーションは、2つの方法でモデル化できます
- 明示的な多対多リレーション。リレーションテーブルはPrismaスキーマで明示的なモデルとして表されます
- 暗黙的な多対多リレーション。Prisma ORMがリレーションテーブルを管理し、Prismaスキーマには表示されません。
暗黙的な多対多リレーションでは、両方のモデルに単一の@id
が必要です。以下に注意してください
- 複数フィールドIDは使用できません
@id
の代わりに@unique
は使用できません
これらの機能のいずれかを使用するには、代わりに明示的な多対多リレーションを設定する必要があります。
暗黙的な多対多リレーションは、基盤となるデータベースのリレーションテーブルに依然として現れます。ただし、Prisma ORMはこのリレーションテーブルを管理します。
明示的な多対多リレーションの代わりに暗黙的な多対多リレーションを使用すると、Prisma Client APIが簡素化されます(たとえば、ネストされた書き込みの内部のネストレベルが1つ少なくなるため)。
Prisma Migrateを使用せずに、イントロスペクションからデータモデルを取得する場合でも、Prisma ORMのリレーションテーブルの規則に従うことで、暗黙的な多対多リレーションを利用できます。
リレーションフィールド
リレーションフィールドは、Prisma モデルのフィールドであり、スカラー型を持っていません。代わりに、それらの型は別のモデルです。
すべてのリレーションは、各モデルに1つずつ、正確に2つのリレーションフィールドを持つ必要があります。1対1および1対多リレーションの場合、追加のリレーションスカラーフィールドが必要であり、これは@relation
属性の2つのリレーションフィールドの1つによってリンクされます。このリレーションスカラーフィールドは、基盤となるデータベースの外部キーの直接表現です。
- リレーショナルデータベース
- MongoDB
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)
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
role Role @default(USER)
posts Post[] // relation field (defined only at the Prisma ORM level)
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
author User @relation(fields: [authorId], references: [id]) // relation field (uses the relation scalar field `authorId` below)
authorId String @db.ObjectId // relation scalar field (used in the `@relation` attribute above)
}
posts
とauthor
の両方がリレーションフィールドです。なぜなら、それらの型はスカラー型ではなく、他のモデルであるからです。
また、注釈付きリレーションフィールドauthor
は、@relation
属性内のPost
モデルのリレーションスカラーフィールドauthorId
をリンクする必要があることにも注意してください。リレーションスカラーフィールドは、基盤となるデータベースの外部キーを表します。
リレーションフィールド(つまり、posts
とauthor
)の両方は、純粋にPrisma ORMレベルで定義されており、データベースには現れません。
注釈付きリレーションフィールド
リレーションの一方の側を@relation
属性で注釈する必要があるリレーションは、注釈付きリレーションフィールドと呼ばれます。これには以下が含まれます
- 1対1リレーション
- 1対多リレーション
- MongoDBのみの多対多リレーション
@relation
属性で注釈が付けられたリレーションの側は、基盤となるデータベースに外部キーを格納する側を表します。外部キーを表す「実際」のフィールドも、リレーションのその側で必要であり、リレーションスカラーフィールドと呼ばれ、@relation
属性内で参照されます
- リレーショナルデータベース
- MongoDB
author User @relation(fields: [authorId], references: [id])
authorId Int
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
スカラーフィールドは、@relation
属性のfields
で使用されると、リレーションスカラーフィールドになります。
リレーションスカラーフィールド
リレーションスカラーフィールドの命名規則
リレーションスカラーフィールドは常にリレーションフィールドに属するため、次の命名規則が一般的です
- リレーションフィールド:
author
- リレーションスカラーフィールド:
authorId
(リレーションフィールド名 +Id
)
@relation
属性
@relation
属性は、リレーションフィールドにのみ適用でき、スカラーフィールドには適用できません。
@relation
属性は、次の場合に必須です
- 1対1または1対多リレーションを定義する場合、リレーションの片側(対応するリレーションスカラーフィールドを使用)で必須です
- リレーションを明確にする必要がある場合(たとえば、同じモデル間に2つのリレーションがある場合など)
- 自己リレーションを定義する場合
- MongoDBの多対多リレーションを定義する場合
- リレーションテーブルが基盤となるデータベースでどのように表現されるかを制御する必要がある場合(例:リレーションテーブルに特定の名前を使用するなど)
注:リレーショナルデータベースの暗黙的な多対多リレーションは、
@relation
属性を必要としません。
リレーションの明確化
同じ2つのモデル間に2つのリレーションを定義する場合、@relation
属性にname
引数を追加して、それらを明確にする必要があります。なぜそれが必要なのかの例として、次のモデルを考えてみましょう
- リレーショナルデータベース
- MongoDB
// 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?
}
// NOTE: This schema is intentionally incorrect. See below for a working solution.
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
writtenPosts Post[]
pinnedPost Post?
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String?
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
pinnedBy User? @relation(fields: [pinnedById], references: [id])
pinnedById String? @db.ObjectId
}
その場合、リレーションはあいまいであり、それらを解釈する方法は4つあります
User.writtenPosts
↔Post.author
+Post.authorId
User.writtenPosts
↔Post.pinnedBy
+Post.pinnedById
User.pinnedPost
↔Post.author
+Post.authorId
User.pinnedPost
↔Post.pinnedBy
+Post.pinnedById
これらのリレーションを明確にするには、@relation
属性でリレーションフィールドに注釈を付け、name
引数を指定する必要があります。任意のname
(空文字列""
を除く)を設定できますが、リレーションの両側で同じである必要があります
- リレーショナルデータベース
- MongoDB
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
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
writtenPosts Post[] @relation("WrittenPosts")
pinnedPost Post? @relation("PinnedPost")
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String?
author User @relation("WrittenPosts", fields: [authorId], references: [id])
authorId String @db.ObjectId
pinnedBy User? @relation("PinnedPost", fields: [pinnedById], references: [id])
pinnedById String? @unique @db.ObjectId
}