Prepared statements allow you to parse and plan a query once, then execute it multiple times with different parameter values. This provides two key benefits:
Parses and plans a query, storing the result under a named handle for later execution.
PREPARE statement_name AS query
-- Prepare a simple SELECT
PREPARE get_user AS SELECT * FROM "users" WHERE user_id == $1
-- Prepare with multiple parameters
PREPARE find_orders AS SELECT * FROM "orders"
WHERE customer_id == $1 AND status == $2
-- Prepare an aggregate query
PREPARE count_by_status AS SELECT status, COUNT(*) FROM "orders"
GROUP BY status WHERE status == $1
Executes a previously prepared statement. Parameter values are supplied via the wire protocol layer, not inline in the SQL text.
EXECUTE statement_name
The parameter values ($1, $2, etc.) are bound through the client protocol when sending the EXECUTE command. This keeps data values separate from query structure.
Removes a prepared statement from the session, freeing its cached plan.
DEALLOCATE statement_name
After deallocating, the statement name can be reused for a new PREPARE.
Parameter placeholders use the $N syntax, where N is a 1-based index:
| Placeholder | Description |
|---|---|
$1 |
First parameter |
$2 |
Second parameter |
$3 |
Third parameter, and so on |
Parameters can be used anywhere a literal value would appear in the query:
-- In WHERE conditions
PREPARE q1 AS SELECT * FROM "users" WHERE age > $1
-- In multiple conditions
PREPARE q2 AS SELECT * FROM "products" WHERE price >= $1 AND price <= $2
-- In different clause types
PREPARE q3 AS SELECT * FROM "events" WHERE category == $1 ORDER BY date LIMIT $2
SyndrDB uses an adaptive plan caching strategy inspired by PostgreSQL:
The plan cache is sharded (8 shards) with LRU eviction and uses xxhash for fast key lookup.
-- Prepare a reusable user lookup
PREPARE get_user_by_email AS SELECT * FROM "users" WHERE email == $1
-- Execute multiple times with different emails (params via protocol)
EXECUTE get_user_by_email -- $1 = "alice@example.com"
EXECUTE get_user_by_email -- $1 = "bob@example.com"
EXECUTE get_user_by_email -- $1 = "carol@example.com"
-- Clean up when done
DEALLOCATE get_user_by_email
-- Prepare a price range query
PREPARE products_in_range AS SELECT name, price, category
FROM "products"
WHERE price >= $1 AND price <= $2
ORDER BY price
-- Execute with different ranges (params via protocol)
EXECUTE products_in_range -- $1 = 10.00, $2 = 50.00
EXECUTE products_in_range -- $1 = 100.00, $2 = 500.00
DEALLOCATE products_in_range
-- Prepare outside a transaction (plan is session-scoped)
PREPARE insert_log AS ADD DOCUMENT TO BUNDLE "audit_log" WITH ({
"action" = $1,
"user_id" = $2,
"timestamp" = F:NOW()
})
BEGIN TRANSACTION
-- Execute the prepared statement multiple times within the transaction
EXECUTE insert_log -- $1 = "login", $2 = 42
EXECUTE insert_log -- $1 = "view_page", $2 = 42
EXECUTE insert_log -- $1 = "logout", $2 = 42
COMMIT
-- Statement persists after transaction ends
DEALLOCATE insert_log
| Command | Description |
|---|---|
PREPARE name AS query |
Parse, plan, and store a query under a name |
EXECUTE name |
Run a prepared statement (params via protocol) |
DEALLOCATE name |
Remove a prepared statement from the session |
Last updated: March 2026