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

リレーションクエリ

Prisma Clientの重要な機能は、2つ以上のモデル間のリレーションをクエリできることです。リレーションクエリには以下が含まれます。

Prisma Clientには、リレーションをトラバースするためのfluent APIもあります。

ネストされた読み取り

ネストされた読み取りを使用すると、ユーザーとそのユーザーの投稿など、データベース内の複数のテーブルから関連データを読み取ることができます。以下のことができます。

  • include を使用して、クエリのレスポンスにユーザーの投稿やプロフィールなどの関連レコードを含めます。
  • ネストされた select を使用して、関連レコードから特定のフィールドを含めます。include の内側に select をネストすることもできます。

リレーションロード戦略(プレビュー)

バージョン 5.8.0 以降、PostgreSQLデータベースの場合、relationLoadStrategy オプションを介して、リレーションクエリをどのように実行するか(つまり、どのようなロード戦略を適用するか)をクエリレベルごとに決定できます。

バージョン 5.10.0 以降、この機能はMySQLでも利用可能です。

relationLoadStrategy オプションは現在プレビュー段階であるため、Prismaスキーマファイルで relationJoins プレビュー機能フラグを有効にする必要があります。

schema.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["relationJoins"]
}

このフラグを追加した後、Prisma Clientを再生成するために prisma generate を再度実行する必要があります。relationJoins 機能は現在、PostgreSQL、CockroachDB、MySQLで利用可能です。

Prisma Clientは、リレーションに対して2つのロード戦略をサポートしています。

  • join (デフォルト): データベースレベルの LATERAL JOIN (PostgreSQL) または相関サブクエリ (MySQL) を使用し、データベースへの単一のクエリですべてのデータをフェッチします。
  • query: 複数のクエリをデータベースに送信し (テーブルごとに1つ)、アプリケーションレベルで結合します。

これら2つのオプションのもう1つの重要な違いは、join 戦略がデータベースレベルでJSON集計を使用することです。つまり、Prisma Clientによって返されるJSON構造をデータベース内で既に作成するため、アプリケーションレベルでの計算リソースを節約できます。

注意: relationLoadStrategyプレビュー から 一般提供 (GA) に移行すると、join はすべてのリレーションクエリのデフォルトとして普遍的に使用されるようになります。

relationLoadStrategy オプションは、include または select をサポートする任意のクエリの最上位で使用できます。

include を使用した例を以下に示します。

const users = await prisma.user.findMany({
relationLoadStrategy: 'join', // or 'query'
include: {
posts: true,
},
})

select を使用した別の例を以下に示します。

const users = await prisma.user.findMany({
relationLoadStrategy: 'join', // or 'query'
select: {
posts: true,
},
})

どちらのロード戦略をいつ使用すべきか?

  • join 戦略(デフォルト)は、ほとんどのシナリオでより効果的です。PostgreSQLでは、LATERAL JOIN と JSON 集計の組み合わせを使用して、結果セットの冗長性を減らし、クエリ結果を期待されるJSON構造に変換する作業をデータベースサーバーに委任します。MySQLでは、相関サブクエリを使用して、単一のクエリで結果をフェッチします。
  • データセットとクエリの特性によっては、query がよりパフォーマンスが高くなるエッジケースが存在する可能性があります。これらの状況を特定するために、データベースクエリをプロファイルすることを推奨します。
  • データベースサーバーのリソースを節約し、アプリケーションサーバーでデータのマージと変換の重労働を行いたい場合は、query を使用してください。アプリケーションサーバーの方がスケールしやすい場合があります。

リレーションを含める

次の例では、単一のユーザーと、そのユーザーの投稿を返します。

const user = await prisma.user.findFirst({
include: {
posts: true,
},
})
表示クエリ結果

特定のリレーションのすべてのフィールドを含める

次の例では、投稿とその作成者を返します。

const post = await prisma.post.findFirst({
include: {
author: true,
},
})
表示クエリ結果

深くネストされたリレーションを含める

