多対多リレーションのモデリングとクエリ
課題
リレーショナルデータベースにおける多対多リレーションのモデリングとクエリは、難しい場合があります。この記事では、Prisma ORMでこれにどのようにアプローチできるかの2つの例を示します。最初の例では暗黙的な多対多リレーションを使用し、2番目の例では明示的な多対多リレーションを使用します。
解決策
暗黙的なリレーション
これは、Prisma ORMがリレーションテーブルを内部的に処理する多対多リレーションのタイプです。暗黙的な多対多リレーションの基本的な例は次のようになります。
model Post {
id Int @id @default(autoincrement())
title String
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
投稿とそのタグを作成するには、Prisma Clientで次のように記述できます。
await prisma.post.create({
data: {
title: 'Types of relations',
tags: { create: [{ name: 'dev' }, { name: 'prisma' }] },
},
})
上記の例では、次のように投稿をタグとともに直接クエリできます。
await prisma.post.findMany({
include: { tags: true },
})
そして、得られるレスポンスは次のようになります。
[
{
"id": 1,
"title": "Types of relations",
"tags": [
{
"id": 1,
"name": "dev"
},
{
"id": 2,
"name": "prisma"
}
]
}
]
この別のユースケースは、新しいタグを追加するだけでなく、既存のタグを投稿に接続する場合です。この例としては、ユーザーが投稿に新しいタグを作成し、追加する既存のタグも選択した場合です。この場合、次の方法で実行できます。
await prisma.post.update({
where: { id: 1 },
data: {
title: 'Prisma is awesome!',
tags: { set: [{ id: 1 }, { id: 2 }], create: { name: 'typescript' } },
},
})
明示的なリレーション
明示的なリレーションは、リレーションテーブルに追加のフィールドを格納する必要がある場合、または既存のデータベースをイントロスペクションする場合で、すでに多対多リレーションが設定されている場合に主に作成する必要があります。これは、上記と同じスキーマですが、明示的なリレーションテーブルを使用しています。
model Post {
id Int @id @default(autoincrement())
title String
tags PostTags[]
}
model PostTags {
id Int @id @default(autoincrement())
post Post? @relation(fields: [postId], references: [id])
tag Tag? @relation(fields: [tagId], references: [id])
postId Int?
tagId Int?
@@index([postId, tagId])
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts PostTags[]
}
投稿にタグを追加するには、リレーションテーブル(PostTags
)とタグテーブル(Tag
)への作成が必要になります。
await prisma.post.create({
data: {
title: 'Types of relations',
tags: {
create: [
{ tag: { create: { name: 'dev' } } },
{ tag: { create: { name: 'prisma' } } },
],
},
},
})
また、投稿をタグとともにクエリするには、次のように追加のinclude
が必要になります。
await prisma.post.findMany({
include: { tags: { include: { tag: true } } },
})
これにより、次の出力が得られます。
[
{
"id": 1,
"title": "Types of relations",
"tags": [
{
"id": 1,
"postId": 1,
"tagId": 1,
"tag": {
"id": 1,
"name": "prisma"
}
},
{
"id": 2,
"postId": 1,
"tagId": 2,
"tag": {
"id": 2,
"name": "dev"
}
}
]
}
]
UIでリレーションテーブルのデータを表示することが理想的でない場合があります。この場合、サーバー自体でデータをフェッチした後にマッピングし、そのレスポンスをフロントエンドに送信するのが最適です。
const result = posts.map((post) => {
return { ...post, tags: post.tags.map((tag) => tag.tag) }
})
これにより、暗黙的なリレーションで受信した場合と同様の出力が得られます。
[
{
"id": 1,
"title": "Types of relations",
"tags": [
{
"id": 1,
"name": "prisma"
},
{
"id": 2,
"name": "dev"
}
]
}
]
この記事では、暗黙的および明示的な多対多リレーションを実装し、Prisma Clientを使用してクエリする方法を示しました。