When PostgreSQL encounters a PREPARE statement wrapping a query type that cannot be prepared, it raises:
ERROR: utility statements cannot be prepared
or, depending on the specific issue:
ERROR: PREPARE AS SELECT is not supported for this query type
The SQLSTATE code is 42P14 and the condition name is invalid_prepared_statement_definition. This error occurs during the parsing/analysis phase of a PREPARE command, before any execution takes place.
What This Error Means
SQLSTATE class 42 covers syntax errors and rule violations — specifically, errors that result from the structure or content of a SQL statement being semantically invalid, even if syntactically well-formed. Code 42P14 is a PostgreSQL-specific extension within this class (the P prefix denotes a PostgreSQL-defined code not in the SQL standard).
The PREPARE command in PostgreSQL allows you to create a named, server-side prepared statement that can be executed multiple times with different parameter values. PostgreSQL's planner pre-analyzes the statement at preparation time. However, not all SQL constructs are eligible for preparation. PostgreSQL requires that the statement body be one of: SELECT, INSERT, UPDATE, DELETE, or VALUES. Attempting to prepare any other statement type — such as CREATE TABLE, ALTER TABLE, COPY, VACUUM, BEGIN, or other utility/DDL commands — triggers 42P14.
After this error is raised, no prepared statement is created. The connection remains fully usable; there is no transaction rollback and no partial state left behind. The error is purely a rejection at statement-definition time.
Common Causes
Wrapping DDL or utility statements in PREPARE. Commands like
CREATE TABLE,DROP INDEX,TRUNCATE,VACUUM,ANALYZE,COPY,SET, orEXPLAINcannot be prepared. Passing any of these as the body of aPREPAREcall will trigger this error.Dynamic SQL generation in application code or ORMs producing an invalid PREPARE. A code path that prepares all outgoing queries unconditionally may accidentally wrap a DDL migration or administrative command in a
PREPAREcall.Using PREPARE with a transaction control statement. Commands such as
BEGIN,COMMIT,ROLLBACK,SAVEPOINT, andRELEASE SAVEPOINTcannot be prepared.Nested or compound statements. Attempting to prepare a statement that contains multiple semicolon-separated commands (a multi-statement string) is not supported and will be rejected.
How to Fix invalid_prepared_statement_definition
Execute non-preparable statements directly. DDL, utility commands, and transaction control statements must be sent as plain queries, not via
PREPARE/EXECUTE. Remove thePREPAREwrapper and send the command directly to the server.-- Wrong: cannot prepare DDL PREPARE my_stmt AS CREATE TABLE orders (id serial PRIMARY KEY, amount numeric); -- Correct: execute DDL directly CREATE TABLE orders (id serial PRIMARY KEY, amount numeric);Restrict PREPARE to DML statements only. In application code or ORMs, guard the prepared-statement path to only activate for
SELECT,INSERT,UPDATE,DELETE, andVALUESqueries.# Python / psycopg2 example # Use cursor.execute() for DDL; use prepared statements only for DML cur.execute("CREATE TABLE IF NOT EXISTS orders (id serial PRIMARY KEY)") cur.execute("PREPARE insert_order AS INSERT INTO orders (amount) VALUES ($1)") cur.execute("EXECUTE insert_order(99.50)")Check ORM or migration tooling configuration. Some migration frameworks (e.g., Flyway, Liquibase, Alembic) route all statements through a "prepare-first" path when connecting via certain JDBC/driver settings. Ensure your driver or framework does not set
prepareThresholdor equivalent options in a way that attempts to prepare DDL statements.Split multi-statement strings. If you are passing a string containing multiple SQL commands separated by semicolons, split them and execute each statement individually.
-- Wrong: multi-statement prepare PREPARE batch AS INSERT INTO a VALUES ($1); INSERT INTO b VALUES ($2); -- Correct: prepare and execute separately PREPARE ins_a AS INSERT INTO a VALUES ($1); PREPARE ins_b AS INSERT INTO b VALUES ($1);
Additional Information
- SQLSTATE
42P14has been present in PostgreSQL since at least version 8.x; the set of preparable statement types has not changed significantly across recent major versions. - Related SQLSTATE codes in class
42include42601(syntax error),42703(undefined column),42P01(undefined table), and42P07(duplicate table). These all represent structural/semantic violations caught before execution. - Most PostgreSQL client drivers (libpq, JDBC, psycopg2, node-postgres) surface this as a server-side error with the SQLSTATE attached; the exact exception class depends on the driver (e.g.,
psycopg2.errors.InvalidPreparedStatementDefinition). - The
pg_prepared_statementssystem view lists all currently active prepared statements in a session. If aPREPAREcommand raises42P14, no entry will appear in this view for that statement name. - In PL/pgSQL,
EXECUTE(dynamic SQL) bypasses thePREPAREmechanism entirely and can run any SQL command, making it a valid alternative for DDL inside functions.
Frequently Asked Questions
Why can't PostgreSQL prepare DDL statements?
Prepared statements are designed for repeated execution of the same query plan with different parameter values. DDL and utility commands are transactional but not re-executable in the same parameterized sense — they have no bind parameters, and their semantics depend on catalog state that may change between executions. PostgreSQL deliberately restricts PREPARE to the DML statement types that benefit from plan caching.
I'm using a connection pooler (PgBouncer, pgpool-II). Can that cause 42P14?
Indirectly, yes. Some poolers or their client configurations issue server-side PREPARE for all statements when operating in session mode. If your application sends DDL through such a pooler with aggressive prepare settings, the pooler may attempt to prepare the DDL statement. Review the pooler's server_reset_query and client driver prepareThreshold settings.
Does this error affect my transaction?
No. 42P14 is raised before any execution occurs, so no transaction is started or rolled back as a result. The current transaction state (if any) is unaffected, and the connection remains fully operational.
How do I prepare a query that needs to run DDL conditionally?
You cannot prepare DDL itself. Instead, use a PL/pgSQL function that wraps the DDL in EXECUTE (dynamic SQL), and prepare a SELECT call to that function. Alternatively, send the DDL as a plain statement and use application-level logic to decide whether to execute it.