include オプションをネストして、リレーションのリレーションを含めることができます。次の例では、ユーザーの投稿と、各投稿のカテゴリを返します。

const user = await prisma.user.findFirst({
include: {
posts: {
include: {
categories: true,
},
},
},
})
表示クエリ結果

含まれるリレーションの特定のフィールドを選択する

ネストされた select を使用して、返すリレーションのフィールドのサブセットを選択できます。たとえば、次のクエリは、ユーザーの name と、関連する各投稿の title を返します。

const user = await prisma.user.findFirst({
select: {
name: true,
posts: {
select: {
title: true,
},
},
},
})
表示クエリ結果

include の内側に select をネストすることもできます。次の例では、すべての User フィールドと、各投稿の title フィールドを返します。

const user = await prisma.user.findFirst({
include: {
posts: {
select: {
title: true,
},
},
},
})
表示クエリ結果

selectinclude同じレベルで使用できないことに注意してください。これは、ユーザーの投稿を include し、各投稿のタイトルを select する場合、ユーザーの email のみを select することはできないことを意味します。

// The following query returns an exception
const user = await prisma.user.findFirst({
select: { // This won't work!
email: true
}
include: { // This won't work!
posts: {
select: {
title: true
}
}
},
})
表示CLI結果

代わりに、ネストされた select オプションを使用してください。

const user = await prisma.user.findFirst({
select: {
// This will work!
email: true,
posts: {
select: {
title: true,
},
},
},
})

リレーション数

3.0.1 以降では、フィールドとともにリレーションの数を include または selectできます。たとえば、ユーザーの投稿数などです。

const relationCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
表示クエリ結果

リレーションのリストをフィルタリングする

select または include を使用して関連データのサブセットを返す場合、select または include 内でリレーションのリストをフィルタリングおよびソートできます。

たとえば、次のクエリは、ユーザーに関連付けられた未公開の投稿のタイトルのリストを返します。

const result = await prisma.user.findFirst({
select: {
posts: {
where: {
published: false,
},
orderBy: {
title: 'asc',
},
select: {
title: true,
},
},
},
})

include を使用して同じクエリを次のように記述することもできます。

const result = await prisma.user.findFirst({
include: {
posts: {
where: {
published: false,
},
orderBy: {
title: 'asc',
},
},
},
})

ネストされた書き込み

ネストされた書き込みを使用すると、単一のトランザクションでデータベースにリレーショナルデータを書き込むことができます。

ネストされた書き込み

  • 単一のPrisma Clientクエリで複数のテーブルにわたるデータの作成、更新、または削除に対してトランザクション保証を提供します。クエリの一部が失敗した場合(たとえば、ユーザーの作成は成功したが、投稿の作成が失敗した場合)、Prisma Clientはすべての変更をロールバックします。
  • データモデルでサポートされている任意のレベルのネストをサポートします。
  • モデルのcreateまたはupdateクエリを使用する場合、リレーションフィールドで使用できます。次のセクションでは、クエリごとに使用可能なネストされた書き込みオプションを示します。

レコードと1つ以上の関連レコードを同時に作成できます。次のクエリは、User レコードと2つの関連する Post レコードを作成します。

const result = await prisma.user.create({
data: {
email: 'elsa@prisma.io',
name: 'Elsa Prisma',
posts: {
create: [
{ title: 'How to make an omelette' },
{ title: 'How to eat an omelette' },
],
},
},
include: {
posts: true, // Include all posts in the returned object
},
})
表示クエリ結果

単一のレコードと複数の関連レコードを作成または更新する方法は2つあります。たとえば、複数の投稿を持つユーザーなどです。

  • ネストされた create クエリを使用する
  • ネストされた createMany クエリを使用する

ほとんどの場合、skipDuplicates クエリオプションが必要でない限り、ネストされた create が推奨されます。2つのオプションの違いを説明する簡単な表を以下に示します。

