投稿日:2026/7/30
更新日:2026/7/30

Prisma の include は「関連レコードを一緒に取ってくる」指定。ActiveRecord の includes / preload に相当する。
ただの N+1 対策にとどまらず、関連側に where を書けることと戻り値の型が include の内容に応じて変わることが Prisma 独自のポイントになる。
include で明示しないと付いてこない(型にも存在しない)。include の中に where を書けるので、「関連のうち条件に合うものだけ」を取得時点で絞れる。ActiveRecord の preload にはできない芸当。include と select は同じ階層に併記できない。include = 全カラム + リレーション、select = 指定したものだけ。include の内容から自動で決まる。Prisma.UserGetPayload<{ include: ... }> で引数の型を手書きせずに導出できる。const user = await prisma.user.findFirst({ where: { userId } });
// → { userId, familyName, givenName, email, ... }
// customer も orders も入っていない
user.customer はそもそも型に存在しないので、参照するとコンパイルエラーになる。
const user = await prisma.user.findFirst({
where: { userId },
include: { customer: true, orders: true },
});
// → { userId, ..., customer: {...}, orders: [...] }
User.includes(:customer, :orders).find_by(user_id: id)
prisma.user.findFirst({
where: { userId: id },
include: { customer: true, orders: true },
});
挙動も近い。Prisma は既定では JOIN せず、別クエリを投げてアプリ側で結合する。ActiveRecord の preload と同じ方式(relationJoins を有効にすると JOIN になる)。
sequenceDiagram
participant App as アプリ
participant DB as データベース
App->>DB: SELECT * FROM users WHERE user_id = ...
DB-->>App: users 1件
App->>DB: SELECT * FROM customers WHERE user_id IN (...)
DB-->>App: customers
App->>DB: SELECT * FROM orders WHERE user_id IN (...)
DB-->>App: orders
Note over App: アプリ側で user.customer / user.orders に組み立て
目的も同じで、N+1 を避けるため。
// include なし → N+1
const users = await prisma.user.findMany();
for (const user of users) {
// ↓ ユーザーの人数ぶんクエリが飛ぶ
const customer = await prisma.customer.findFirst({ where: { userId: user.userId } });
}
include: {
customer: {
include: {
customerTags: true,
favorites: true,
},
},
}
User → Customer → CustomerTag / Favorite と3階層たどる。ActiveRecord の includes(customer: [:customer_tags, :favorites]) と同じ。
flowchart LR
User["User<br/>(ユーザー)"] --> Customer["Customer<br/>(購入者プロフィール)"]
Customer --> CustomerTag["CustomerTag<br/>(顧客タグ)"]
Customer --> Favorite["Favorite<br/>(お気に入り)"]
Favorite --> Product["Product<br/>(商品)"]
ここが便利なところで、関連側を絞り込める。
favorites: {
where: {
shopId,
product: { isPublished: true }, // ← さらに関連先の条件
},
},
「この購入者のお気に入りのうち、自ショップかつ公開中の商品だけ」を取る。
ActiveRecord で同じことをするなら preload ではできず、joins + 条件か、スコープ付きの association が要る。
取得の時点で絞り込めることに意味がある場面がある。
例えば「CSV に無いお気に入り=解除された」と判定する差分同期を書くとき、非公開商品を取得時点で除外しておかないと、非公開商品まで「解除された」と誤判定してしまう。whereを後段のフィルタではなくクエリに寄せると、この手の事故が減る。
同じ階層ではどちらか一方しか書けない。
| 意味 | |
|---|---|
include |
自分の全カラム + 指定したリレーション |
select |
指定したものだけ(自分のカラムもリレーションも) |
// 必要な列だけ欲しいとき
favorites: {
where: { shopId },
select: { restockNotifyEnabled: true, priceDropNotifyEnabled: true },
}
件数を数えたいだけ、フラグを見たいだけ、というときは全カラムは要らないので select を使う。
Prisma の特徴。include の内容に応じて型が自動で決まる。
const user = await prisma.user.findFirst({ include: { customer: true } });
user.customer // OK
user.orders // コンパイルエラー(include していない)
これを利用すると、関数の引数型を手書きせずに済む。
const activeCustomerInclude = (shopId: string) =>
({
customer: {
include: {
customerTags: true,
favorites: {
where: { shopId, product: { isPublished: true } },
},
},
},
}) satisfies Prisma.UserInclude;
type ActiveCustomerSource = Prisma.UserGetPayload<{
include: ReturnType<typeof activeCustomerInclude>;
}>;
const toActiveCustomer = (source: ActiveCustomerSource) => { /* ... */ };
include の定義を変数に切り出し、Prisma.UserGetPayload でその include から型を導出している。include に項目を足せば引数の型も自動で追随する。satisfies Prisma.UserInclude は「include として妥当か」をその場で検算するため。as と違って型を潰さないので、ReturnType で取り出したときに具体的な形が残る(→ Zodとsatisfiesの併用)。flowchart TD
Def["activeCustomerInclude<br/>(include の定義)"] -->|satisfies で妥当性を検算| Check["Prisma.UserInclude"]
Def -->|ReturnType| RT["include の具体的な型"]
RT -->|UserGetPayload| Payload["ActiveCustomerSource<br/>(戻り値の型)"]
Payload --> Fn["toActiveCustomer の引数型"]
include と select を同じ階層に並べると実行時エラーになる。片方に寄せる。include していないリレーションを参照するとコンパイルエラー。型が守ってくれる代わりに、後から使いたくなったら include 側も直す必要がある。where はあくまで関連側の絞り込みであって、親の絞り込みではない。「公開中の商品をお気に入りに入れているユーザーだけ」を取りたいなら、親の where に some を書く必要がある。// 親を絞りたいときは where 側に some
prisma.user.findMany({
where: { customer: { favorites: { some: { product: { isPublished: true } } } } },
});
take や別クエリへの分割を検討する。