The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Create a session temporary table | CREATE TEMP TABLE work_items (item_id bigint PRIMARY KEY, payload jsonb NOT NULL); | View examples |
| Preserve rows across commits | CREATE TEMP TABLE work_items (item_id bigint)
ON COMMIT PRESERVE ROWS; | View examples |
| Clear rows after each commit | CREATE TEMP TABLE work_items (item_id bigint)
ON COMMIT DELETE ROWS; | View examples |
| Drop the table at commit | CREATE TEMP TABLE work_items (item_id bigint)
ON COMMIT DROP; | View examples |
| Materialize a transaction-scoped result | CREATE TEMP TABLE recent_orders
ON COMMIT DROP AS
SELECT order_id, total
FROM app.orders
WHERE created_at >= CURRENT_DATE; | View examples |
| Copy a table shape | CREATE TEMP TABLE staged_orders (LIKE app.orders INCLUDING DEFAULTS INCLUDING CONSTRAINTS); | View examples |
| Index a populated temporary table | CREATE INDEX work_items_customer_idx
ON work_items (customer_id); | View examples |
| Collect temporary-table statistics | ANALYZE work_items; | View examples |
| Bypass temporary name shadowing | SELECT order_id FROM app.orders; | View examples |
| Restrict temporary-table creation | REVOKE TEMPORARY ON DATABASE appdb FROM PUBLIC; | View examples |
| Delegate temporary-table creation | GRANT TEMPORARY ON DATABASE appdb TO batch_worker; | View examples |
| Budget session-local temp buffers | SET temp_buffers = '64MB'; | View examples |
| Create a shared unlogged table | CREATE UNLOGGED TABLE app.ingest_buffer (event_id bigint PRIMARY KEY, payload jsonb NOT NULL); | View examples |
| Materialize an unlogged result | CREATE UNLOGGED TABLE app.daily_rollup AS
SELECT account_id, sum(amount) AS total
FROM app.ledger
GROUP BY account_id; | View examples |
| Promote data to crash-safe storage | ALTER TABLE app.ingest_buffer SET LOGGED; | View examples |
| Convert rebuildable data to unlogged | ALTER TABLE app.daily_rollup SET UNLOGGED; | View examples |
| Audit relation persistence | SELECT n.nspname, c.relname, c.relpersistence
FROM pg_class c
JOIN pg_namespace n
ON n.oid = c.relnamespace
WHERE c.relkind = 'r'; | View examples |
| Preserve unlogged data in a logical dump | pg_dump --format=custom --file=appdb.dump appdb | View examples |
Temporary and unlogged tables both reduce durability overhead, but they solve different problems. A temporary table belongs to one database session and can have transaction-scoped contents or identity; an unlogged table is a shared persistent object whose data survives clean restarts but is truncated after crash recovery and whose contents are unavailable on standbys. Choose from the failure and visibility contract, not from benchmark speed alone.
Step by step
Detailed examples
Make temporary-table lifetime explicit
PostgreSQL creates each temporary table in a session-specific schema; other sessions cannot see its definition or data, and the table disappears when the session ends. ON COMMIT PRESERVE ROWS is the PostgreSQL default, unlike the SQL standard's default. DELETE ROWS performs an automatic commit-time truncation, while DROP removes the table at commit. Creation and population still follow transaction semantics: a rollback undoes them. GLOBAL and LOCAL TEMPORARY are accepted but currently have no effect and are deprecated.
CREATE TEMP TABLE work_items (
item_id bigint PRIMARY KEY,
customer_id bigint NOT NULL,
payload jsonb NOT NULL
) ON COMMIT DELETE ROWS;
BEGIN;
INSERT INTO work_items (item_id, customer_id, payload)
VALUES (1001, 42, '{"op":"reprice"}');
COMMIT;
-- The definition remains in this session; its rows were cleared at COMMIT. Use CREATE TABLE AS or a deliberate LIKE subset
CREATE TEMP TABLE AS evaluates its query once and is preferred over PostgreSQL's historical SELECT INTO form, which conflicts with procedural and embedded-SQL meanings. LIKE copies only the features requested: defaults, generated expressions, constraints, indexes, identity, statistics, storage, comments, or all. Copied defaults can retain dependencies on source sequences, and copied constraints do not imply copied foreign keys, so inspect the result before using it as an isolated staging contract.
BEGIN;
CREATE TEMP TABLE recent_orders
ON COMMIT DROP
AS
SELECT order_id, customer_id, total
FROM app.orders
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days';
ALTER TABLE recent_orders ADD PRIMARY KEY (order_id);
COMMIT; CREATE TEMP TABLE staged_orders (
LIKE app.orders
INCLUDING DEFAULTS
INCLUDING GENERATED
INCLUDING CONSTRAINTS
) ON COMMIT DROP;
SELECT column_name, data_type, column_default
FROM information_schema.columns
WHERE table_schema = pg_my_temp_schema()::regnamespace::text
AND table_name = 'staged_orders'
ORDER BY ordinal_position; Analyze temporary data after its shape stabilizes
Indexes on temporary tables are temporary automatically. For performance, build them only when repeated probes, joins, uniqueness checks, or ordering repay their build cost. Autovacuum workers cannot access session-private temporary tables, so they neither vacuum nor analyze them. Run ANALYZE after the representative load and manual VACUUM when a long-lived session generates substantial dead rows. ANALYZE uses a read-compatible lock, but its sample and planning overhead still belong in the batch budget.
CREATE INDEX work_items_customer_idx ON work_items (customer_id);
ANALYZE work_items;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT w.item_id, c.segment
FROM work_items AS w
JOIN app.customers AS c USING (customer_id)
WHERE c.active; Control TEMPORARY privilege and relation-name shadowing
TEMPORARY is a database privilege and is granted to PUBLIC by default on newly created databases. Revoke it when untrusted roles should not allocate session-local relations, then grant it to bounded worker roles. Once a temporary schema exists, it is searched before pg_catalog for relation and data-type names, though never for function or operator names. A temporary table can therefore hide an unqualified permanent table from new plans. Qualify security-sensitive permanent relations and use a controlled search_path in privileged code.
REVOKE TEMPORARY ON DATABASE appdb FROM PUBLIC;
GRANT TEMPORARY ON DATABASE appdb TO batch_worker;
-- This remains the permanent table even if the session creates pg_temp.orders.
SELECT order_id, total
FROM app.orders
WHERE status = 'pending';
SELECT current_schemas(true), pg_my_temp_schema(); Design for physical sessions, pools, and two-phase commit
Temporary objects belong to a PostgreSQL backend session, not to a logical request. Under session pooling, PRESERVE ROWS data can leak into the next request unless cleanup is guaranteed; under transaction pooling, a later transaction may run on another backend and cannot rely on the table. Prefer ON COMMIT DROP for transaction-scoped work. A transaction that touched a temporary table or the session's temporary namespace cannot be prepared with PREPARE TRANSACTION. temp_buffers is per session and must be set before that session first accesses temporary tables, so large settings multiplied across many active backends require capacity planning.
SET temp_buffers = '64MB';
BEGIN;
CREATE TEMP TABLE request_keys (
key bigint PRIMARY KEY
) ON COMMIT DROP;
INSERT INTO request_keys (key) VALUES (101), (205), (309);
SELECT a.* FROM app.accounts AS a JOIN request_keys AS r ON r.key = a.account_id;
COMMIT; Reserve unlogged tables for shared, rebuildable state
An unlogged table is visible to every authorized session and remains after clean shutdown, but its data changes bypass normal write-ahead logging. PostgreSQL automatically truncates it after a crash or unclean shutdown, and its contents are not available on physical standbys or eligible for logical publications. Indexes and identity or serial sequences created with the table become unlogged too. Unlogged partitioned tables are not supported in PostgreSQL 18. Never use this persistence for a system of record, failover-critical queue, or the only copy of an ingest batch.
CREATE UNLOGGED TABLE app.ingest_buffer (
event_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
source_id text NOT NULL,
received_at timestamptz NOT NULL DEFAULT now(),
payload jsonb NOT NULL,
UNIQUE (source_id)
);
COMMENT ON TABLE app.ingest_buffer IS
'Rebuildable staging data; empty after crash recovery and absent from standbys'; Schedule logged and unlogged conversions as blocking migrations
ALTER TABLE SET LOGGED or SET UNLOGGED requires table ownership, takes ACCESS EXCLUSIVE unless a command-specific exception applies, and is unsuitable for latency-sensitive traffic without a maintenance plan. It cannot target temporary or partitioned tables. Linked identity and serial sequences change persistence with the table. Converting to LOGGED establishes WAL-backed durability for subsequent recovery and replication, but do not advertise the data as failover-safe until the conversion commits and standby or backup recovery has been verified.
BEGIN;
SET LOCAL lock_timeout = '5s';
LOCK TABLE app.ingest_buffer IN ACCESS EXCLUSIVE MODE;
ALTER TABLE app.ingest_buffer SET LOGGED;
COMMIT;
SELECT c.relpersistence
FROM pg_class AS c
WHERE c.oid = 'app.ingest_buffer'::regclass; Audit persistence and test the intended recovery path
pg_class.relpersistence reports p for permanent, u for unlogged, and t for temporary relations. Logical pg_dump includes unlogged table and sequence data by default; --no-unlogged-table-data deliberately omits it. That dump behavior does not make unlogged data crash-safe between backups, nor does WAL-based point-in-time recovery reconstruct its changes. Document a rebuild source and objective, monitor buffer growth, and exercise crash, failover, and logical-restore tests before adopting unlogged storage in production.
SELECT n.nspname AS schema_name, c.relname,
CASE c.relpersistence
WHEN 'p' THEN 'permanent'
WHEN 'u' THEN 'unlogged'
WHEN 't' THEN 'temporary'
END AS persistence,
pg_total_relation_size(c.oid) AS total_bytes
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'p')
AND c.relpersistence <> 'p'
ORDER BY total_bytes DESC; # Default: include unlogged table and sequence data.
pg_dump --format=custom --file=appdb.dump appdb
# Schema remains, but omit rebuildable unlogged data.
pg_dump --format=custom --no-unlogged-table-data --file=appdb-without-stage.dump appdb Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- PostgreSQL Global Development GroupPostgreSQL 18: CREATE TABLEpostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: CREATE TABLE ASpostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: ALTER TABLEpostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: Client Connection Defaultspostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: Resource Consumptionpostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: Privilegespostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: PREPARE TRANSACTIONpostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: CREATE PUBLICATIONpostgresql.org
- PostgreSQL Global Development GroupPostgreSQL 18: pg_dumppostgresql.org
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