機能createcreateMany
追加のリレーションのネストをサポート✘ *たとえば、1つのクエリでユーザー、複数の投稿、および投稿ごとの複数のコメントを作成できます。
* たとえば、has-oneリレーションで外部キーを手動で設定できます: { authorId: 9}
1-nリレーションをサポートたとえば、ユーザーと複数の投稿を作成できます(1人のユーザーが多数の投稿を持つ)。
m-nリレーションをサポートたとえば、投稿と複数のカテゴリを作成できます(1つの投稿は複数のカテゴリを持つことができ、1つのカテゴリは複数の投稿を持つことができる)。
重複レコードのスキップをサポートskipDuplicates クエリオプションを使用します。

ネストされた create の使用

次のクエリでは、ネストされた create を使用して作成します。

  • 1人のユーザー
  • 2つの投稿
  • 1つの投稿カテゴリ

この例では、ネストされた include を使用して、返されるデータにすべての投稿と投稿カテゴリを含めています。

const result = await prisma.user.create({
data: {
email: 'yvette@prisma.io',
name: 'Yvette',
posts: {
create: [
{
title: 'How to make an omelette',
categories: {
create: {
name: 'Easy cooking',
},
},
},
{ title: 'How to eat an omelette' },
],
},
},
include: {
// Include posts
posts: {
include: {
categories: true, // Include post categories
},
},
},
})
表示クエリ結果

ネストされたcreate操作がデータベース内の複数のテーブルに一度に書き込む方法の視覚的な表現を以下に示します。

ネストされた createMany の使用

次のクエリでは、ネストされた createMany を使用して作成します。

  • 1人のユーザー
  • 2つの投稿

この例では、ネストされた include を使用して、返されるデータにすべての投稿を含めています。

const result = await prisma.user.create({
data: {
email: 'saanvi@prisma.io',
posts: {
createMany: {
data: [{ title: 'My first post' }, { title: 'My second post' }],
},
},
},
include: {
posts: true,
},
})
表示クエリ結果

強調表示されたクエリの内側に、追加の create または createMany をネストすることはできません。つまり、ユーザー、投稿、および投稿カテゴリを同時に作成することはできません。

回避策として、最初に関連付けられるレコードを作成するクエリを送信し、次いで実際​​のレコードを作成できます。例:

const categories = await prisma.category.createManyAndReturn({
data: [
{ name: 'Fun', },
{ name: 'Technology', },
{ name: 'Sports', }
],
select: {
id: true
}
});

const posts = await prisma.post.createManyAndReturn({
data: [{
title: "Funniest moments in 2024",
categoryId: categories[0].id
}, {
title: "Linux or macOS — what's better?",
categoryId: categories[1].id
},
{
title: "Who will win the next soccer championship?",
categoryId: categories[2].id
}]
});

すべてのレコードを単一のデータベースクエリで作成する場合は、$transaction または 型安全な生のSQLを使用することを検討してください。

createMany() または createManyAndReturn() クエリでリレーションにアクセスすることはできません。つまり、単一のネストされた書き込みで複数のユーザーと複数の投稿を作成することはできません。次は不可能です。

const createMany = await prisma.user.createMany({
data: [
{
name: 'Yewande',
email: 'yewande@prisma.io',
posts: {
// Not possible to create posts!
},
},
{
name: 'Noor',
email: 'noor@prisma.io',
posts: {
// Not possible to create posts!
},
},
],
})

複数のレコードを接続する

次のクエリは、新しい User レコードを (create ) 作成し、そのレコードを (connect ) 3つの既存の投稿に接続します。

const result = await prisma.user.create({
data: {
email: 'vlad@prisma.io',
posts: {
connect: [{ id: 8 }, { id: 9 }, { id: 10 }],
},
},
include: {
posts: true, // Include all posts in the returned object
},
})
表示クエリ結果

注意: いずれかの投稿レコードが見つからない場合、Prisma Clientは例外をスローします: connect: [{ id: 8 }, { id: 9 }, { id: 10 }]

単一のレコードを接続する

connect 既存のレコードを新規または既存のユーザーに接続できます。次のクエリは、既存の投稿 (id: 11) を既存のユーザー (id: 9) に接続します。

