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

リレーションクエリ

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

Prisma Clientには、リレーションを走査するためのFluent APIもあります。

ネストされた読み取り

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

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

リレーション読み込み戦略 (プレビュー)

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

バージョン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プレビューから一般提供に移行すると、すべてのリレーションクエリで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 JOINsと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,
},
},
},
})
表示クエリ結果

selectincludeの中にネストすることもできます。次の例は、すべての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はすべての変更をロールバックします。
  • データモデルがサポートする任意のレベルのネストをサポートします。
  • モデルの作成または更新クエリを使用する際に、リレーションフィールドで利用できます。以下のセクションでは、クエリごとに利用可能なネストされた書き込みオプションを示します。

レコードと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つのクエリで作成できます。
* 1対1リレーションでは、外部キーを手動で設定できます(例:{ authorId: 9})。
1対多リレーションをサポートたとえば、ユーザーと複数の投稿を作成できます(1人のユーザーは多くの投稿を持つ)。
多対多リレーションをサポートたとえば、投稿と複数のカテゴリを作成できます (1つの投稿は多くのカテゴリを持つことができ、1つのカテゴリは多くの投稿を持つことができます)。
重複レコードのスキップをサポートskipDuplicatesクエリオプションを使用してください。

ネストされたcreateの使用

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

  • 一人のユーザー
  • 二つの投稿
  • 一つの投稿カテゴリ

この例では、返されるデータにすべての投稿と投稿カテゴリを含めるために、ネストされた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
},
},
},
})
表示クエリ結果

以下は、ネストされた作成操作がデータベース内の複数のテーブルに一度に書き込む方法の視覚的表現です。

ネストされたcreateManyの使用

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

  • 一人のユーザー
  • 二つの投稿

この例では、返されるデータにすべての投稿を含めるために、ネストされた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.filter(category => category.name === 'Fun')!.id
}, {
title: "Linux or macOS — what's better?",
categoryId: categories.filter(category => category.name === 'Technology')!.id
},
{
title: "Who will win the next soccer championship?",
categoryId: categories.filter(category => category.name === 'Sports')!.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 posts are unpublished (いくつかの投稿が未公開)
「未公開のPostレコードを持たないすべてのUserのリストが欲しい」none of the posts are unpublished (投稿のどれも未公開ではない)
未公開のPostレコードのみを持つすべてのUserのリストが欲しい」every post is unpublished (すべての投稿が未公開)

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

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

"一"リレーションでフィルタリングする

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

たとえば、次のクエリは、以下の基準を満たす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を使用すると、関数呼び出しを介してモデルのリレーション流れるようにたどることができます。最後の関数呼び出しがクエリ全体の戻り型を決定することに注意してください(そのことを明確にするために、以下のコードスニペットにはそれぞれの型アノテーションが追加されています)。

このクエリは、特定の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イシューを参照)。

: .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()
© . All rights reserved.