The Prisma P1016 error occurs when using raw SQL queries with parameter placeholders that don't match the number of provided values. This happens when you have mismatched placeholders (like $1, $2) and the actual parameter array length. The fix involves carefully counting placeholders and ensuring each has a corresponding value.
The P1016 error in Prisma indicates a mismatch between the number of parameter placeholders in your raw SQL query and the number of values you've provided in the parameters array. When using Prisma's `$queryRaw` or `$executeRaw` methods, you use placeholders like $1, $2, $3, etc., and provide corresponding values in an array. This error is a safety feature that prevents SQL injection and ensures query integrity. Unlike application-level errors, this is caught by Prisma's query preparation layer before the query is sent to the database. The error typically occurs when: - You add or remove placeholders in the SQL string but forget to update the parameters array - You have typos in placeholder numbering (skipping numbers like $1, $3) - You use conditional logic that changes the SQL string but not the parameters - You're dynamically building SQL queries and miscounting placeholders Prisma uses numbered parameters ($1, $2, etc.) for PostgreSQL and positional parameters (?) for MySQL and SQLite. The error message will specify which database you're using and show the mismatch.
First, carefully count the number of placeholders in your SQL string and compare with your parameters array:
// ❌ WRONG: 3 placeholders but only 2 parameters
const result = await prisma.$queryRaw`
SELECT * FROM users
WHERE age > $1
AND status = $2
AND created_at > $3
`, [18, 'active']; // Missing third parameter!
// ✅ CORRECT: 3 placeholders, 3 parameters
const result = await prisma.$queryRaw`
SELECT * FROM users
WHERE age > $1
AND status = $2
AND created_at > $3
`, [18, 'active', new Date('2024-01-01')];
// For tagged template syntax, parameters come after the SQL stringCount carefully:
1. Look for all $1, $2, $3, etc., placeholders
2. Check your parameters array length
3. Ensure they match exactly
Ensure your placeholders are numbered sequentially without gaps:
// ❌ WRONG: Missing $2, jumps from $1 to $3
const result = await prisma.$queryRaw`
SELECT * FROM users
WHERE id = $1
OR email = $3 // Should be $2!
`, [123, '[email protected]'];
// ✅ CORRECT: Sequential numbering $1, $2
const result = await prisma.$queryRaw`
SELECT * FROM users
WHERE id = $1
OR email = $2
`, [123, '[email protected]'];
// ❌ WRONG: Duplicate $1 placeholder
const result = await prisma.$queryRaw`
SELECT * FROM users
WHERE id = $1
AND parent_id = $1 // Should be $2 if different value
`, [123];
// ✅ CORRECT: Use different placeholders for different values
const result = await prisma.$queryRaw`
SELECT * FROM users
WHERE id = $1
AND parent_id = $2
`, [123, 456];Remember: Placeholders must be unique and sequential starting from 1.
Different databases use different parameter syntax. Prisma handles this automatically, but you need to use the correct Prisma method:
// PostgreSQL: Uses $1, $2, $3...
const pgResult = await prisma.$queryRaw`
SELECT * FROM users WHERE id = $1 AND status = $2
`, [123, 'active'];
// MySQL: Uses ? for positional parameters
const mysqlResult = await prisma.$queryRaw(
`SELECT * FROM users WHERE id = ? AND status = ?`,
[123, 'active']
);
// SQLite: Also uses ? for positional parameters
const sqliteResult = await prisma.$queryRaw(
`SELECT * FROM users WHERE id = ? AND status = ?`,
[123, 'active']
);
// Using the wrong syntax will cause P1016
// ❌ WRONG for MySQL: Using $1 instead of ?
const wrong = await prisma.$queryRaw(
`SELECT * FROM users WHERE id = $1`, // Should be ?
[123]
);Prisma automatically uses the correct syntax based on your database connection, but you must use the appropriate method signature.
If you're building SQL dynamically, ensure placeholders and parameters stay synchronized:
// ❌ PROBLEMATIC: Dynamic SQL with mismatched placeholders
async function searchUsers(filters: any) {
let sql = 'SELECT * FROM users WHERE 1=1';
const params: any[] = [];
let paramCount = 0;
if (filters.name) {
sql += ` AND name = $${++paramCount}`; // $1
params.push(filters.name);
}
if (filters.age) {
sql += ` AND age > $${++paramCount}`; // $2
params.push(filters.age);
}
if (filters.status) {
sql += ` AND status = $${++paramCount}`; // $3
params.push(filters.status);
}
// ❌ If filters.status is undefined, we have $3 placeholder but only 2 params
return prisma.$queryRawUnsafe(sql, ...params);
}
// ✅ CORRECT: Build SQL and params together
async function searchUsers(filters: any) {
const conditions: string[] = [];
const params: any[] = [];
if (filters.name) {
conditions.push(`name = $${params.length + 1}`);
params.push(filters.name);
}
if (filters.age) {
conditions.push(`age > $${params.length + 1}`);
params.push(filters.age);
}
if (filters.status) {
conditions.push(`status = $${params.length + 1}`);
params.push(filters.status);
}
const whereClause = conditions.length > 0
? `WHERE ${conditions.join(' AND ')}`
: '';
const sql = `SELECT * FROM users ${whereClause}`;
return prisma.$queryRawUnsafe(sql, ...params);
}Always build placeholders and parameters together to keep them synchronized.
Consider using Prisma's type-safe raw query methods when possible:
// Using $queryRaw with template literals (type-safe for PostgreSQL)
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM users
WHERE age > ${18}
AND status = ${'active'}
`;
// Using $queryRaw with positional parameters
const users = await prisma.$queryRaw<User[]>(
`SELECT * FROM users WHERE age > ? AND status = ?`,
[18, 'active']
);
// Using Prisma.sql helper for complex queries
import { Prisma } from '@prisma/client';
const minAge = 18;
const status = 'active';
const limit = 10;
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM users
WHERE age > ${minAge}
AND status = ${status}
LIMIT ${limit}
`;
// For dynamic WHERE clauses, use Prisma.join
const conditions = [];
if (minAge) conditions.push(Prisma.sql`age > ${minAge}`);
if (status) conditions.push(Prisma.sql`status = ${status}`);
const whereClause = conditions.length > 0
? Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}`
: Prisma.empty;
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM users
${whereClause}
LIMIT ${limit}
`;Prisma.sql automatically handles parameter numbering and prevents P1016 errors.
When debugging P1016 errors, start with a simple query and gradually add complexity:
async function debugRawQuery() {
try {
// 1. Start with simplest possible query
console.log('Test 1: Simple query with one parameter');
const test1 = await prisma.$queryRaw`
SELECT * FROM users WHERE id = $1
`, [1];
console.log('✓ Test 1 passed');
// 2. Add second parameter
console.log('Test 2: Two parameters');
const test2 = await prisma.$queryRaw`
SELECT * FROM users WHERE id = $1 AND status = $2
`, [1, 'active'];
console.log('✓ Test 2 passed');
// 3. Test your actual query with hardcoded values
console.log('Test 3: Your actual query structure');
const hardcodedQuery = await prisma.$queryRaw`
SELECT * FROM users
WHERE age > $1
AND status = $2
AND created_at > $3
ORDER BY $4
LIMIT $5
`, [18, 'active', new Date('2024-01-01'), 'name', 10];
console.log('✓ Test 3 passed - query structure is valid');
// 4. Now test with your dynamic values
console.log('Test 4: With your actual parameters');
const actualParams = [18, 'active', new Date('2024-01-01'), 'name', 10];
console.log('Parameter count:', actualParams.length);
const finalResult = await prisma.$queryRaw`
SELECT * FROM users
WHERE age > $1
AND status = $2
AND created_at > $3
ORDER BY $4
LIMIT $5
`, actualParams;
console.log('✓ All tests passed!');
return finalResult;
} catch (error) {
console.error('Test failed at step:', error.message);
// The error will show which test failed and why
throw error;
}
}This incremental approach helps identify exactly where the parameter mismatch occurs.
Database-Specific Considerations:
- PostgreSQL: Uses $1, $2, $3... and supports named parameters with $queryRawNamed
- MySQL/SQLite: Use ? for positional parameters
- SQL Server: Uses @p1, @p2, @p3... (Prisma handles this automatically)
Performance Implications: While debugging, avoid using $queryRawUnsafe with string concatenation as it can lead to SQL injection. Always use parameterized queries even during development.
Common Pitfalls with Dynamic SQL:
1. Conditional WHERE clauses: When building WHERE clauses conditionally, it's easy to miscount parameters. Use a query builder pattern that tracks both SQL fragments and parameters together.
2. IN clauses: SQL IN clauses with variable numbers of values are particularly tricky:
// ❌ WRONG: Hard to parameterize dynamically
const ids = [1, 2, 3, 4, 5];
const placeholders = ids.map((_, i) => `$${i + 1}`).join(',');
// This creates $1, $2, $3, $4, $5 - must have exactly 5 parameters
// ✅ BETTER: Use ANY() for PostgreSQL
const result = await prisma.$queryRaw`
SELECT * FROM users WHERE id = ANY($1)
`, [ids]; // Single array parameter
// ✅ ALTERNATIVE: Generate placeholders dynamically
function buildInQuery(ids: number[]) {
const placeholders = ids.map((_, i) => `$${i + 1}`).join(',');
return {
sql: `SELECT * FROM users WHERE id IN (${placeholders})`,
params: ids
};
}Transaction Context: When using raw queries inside transactions, parameter counting errors can be harder to debug because the error might occur deep in a transaction chain. Test raw queries outside transactions first.
Type Safety with Prisma.sql: The Prisma.sql tagged template literal is the most robust way to write raw queries because it:
1. Automatically numbers parameters
2. Provides some type checking
3. Prevents SQL injection
4. Makes parameter mismatches impossible
Migration from Other ORMs: If migrating from Sequelize, TypeORM, or other ORMs, note that their raw query APIs might have different parameter conventions. Prisma is strict about parameter counts where other ORMs might be more lenient.
Debugging Tools: For complex queries, consider:
1. Logging the final SQL with parameters substituted (in development only)
2. Using Prisma's query logging: prisma.$on('query', (e) => console.log(e))
3. Testing the SQL directly in your database client with the same parameters
P6005: Invalid parameters (Pulse)
How to fix "P6005: Invalid parameters (Pulse)" in Prisma
P2011: Null constraint violation on the field
How to fix "P2011: Null constraint violation" in Prisma
P2009: Failed to validate the query: {validation_error}
How to fix "P2009: Failed to validate the query" in Prisma
P2007: Data validation error
How to fix "P2007: Data validation error" in Prisma
P1013: The provided database string is invalid
The provided database string is invalid