const result = await prisma.user.update({
where: {
id: 9,
},
data: {
posts: {
connect: {
id: 11,
},
},
},
include: {
posts: true,
},
})

レコードを接続または作成する

関連レコードが既に存在する場合と存在しない場合がある場合は、connectOrCreate を使用して関連レコードを接続します。

  • メールアドレス viola@prisma.ioUser を接続または
  • ユーザーがまだ存在しない場合は、メールアドレス viola@prisma.io で新しい User を作成します。
const result = await prisma.post.create({
data: {
title: 'How to make croissants',
author: {
connectOrCreate: {
where: {
email: 'viola@prisma.io',
},
create: {
email: 'viola@prisma.io',
name: 'Viola',
},
},
},
},
include: {
author: true,
},
})
表示クエリ結果

レコードのリストから1つを disconnect するには(たとえば、特定のブログ投稿)、切断するレコードのIDまたは一意の識別子を提供します。

const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
disconnect: [{ id: 12 }, { id: 19 }],
},
},
include: {
posts: true,
},
})
表示クエリ結果

1つのレコードを disconnect するには(たとえば、投稿の作成者)、disconnect: true を使用します。

const result = await prisma.post.update({
where: {
id: 23,
},
data: {
author: {
disconnect: true,
},
},
include: {
author: true,
},
})
表示クエリ結果

1対多リレーション(ユーザーが多数の投稿を持っている)のすべての関連レコードをdisconnectするには、以下に示すように、リレーションを空のリストに set します。

const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
set: [],
},
},
include: {
posts: true,
},
})
表示クエリ結果

すべての関連する Post レコードを削除します。

const result = await prisma.user.update({
where: {
id: 11,
},
data: {
posts: {
deleteMany: {},
},
},
include: {
posts: true,
},
})

すべての未公開の投稿を削除してユーザーを更新します。

const result = await prisma.user.update({
where: {
id: 11,
},
data: {
posts: {
deleteMany: {
published: false,
},
},
},
include: {
posts: true,
},
})

特定の投稿を削除してユーザーを更新します。

const result = await prisma.user.update({
where: {
id: 6,
},
data: {
posts: {
deleteMany: [{ id: 7 }],
},
},
include: {
posts: true,
},
})

ネストされた updateMany を使用して、特定のユーザーのすべての関連レコードを更新できます。次のクエリは、特定のユーザーのすべての投稿を未公開にします。

const result = await prisma.user.update({
where: {
id: 6,
},
data: {
posts: {
updateMany: {
where: {
published: true,
},
data: {
published: false,
},
},
},
},
include: {
posts: true,
},
})
const result = await prisma.user.update({
where: {
id: 6,
},
data: {
posts: {
update: {
where: {
id: 9,
},
data: {
title: 'My updated title',
},
},
},
},
include: {
posts: true,
},
})

次のクエリでは、ネストされた upsert を使用して、ユーザーが存在する場合は "bob@prisma.io" を更新し、存在しない場合はユーザーを作成します。

const result = await prisma.post.update({
where: {
id: 6,
},
data: {
author: {
upsert: {
create: {
email: 'bob@prisma.io',
name: 'Bob the New User',
},
update: {
email: 'bob@prisma.io',
name: 'Bob the existing user',
},
},
},
},
include: {
author: true,
},
})

update 内に create または createMany をネストして、既存のレコードに新しい関連レコードを追加できます。次のクエリは、id が 9 のユーザーに2つの投稿を追加します。

const result = await prisma.user.update({
where: {
id: 9,
},
data: {
posts: {
createMany: {
data: [{ title: 'My first post' }, { title: 'My second post' }],
},
},
},
include: {
posts: true,
},
})

リレーションフィルター

「多対多」リレーションのフィルタリング

Prisma Clientは、リレーションの「多対多」側の関連レコードのプロパティでレコードをフィルタリングするために、someevery、および none オプションを提供します。たとえば、投稿のプロパティに基づいてユーザーをフィルタリングするなどです。

