PostgreSQL Undefined Parameter (SQLSTATE 42P02)

When PostgreSQL encounters a parameter placeholder ($1, $2, etc.) in a prepared statement or parameterized query that has no corresponding value supplied, it raises ERROR: there is no parameter $N with SQLSTATE 42P02 and condition name undefined_parameter. This error surfaces at parse or bind time, before any rows are touched.

What This Error Means

SQLSTATE 42P02 belongs to error class 42 — "Syntax Error or Access Rule Violation". Despite the class name, undefined_parameter is not a syntax error in the traditional sense; the SQL text is structurally valid. The problem is that PostgreSQL resolved the parameter placeholder against the list of bind values supplied by the client and found a gap: the placeholder number referenced exceeds the count of parameters that were actually bound.

PostgreSQL's extended query protocol separates statement preparation from execution. During the Parse phase, the server records the parameter count and their types (if explicitly cast). During the Bind phase, the client supplies actual values. If the Bind message provides fewer values than the highest placeholder number referenced in the query, PostgreSQL raises 42P02 immediately and the command never reaches execution. The transaction is not automatically aborted by this error alone — it is a non-fatal protocol-level error — but most client drivers will roll back the current transaction on any server error unless you handle it explicitly.

In psql or application logs the error looks like:

ERROR:  there is no parameter $2

or, for explicit casts on a missing parameter:

ERROR:  there is no parameter $3
LINE 1: SELECT * FROM orders WHERE status = $3::text
                                            ^

Common Causes

  1. Off-by-one in parameter numbering. PostgreSQL parameters are 1-indexed ($1, $2, …). Using $0 or skipping a number (e.g., jumping from $1 straight to $3) leaves a gap that causes this error when the bind step provides a contiguous list.

  2. Mismatch between the number of placeholders and the values list. The query was written expecting three parameters but the application code only passes two bind values — for example after a refactor removed a WHERE clause condition but left the original placeholder numbering intact elsewhere in the query.

  3. Manual SQL construction with conditional parameter injection. Code that conditionally appends AND col = $N clauses to a query string can produce a query where the placeholder numbers are not contiguous or are higher than the number of values collected, especially when some conditions are skipped.

  4. Reusing a prepared statement after schema or code changes. A server-side prepared statement (created with PREPARE) that was written for a different number of parameters than the EXECUTE call supplies will raise this error at EXECUTE time.

  5. ORM or query-builder bugs. Some ORMs build parameterized queries dynamically. A bug or misconfiguration in optional filters, IN clause expansion, or bulk insert generation can produce a placeholder number that outstrips the values array.

How to Fix undefined_parameter

  1. Count placeholders vs. values. Inspect the exact query string being sent and count the distinct $N values. The highest $N must be less than or equal to the number of bind values provided. Most drivers expose the final query and bind values in debug or trace logging.

    -- Broken: $3 is referenced but only two values are bound
    SELECT * FROM products WHERE category = $1 AND price < $2 AND in_stock = $3;
    -- Fix: supply all three bind values, or remove the unused placeholder
    
  2. Use contiguous, sequential numbering. PostgreSQL requires that the set of parameter numbers used in a query form a contiguous range starting at $1. Do not skip numbers.

    -- Wrong: $2 is missing
    SELECT $1, $3;
    
    -- Correct
    SELECT $1, $2;
    
  3. Fix conditional query builders. When constructing queries with optional WHERE clauses, use a parameter counter that increments only when a condition is actually appended, and collect bind values in the same pass.

    # Python example using psycopg2
    conditions = []
    params = []
    
    if status:
        conditions.append(f"status = ${len(params) + 1}")
        params.append(status)
    
    if min_price is not None:
        conditions.append(f"price >= ${len(params) + 1}")
        params.append(min_price)
    
    where = "WHERE " + " AND ".join(conditions) if conditions else ""
    query = f"SELECT * FROM orders {where}"
    cursor.execute(query, params)
    
  4. Re-create stale prepared statements. If using named server-side prepared statements (PREPARE / EXECUTE), deallocate and recreate them after any change to the query or parameter count:

    DEALLOCATE my_plan;
    
    PREPARE my_plan(int, text) AS
      SELECT * FROM users WHERE id = $1 AND role = $2;
    
    EXECUTE my_plan(42, 'admin');
    
  5. Check ORM or driver version compatibility. If the error appears after upgrading a dependency, review the changelog for changes to how the library numbers or batches parameters. A minimal reproduction query (bypassing the ORM) will confirm whether the issue is in application code or the library.

Additional Information

  • SQLSTATE class 42 groups syntax and access-rule errors. Closely related codes include 42601 (syntax_error), 42703 (undefined_column), and 42883 (undefined_function). These are all caught at parse/planning time.
  • 42P02 is specific to PostgreSQL and does not have a direct equivalent in the SQL standard.
  • The error has existed since prepared-statement support was introduced in PostgreSQL 7.3; its behavior has not changed in modern versions.
  • Most PostgreSQL drivers (libpq, psycopg2/3, asyncpg, JDBC, node-postgres) surface this as a generic database error with the SQLSTATE code in the exception metadata. Check e.pgcode (Python), e.getSQLState() (Java), or err.code (Node.js) to distinguish it programmatically.
  • Because the error occurs at bind time, it does not incur any table I/O or locking. There are no performance or data-integrity side effects beyond the failed statement itself.

Frequently Asked Questions

Why does PostgreSQL say "there is no parameter $2" when my query clearly has $2 in it? The error means PostgreSQL found $2 in the query text but the client only supplied one bind value. The placeholder exists in the SQL string, but the value to substitute for it was never sent. Double-check that the list of values passed to the driver matches the highest $N in the query.

Can I use $0 as the first parameter? No. PostgreSQL parameters are strictly 1-indexed. $0 is not a valid placeholder and will produce a syntax error (42601) rather than 42P02.

Is this the same as an "unbound parameter" error in application frameworks? Not necessarily. Application-level "unbound parameter" messages (common in ActiveRecord, SQLAlchemy, etc.) are raised by the library before the query reaches the database. 42P02 is a server-side error returned by PostgreSQL itself, meaning the malformed bind did reach the server.

Does this error abort my transaction? By itself, 42P02 does not automatically put the current transaction into an aborted state the way a runtime error (like 23505) does in PostgreSQL 14+. However, most client drivers treat any server error as transaction-aborting unless you use savepoints or explicit error handling. Check your driver documentation for the default error handling behavior.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.