Incident brief
Undefined parameter
A query referenced a positional parameter ($1, $2, ...) outside of any context that supplies a value for it. PostgreSQL had nothing to substitute.
What lands in your log
ERROR: there is no parameter $1
In 10 seconds
- What triggers it
- Type a bare query containing $1 directly into a client, with no PREPARE/parameter binding around it.
- Fix
- Only use $1, $2, ... inside a PREPARE'd statement (then supply values via EXECUTE), or via your client library's real parameter-binding API.
- Proof
- Reproduced on PostgreSQL 16.14 → A bare SELECT $1 typed directly, with no surrounding PREPARE, was rejected with SQLSTATE 42P02. The session was unaffected afterward.
Fix
What to do right now
Application-level steps for this error.
- Only use $1, $2, ... inside a PREPARE'd statement (then supply values via EXECUTE), or via your client library's real parameter-binding API.
- Never paste a $n placeholder into ad-hoc SQL text and run it directly, there is nothing to bind it to.
- A parameter-count mismatch caught later, at EXECUTE time, surfaces as a different SQLSTATE entirely, see the counterexample.
PREPARE fetch_one(int) AS SELECT $1;
EXECUTE fetch_one(5);
DEALLOCATE fetch_one;For this error
See this error live on the server
Run these against the affected instance to confirm the diagnosis before you act.
Undefined parameter (42P02) is statement-local: PostgreSQL does not retain the rejected value after the statement ends. Capture the exact bound value and statement position in application/server logs, then run this targeted validation before retrying.
Inspect prepared statement parameter slots
Validate the specific value/shape that can raise this SQLSTATE.
SELECT name, parameter_types, statement
FROM pg_prepared_statements
ORDER BY prepare_time;Why it happens
What PostgreSQL is telling you
The mechanism behind the error, grounded in the official manual, not paraphrased.
PostgreSQL 16 Documentation, PREPARE
Prepared statements can take parameters: values that are substituted into the statement when it is executed. When creating the prepared statement, refer to parameters by position, using $1, $2, etc.Read the full section on postgresql.org →
The unbound parameter reference
$1 only has meaning inside a prepared statement, where a value gets substituted in at EXECUTE time. Typed as a bare, ordinary query, there's no value anywhere for PostgreSQL to substitute, so it rejects the reference outright.Confirming the session still works
The rejected query didn't affect the session, the next statement ran normally.Reproduce & verify
A real, single-session PostgreSQL reproduction
A literal transcript of SQL run against a live PostgreSQL instance in an isolated lab. The commands below are exactly what was executed.
- 1Type a bare query containing $1 directly into a client, with no PREPARE/parameter binding around it.
- 2PostgreSQL has no value bound to $1 in this context.
- 3SQLSTATE 42P02 is reported, naming the missing parameter.
One client: type $1 directly into a plain query, outside any PREPARE/parameter context.
-- No table needed: the failure happens purely at the parse stage.
SELECT 'no schema required' AS setup_note;SELECT $1;SELECT 1 AS session_still_alive;What PostgreSQL actually returned
setup_note
--------------------
no schema required
(1 row)ERROR: there is no parameter $1
LINE 1: SELECT $1;
^ session_still_alive
----------------------
1
(1 row)A subtler mistake was tested directly: declaring fewer parameter types in PREPARE than the number of $n placeholders actually used in the query body.
Without this
Above: referencing $1 with no PREPARE context at all is rejected with SQLSTATE 42P02.
With this, tested
Below: PREPARE itself does not catch a parameter-count mismatch, a later EXECUTE call fails instead, with a completely different SQLSTATE.
- A second operational test: exact SQL, raw output, measured result, and engineer notes
- A related mistake caught at a different time: exact SQL, output, and verdict
- A manual-grounded production interpretation of the lab result
Card required. Cancel before day 7 and you are not charged.
Verification
- Last verified
- 2026-07-16 (Docker lab, PostgreSQL 16.14)
- Verification scope
- Verified against PostgreSQL 16.14 in an isolated lab environment
- Audit status
- reviewed
Went further?
Pro unlocks the second lab proof
Free page stops the bleeding. Pro adds the operational test, SQLSTATE audit, and deeper evidence, same error, more certainty.