How to fix ERROR: Parameter $N does not exist in PostgreSQL

PostgreSQLINTERMEDIATEHIGHUpdated Dec 26, 2025· 166 views

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.

Quick fix

Fixes ERROR: Parameter $N does not exist

SELECT * FROM users WHERE id = $1 AND status = $2;
  1. Verify the query has the correct parameter placeholders
  2. Ensure the parameter array is defined and not empty
  3. Use your database driver's parameterized query function
  4. Handle connection pooling issues if using PgBouncer
postgresql — error
SELECT * FROM users WHERE id = $1 AND status = $2;
ERROR: Parameter $N does not exist

What this error means

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

Typical symptoms

  • Query fails with 'ERROR: Parameter $N does not exist' during execution
  • Prepared statements work in some requests but fail in others
  • Parameterized queries fail while raw SQL strings work
  • Database driver throws parameter count mismatch errors
  • Connection pooler (PgBouncer) causes prepared statement errors after reconnection
  • Parameters work correctly when testing locally but fail in production

Common causes

  1. The number of values provided does not match the number of parameter placeholders ($1, $2, etc.) in the query
  2. The parameter array or list is undefined, null, or empty when the query expects values
  3. Forgetting to pass the parameters array to the query function
  4. Connection pooler such as PgBouncer deallocates prepared statements between requests
  5. Database driver incorrectly configured to use prepared statements with connection pooling
  6. Mixing parameterized query API with string concatenation in the same query
  7. Using a different parameter style than what your driver expects (e.g. ? instead of $N)

How to fix "ERROR: Parameter $N does not exist"

1Verify the query has the correct parameter placeholders

Check that your SQL query uses $1, $2, etc. for each parameter you intend to pass:

CORRECT - Using parameter placeholders:

sql
SELECT * FROM users WHERE id = $1 AND status = $2;

WRONG - Missing placeholders:

sql
-- This query references $1 but the WHERE clause doesn't have $2
SELECT * FROM users WHERE id = $1;

WRONG - Placeholder mismatch:

sql
-- 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.

2Ensure the parameter array is defined and not empty

The parameter array must be defined and populated with values matching each placeholder:

Node.js with node-postgres (pg):

javascript
// 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:

python
# 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:

go
// 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" arguments

Always verify that the number of values in your parameters array exactly matches the number of $N placeholders.

3Use your database driver's parameterized query function

Never concatenate user input into SQL strings. Always use your driver's parameterized query API:

Node.js - ALWAYS use the params array:

javascript
// 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:

python
# SAFE
cursor.execute('SELECT * FROM users WHERE username = %s', (username,))

# UNSAFE
cursor.execute(f'SELECT * FROM users WHERE username = \'{username}\'')

Go with pgx:

go
// 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.

4Handle connection pooling issues if using PgBouncer

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:

javascript
const { Client } = require('pg');
const client = new Client({
  connectionString: 'postgres://...',
  statement_timeout: 30000,
});
// PgBouncer should be configured with 'query' mode or 'transaction' mode

Option 2: Use PgBouncer with 'session' mode:

In pgbouncer.ini:

ini
[databases]
mydb = host=localhost dbname=mydb port=5432

[pgbouncer]
pool_mode = session  # Use session mode, not query or transaction

Option 3: Check if prepared statement caching is enabled:

For JDBC connections:

bash
jdbc:postgresql://localhost:5432/dbname?prepareThreshold=0

The 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."

5Match parameter order in your code to the query

Ensure the order of values in your parameters array exactly matches the order of $N placeholders:

CORRECT - Order matches:

javascript
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = ['Alice', 'alice@example.com', 30];
//              $1      $2                    $3
await client.query(query, params);

WRONG - Order doesn't match:

javascript
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = ['alice@example.com', 'Alice', 30];  // email and name swapped!
//              $1 expects name    $2 expects email  $3 expects age
await client.query(query, params);  // Will insert wrong data

WRONG - Missing parameter:

javascript
const query = 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3)';
const params = ['Alice', 'alice@example.com'];  // Missing $3 (age)
//              $1      $2
await client.query(query, params);  // Error: Parameter $3 does not exist

Use named parameters or comments in your code to clarify intent:

javascript
const name = 'Alice';
const email = 'alice@example.com';
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);
6Check for off-by-one errors in dynamic query building

If you're building queries dynamically, verify parameter indices are sequential:

CORRECT - Sequential indices:

javascript
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:

javascript
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 exist

When building dynamic queries, use a counter and increment it for each parameter.

7Debug by logging the query and parameters

Add logging to see exactly what query and parameters are being sent:

Node.js:

javascript
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:

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.

Advanced notes

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:

sql
SET log_min_duration_statement = 0;  -- Log all queries

- Use EXPLAIN to check parameter binding:

sql
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.

Frequently asked questions

What does "ERROR: Parameter $N does not exist" mean?
PostgreSQL error 'Parameter $N does not exist' indicates a mismatch between query parameter placeholders and the actual values supplied to the query. ) 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.
How do I fix "ERROR: Parameter $N does not exist"?
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.
What causes "ERROR: Parameter $N does not exist"?
The number of values provided does not match the number of parameter placeholders ($1, $2, etc.) in the query The parameter array or list is undefined, null, or empty when the query expects values Forgetting to pass the parameters array to the query function
Is the "ERROR: Parameter $N does not exist" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading