This error occurs when using parameterized queries in PostgreSQL and the parameter placeholders ($1, $2, etc.) don't match the number of values provided. It typically happens when developers use prepared statements incorrectly, fail to provide all parameter values, or misconfigure their database driver. Ensure parameter counts align and use your driver's parameterized query API correctly.
PostgreSQL error 'Parameter $N does not exist' indicates a mismatch between query parameter placeholders and the actual values supplied to the query. PostgreSQL uses positional parameters ($1, $2, $3, etc.) as placeholders for data values in prepared statements and parameterized queries. When you execute such a query, you must provide an array or list of values that maps to these placeholders in order. This error surfaces when: - The query references a parameter like $5 but only 3 values were provided - A parameter placeholder exists in the SQL but the corresponding value is missing - The parameter array is undefined, null, or empty when values are expected - The driver or connection pool is not configured to handle prepared statements correctly - Using a connection pooler (like PgBouncer) that deallocates statements between requests
Check that your SQL query uses $1, $2, etc. for each parameter you intend to pass:
CORRECT - Using parameter placeholders:
SELECT * FROM users WHERE id = $1 AND status = $2;WRONG - Missing placeholders:
-- This query references $1 but the WHERE clause doesn't have $2
SELECT * FROM users WHERE id = $1;WRONG - Placeholder mismatch:
-- Query has $1, $2, $3 but you'll only provide 2 values
SELECT * FROM users WHERE id = $1 AND username = $2 AND email = $3;Count every $N placeholder in your query and ensure the numbers are sequential starting from $1.
The parameter array must be defined and populated with values matching each placeholder:
Node.js with node-postgres (pg):
// CORRECT - Parameters array provided with matching values
const result = await client.query(
'SELECT * FROM users WHERE id = $1 AND status = $2',
[userId, 'active'] // Must provide 2 values for $1 and $2
);
// WRONG - Parameters not provided
const result = await client.query(
'SELECT * FROM users WHERE id = $1 AND status = $2'
// Missing parameters array - will cause "Parameter $1 does not exist"
);
// WRONG - Undefined or null array
let params = null;
const result = await client.query(
'SELECT * FROM users WHERE id = $1',
params // Will fail because params is null
);Python with psycopg2:
# CORRECT
cursor.execute(
'SELECT * FROM users WHERE id = %s AND status = %s',
(user_id, 'active') # Tuple with matching values
)
# WRONG - Missing tuple
cursor.execute(
'SELECT * FROM users WHERE id = %s AND status = %s'
# No parameters - causes error
)Go with pgx:
// CORRECT
row := conn.QueryRow(ctx, "SELECT * FROM users WHERE id = $1 AND status = $2", userId, "active")
// WRONG - Not passing all required arguments
row := conn.QueryRow(ctx, "SELECT * FROM users WHERE id = $1 AND status = $2")
// Missing userId and "active" argumentsAlways verify that the number of values in your parameters array exactly matches the number of $N placeholders.
Never concatenate user input into SQL strings. Always use your driver's parameterized query API:
Node.js - ALWAYS use the params array:
// SAFE - Parameterized query
const result = await client.query(
'SELECT * FROM users WHERE username = $1',
[username]
);
// UNSAFE - String concatenation (SQL injection risk)
const result = await client.query(
`SELECT * FROM users WHERE username = '${username}'`
);Python with psycopg2:
# SAFE
cursor.execute('SELECT * FROM users WHERE username = %s', (username,))
# UNSAFE
cursor.execute(f'SELECT * FROM users WHERE username = \'{username}\'')Go with pgx:
// SAFE
row := conn.QueryRow(ctx, "SELECT * FROM users WHERE username = $1", username)
// UNSAFE
query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
row := conn.QueryRow(ctx, query)Using parameterized queries also protects against SQL injection attacks.
Connection poolers like PgBouncer can interfere with prepared statements. If you're using PgBouncer:
Option 1: Disable prepared statements in your driver (recommended for pooling):
Node.js with node-postgres:
const { Client } = require('pg');
const client = new Client({
connectionString: 'postgres://...',
statement_timeout: 30000,
});
// PgBouncer should be configured with 'query' mode or 'transaction' modeOption 2: Use PgBouncer with 'session' mode:
In pgbouncer.ini:
[databases]
mydb = host=localhost dbname=mydb port=5432
[pgbouncer]
pool_mode = session # Use session mode, not query or transactionOption 3: Check if prepared statement caching is enabled:
For JDBC connections:
jdbc:postgresql://localhost:5432/dbname?prepareThreshold=0The prepareThreshold=0 disables automatic prepared statement caching, which can conflict with connection pooling. A value > 0 (e.g. 5) means "after 5 executions, use a prepared statement."
Ensure the order of values in your parameters array exactly matches the order of $N placeholders:
CORRECT - Order matches:
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = ['Alice', '[email protected]', 30];
// $1 $2 $3
await client.query(query, params);WRONG - Order doesn't match:
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = ['[email protected]', 'Alice', 30]; // email and name swapped!
// $1 expects name $2 expects email $3 expects age
await client.query(query, params); // Will insert wrong dataWRONG - Missing parameter:
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = ['Alice', '[email protected]']; // Missing $3 (age)
// $1 $2
await client.query(query, params); // Error: Parameter $3 does not existUse named parameters or comments in your code to clarify intent:
const name = 'Alice';
const email = '[email protected]';
const age = 30;
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = [name, email, age];
await client.query(query, params);If you're building queries dynamically, verify parameter indices are sequential:
CORRECT - Sequential indices:
let paramIndex = 1;
const filters = [];
const params = [];
if (userId) {
filters.push(`id = $${paramIndex++}`);
params.push(userId);
}
if (status) {
filters.push(`status = $${paramIndex++}`);
params.push(status);
}
const query = `SELECT * FROM users WHERE ${filters.join(' AND ')}`;
// Result: "SELECT * FROM users WHERE id = $1 AND status = $2"
await client.query(query, params);WRONG - Skipped indices:
const params = [userId, status, email];
const query = `
SELECT * FROM users
WHERE id = $1 AND status = $2 AND role = $4
-- Missing $3, jumped to $4!
`;
await client.query(query, params); // Error: Parameter $4 does not existWhen building dynamic queries, use a counter and increment it for each parameter.
Add logging to see exactly what query and parameters are being sent:
Node.js:
const query = 'SELECT * FROM users WHERE id = $1 AND status = $2';
const params = [userId, 'active'];
console.log('Query:', query);
console.log('Params:', params);
try {
const result = await client.query(query, params);
} catch (err) {
console.error('Query failed:', err.message);
console.error('Expected params: 2, Provided:', params.length);
}Python:
query = 'SELECT * FROM users WHERE id = %s AND status = %s'
params = (user_id, 'active')
print(f'Query: {query}')
print(f'Params: {params}')
try:
cursor.execute(query, params)
except Exception as e:
print(f'Query failed: {e}')
print(f'Expected params: 2, Provided: {len(params)}')The error message will tell you which parameter number doesn't exist (e.g., $5), helping you identify missing values.
Understanding PostgreSQL Parameter Syntax:
PostgreSQL uses positional parameters ($1, $2, ..., $N) for prepared statements and parameterized queries. This is the native syntax that the PostgreSQL wire protocol understands.
Some database drivers translate other placeholder styles to this format:
- Node.js (pg): Uses $1, $2, ... natively
- Python (psycopg2): Uses %s, but translates to $1, $2, ... internally
- Go (pgx): Uses $1, $2, ... natively
- Java (JDBC): Uses ?, but has options to use $1, $2, ... depending on configuration
- Ruby (pg): Uses $1, $2, ... natively
Common Integration Patterns:
1. Prepared Statements in Applications:
When you prepare a statement, you're telling PostgreSQL to parse and plan the query once, then reuse it with different parameter values. This improves performance for repeated queries.
2. Connection Pooling Pitfall:
Connection poolers like PgBouncer can deallocate prepared statements between requests if configured in 'query' or 'transaction' mode. In 'session' mode, the connection is dedicated to a client for the entire session, preserving prepared statements.
3. Parameter vs. Literal:
Parameters are for DATA VALUES ONLY. You cannot use parameters for:
- Table names: SELECT * FROM $1 (WRONG)
- Column names: SELECT $1 FROM users (WRONG)
- SQL keywords: SELECT * FROM users $1 active`` (WRONG)
Only use parameters for the VALUES clause and WHERE predicates.
Debugging Tips:
- Enable PostgreSQL query logging to see what the driver is actually sending:
SET log_min_duration_statement = 0; -- Log all queries- Use EXPLAIN to check parameter binding:
PREPARE stmt AS SELECT * FROM users WHERE id = $1;
EXPLAIN PREPARE stmt;- Check driver configuration for prepared statement caching thresholds and connection pooling mode.
Performance Implications:
Using parameterized queries has performance benefits:
- The query plan is cached and reused
- Parameter data is sent in binary format (more efficient than text)
- No string escaping/unescaping overhead
This is why PostgreSQL prefers parameters over string concatenation.
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL
ERROR 42501: must be owner of table
How to fix "must be owner of table" in PostgreSQL
trigger cannot change partition destination
How to fix "Trigger cannot change partition destination" in PostgreSQL
SSL error: certificate does not match host name
SSL error: certificate does not match host name in PostgreSQL
No SSL connection
No SSL connection to PostgreSQL