Postgres
Working reference for operating and querying PostgreSQL — reading query plans, moving data between databases, and the diagnostic queries worth keeping close.
EXPLAIN ANALYZE / Query Plans
EXPLAIN shows the planner’s chosen plan and cost estimates; EXPLAIN ANALYZE actually runs the query and reports real timing and row counts. The gap between estimated and actual rows is where most bad plans hide.
How to read a plan
- Run on a realistic data set — plans flip on cardinality; a plan tuned on 100 rows lies about 10M.
- Use the parameters —
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)to see cache vs disk reads. - Start at the innermost/bottom node and work outward — that’s execution order.
- Find the most expensive nodes (actual time × loops), and watch for
Seq Scanon big tables, bad row estimates, and nested loops over large inputs. - Only then optimize — add an index, rewrite the join, or bump
work_mem.
Visualizers
Foreign Data Wrapper (copy a table between databases)
postgres_fdw lets one database query tables in another. Handy for one-off copies without pg_dump.
-- 👇 Replace SOURCE_DB everywhere for reuse
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER source_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname 'SOURCE_DB', host 'localhost');
CREATE USER MAPPING FOR CURRENT_USER
SERVER source_server
OPTIONS (user 'root');
CREATE SCHEMA source_schema;
IMPORT FOREIGN SCHEMA public
LIMIT TO ("TableName1")
FROM SERVER source_server
INTO source_schema;
-- Materialize the foreign table into a local copy
DROP TABLE IF EXISTS "TableName1";
CREATE TABLE "TableName1" AS
SELECT * FROM source_schema."TableName1";Diagnostics
Running queries (filter to a user’s active statements):
SELECT pid, usename, state, backend_start, query_start, query
FROM pg_stat_activity
WHERE usename = 'username'
AND state = 'active'
ORDER BY query_start;Open transactions (find long-running / idle-in-transaction sessions that hold locks):
SELECT pid, usename, datname, state,
xact_start, now() - xact_start AS xact_duration, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;Kill a stuck backend: SELECT pg_cancel_backend(pid); (cancel query) or pg_terminate_backend(pid) (drop the connection).
CDC Prerequisites (logical replication)
Change Data Capture via logical decoding needs these set:
SHOW wal_level; -- must be 'logical'
SHOW max_replication_slots; -- one slot per consumer
SHOW max_wal_senders; -- one sender per connection
SHOW wal_sender_timeout;Cheat Sheet
Table disk usage (largest first):
SELECT tablename AS table_name,
pg_size_pretty(pg_total_relation_size(quote_ident(tablename))) AS size
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(quote_ident(tablename)) DESC;Installed extensions / roles:
SELECT * FROM pg_extension;
SELECT * FROM pg_roles;Group by day with per-day percentages (window function over a DATE_TRUNC partition):
SELECT DATE_TRUNC('day', "createdAt") AS day,
status,
COUNT(*) AS count,
ROUND(COUNT(*) * 100.0 /
SUM(COUNT(*)) OVER (PARTITION BY DATE_TRUNC('day', "createdAt"))) AS percentage
FROM "RateTypeBatch"
WHERE "createdAt" >= CURRENT_DATE - INTERVAL '10 days'
GROUP BY day, status
ORDER BY day DESC;Count every row in every table (diagnostic; scans all tables):
DO $$
DECLARE
rec RECORD;
table_count BIGINT;
total_count BIGINT := 0;
BEGIN
FOR rec IN
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
LOOP
EXECUTE format('SELECT COUNT(*) FROM %I.%I', rec.schemaname, rec.tablename)
INTO table_count;
total_count := total_count + table_count;
RAISE NOTICE 'Table: %.%, Count: %', rec.schemaname, rec.tablename, table_count;
END LOOP;
RAISE NOTICE 'Total records in the database: %', total_count;
END $$;Related