prisma-database-querying
SKILL.md
Prisma 7 Database Querying (PostgreSQL)
You are an expert in efficient querying with Prisma 7 for Postgres.
Activation cues
Use this skill when the user asks about:
- Prisma
findMany/findFirst/findUnique,include/select, filtering, ordering, pagination - transactions, concurrency, batching
- performance optimization, N+1 issues, large reads
- when/how to use
$queryRaw/$executeRaw - counts/aggregations/grouping where ORM becomes awkward or slow
Default policy (important)
- Mutations (create/update/delete/upsert): use Prisma Client ORM by default.
- Reads (SELECT/COUNT/analytics):
- Use Prisma Client first.
- Switch to raw SQL when Prisma cannot express the query cleanly, or when SQL can significantly improve performance (CTEs, window functions, custom joins, partial indexes usage, advanced grouping).
Read patterns to prefer in Prisma Client
- Always scope fields using
select(or narrowly scopedinclude) to avoid overfetching. - Use cursor-based pagination for large tables:
take,skiponly for small datasets; cursor for high-scale.
- Use
distinct,groupBy, aggregates where they fit. - Avoid N+1: query relations with
includeor two-step queries withinfilters.
Raw SQL rules
Use Prisma’s parameterized raw queries:
$queryRawfor SELECT-like reads.$executeRawfor commands that return affected rows (never for SELECT).
Never build SQL strings from untrusted input. If you must do dynamic SQL, build the structure from safe enums/whitelists and pass user data as parameters.
(See Prisma raw SQL docs in references/PRISMA7_CORE_REFERENCES.md.)
Transaction guidance
- Use
$transactionfor multi-step writes that must be atomic. - Prefer short transactions; avoid long-running SELECTs inside write transactions unless required.
Output format
When the user asks for a query, provide:
- The recommended Prisma Client query (or raw SQL if justified)
- Notes on indexes and expected query plan assumptions
- Pagination strategy if results can be large
Examples
Example: efficient list endpoint with cursor pagination
// Input: { workspaceId, cursorId?: string, take?: number }
const take = Math.min(input.take ?? 50, 200);
const items = await prisma.project.findMany({
where: { workspaceId: input.workspaceId },
orderBy: { createdAt: "desc" },
take: take + 1,
...(input.cursorId
? { cursor: { id: input.cursorId }, skip: 1 }
: {}),
select: {
id: true,
name: true,
slug: true,
createdAt: true,
},
});
const hasNextPage = items.length > take;
const page = hasNextPage ? items.slice(0, take) : items;
const nextCursor = hasNextPage ? page[page.length - 1]!.id : null;
Example: COUNT with complex join via raw SQL (read path)
import { Prisma } from "@prisma/client";
const rows = await prisma.$queryRaw<{ total: bigint }[]>`
SELECT COUNT(*)::bigint AS total
FROM "Project" p
JOIN "Workspace" w ON w.id = p."workspaceId"
WHERE w.id = ${input.workspaceId}
AND p."createdAt" >= ${input.since}
`;
const total = Number(rows[0]?.total ?? 0n);
Example: mutation stays in Prisma Client (write path)
await prisma.project.update({
where: { id: input.projectId },
data: { name: input.name, slug: input.slug },
});
Common pitfalls to warn about
- Mixing
selectandincludeincorrectly: choose one strategy; if you need relations and partial scalars, structure the query accordingly. - Using
$executeRawfor SELECT: it returns affected rows, not data. - Using
skip/takeoffsets on large tables: can become slow; use cursor.
Additional resources
- For complete Prisma docs details, see reference.md
Weekly Installs
3
Repository
madsnyl/t3-templateGitHub Stars
1
First Seen
Feb 21, 2026
Security Audits
Installed on
opencode3
gemini-cli3
github-copilot3
codex3
kimi-cli3
amp3