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

多対多リレーションのモデリングとクエリ

課題

リレーショナルデータベースにおける多対多リレーションのモデリングとクエリは、難しい場合があります。この記事では、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を使用してクエリする方法を示しました。