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

リレーション

リレーションとは、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を使用してレコードの取得、作成、更新を行う際にリレーションがどのように現れるかを示しています。

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

以下のクエリは、1つの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に設定します。これにより、id4の投稿がid20のユーザーにリンクされます。

このクエリでは、authorIDの現在の値は関係ありません。クエリは、現在の値にかかわらず、authorID20に変更します。

リレーションの種類

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を持つ必要があります。以下の点に注意してください。

  • 複数フィールドのIDを使用することはできません。
  • @idの代わりに@uniqueを使用することはできません。

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

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

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

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

リレーションフィールド

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

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

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
}
© . All rights reserved.