何が違うか
select | include | |
|---|---|---|
| 役割 | 取得フィールドを明示的に絞る | リレーション先モデルを展開する |
| 通常フィールドの返し方 | 指定したものだけ | すべて返る |
| リレーション | ネスト select で絞れる | 全フィールドが返る(デフォルト) |
| 同一レベルでの併用 | 不可 | 不可 |
| 型推論 | 絞り込まれた型が自動生成される | 展開したリレーションを含む型が生成される |
// select: id と title だけ取得
const posts = await prisma.post.findMany({
select: { id: true, title: true },
});
// include: 全フィールド + author を展開
const posts = await prisma.post.findMany({
include: { author: true },
});
select が向く場面
- API レスポンスに含めるフィールドを最小限に抑えたいとき
- パスワードハッシュや機密フィールドを誤って返さないようにしたいとき
- リレーション先からも特定フィールドだけほしいとき(ネスト
select)
// author の name だけほしい場合は select + ネスト
prisma.post.findMany({
select: {
id: true,
title: true,
author: { select: { name: true } }, // name だけに絞れる
},
});
include が向く場面
- リレーション先のフィールドをすべて使いたいとき
- ループ外で 1 クエリにまとめて N+1 を回避したいとき
- リレーション件数(
_count)だけ取りたいとき
// posts と著者を 1 クエリで取得(N+1 回避)
prisma.user.findMany({
include: {
posts: {
where: { published: true },
orderBy: { createdAt: "desc" },
take: 5,
},
},
});
over-fetching と N+1 への対処
over-fetching(include を使う場合)
include: { author: true } は著者モデルの全フィールドを返します。email や passwordHash など不要なフィールドが多い場合は select + ネスト select に切り替えます。
// over-fetching になりやすい
include: { author: true }
// フィールドを絞る
select: {
id: true,
author: { select: { name: true, avatarUrl: true } },
}
N+1(include を使わない場合)
ループ内で都度 findUnique を呼ぶと N+1 になります。include または Promise.all でまとめて取得します。
// ❌ N+1: posts.length 回クエリが走る
for (const post of posts) {
const author = await prisma.user.findUnique({ where: { id: post.authorId } });
}
// ✅ 1 クエリで解決
const postsWithAuthor = await prisma.post.findMany({
include: { author: true },
});
ネストが深くなるとき
select / include はネストできますが、3 段以上になると可読性が下がります。深いネストが必要な場合は、クエリを分割して Promise.all で並列実行する方がコードが追いやすくなります。
// ネストが深い例(読みにくくなりがち)
prisma.post.findMany({
include: {
author: {
include: {
profile: {
include: { address: true },
},
},
},
},
});
判断フロー
リレーション先が必要か?
├── No → select のみで OK
└── Yes → リレーション先のフィールドを絞りたいか?
├── No → include: { relation: true }
└── Yes → select + ネスト select