例:

要件使用するクエリオプション
「未公開の Post レコードを少なくとも1つ持つすべての User のリストが必要です」some 投稿が未公開
「未公開の Post レコードを1つも持たないすべての User のリストが必要です」投稿の none が未公開
「未公開の Post レコードのみを持つすべての User のリストが必要です」every 投稿が未公開

たとえば、次のクエリは、次の基準を満たす User を返します。

  • 100回以上の閲覧数を持つ投稿がない
  • すべての投稿のいいね数が50以下
const users = await prisma.user.findMany({
where: {
posts: {
none: {
views: {
gt: 100,
},
},
every: {
likes: {
lte: 50,
},
},
},
},
include: {
posts: true,
},
})

「一対一」リレーションのフィルタリング

Prisma Clientは、リレーションの「一対一」側の関連レコードのプロパティでレコードをフィルタリングするために、is および isNot オプションを提供します。たとえば、作成者のプロパティに基づいて投稿をフィルタリングするなどです。

たとえば、次のクエリは、次の基準を満たす Post レコードを返します。

  • 作成者の名前がBobではない
  • 作成者が40歳より年上である
const users = await prisma.post.findMany({
where: {
author: {
isNot: {
name: 'Bob',
},
is: {
age: {
gt: 40,
},
},
},
},
include: {
author: true,
},
})

「多対多」レコードの不在によるフィルタリング

たとえば、次のクエリでは none を使用して、投稿がゼロのすべてのユーザーを返します。

const usersWithZeroPosts = await prisma.user.findMany({
where: {
posts: {
none: {},
},
},
include: {
posts: true,
},
})

「一対一」リレーションの不在によるフィルタリング

次のクエリは、作成者リレーションを持たないすべての投稿を返します。

const postsWithNoAuthor = await prisma.post.findMany({
where: {
author: null, // or author: { }
},
include: {
author: true,
},
})

次のクエリは、少なくとも1つの投稿を持つすべてのユーザーを返します。

const usersWithSomePosts = await prisma.user.findMany({
where: {
posts: {
some: {},
},
},
include: {
posts: true,
},
})

Fluent API

fluent APIを使用すると、関数呼び出しを介してモデルのリレーションfluentにトラバースできます。最後の関数呼び出しがクエリ全体の戻り値の型を決定することに注意してください(それぞれの型アノテーションは、それを明確にするために以下のコードスニペットに追加されています)。

このクエリは、特定の User によるすべての Post レコードを返します。

const postsByUser: Post[] = await prisma.user
.findUnique({ where: { email: 'alice@prisma.io' } })
.posts()

これは、次の findMany クエリと同等です。

const postsByUser = await prisma.post.findMany({
where: {
author: {
email: 'alice@prisma.io',
},
},
})

クエリの主な違いは、fluent API呼び出しが2つの別々のデータベースクエリに変換されるのに対し、もう1つは単一のクエリのみを生成することです(このGitHub issue)を参照してください。

注意: .findUnique({ where: { email: 'alice@prisma.io' } }).posts() クエリはPrisma ClientのPrisma dataloaderによって自動的にバッチ処理されるという事実を利用して、GraphQLリゾルバーでn+1問題を回避できます。

このリクエストは、特定の投稿によるすべてのカテゴリを返します。

const categoriesOfPost: Category[] = await prisma.post
.findUnique({ where: { id: 1 } })
.categories()

必要なだけ多くのクエリをチェーンできることに注意してください。この例では、チェーンは Profile から始まり、User を経由して Post に移動します。

const posts: Post[] = await prisma.profile
.findUnique({ where: { id: 1 } })
.user()
.posts()

チェーンの唯一の要件は、前の関数呼び出しが単一のオブジェクトのみを返す必要があることです(例:findUnique クエリまたは profile.user() のような「一対一リレーション」によって返されるオブジェクト)。

次のクエリは、findMany が単一のオブジェクトではなくリストを返すため、不可能です

// This query is illegal
const posts = await prisma.user.findMany().posts()