A comprehensive guide for interacting with PostgreSQL 16 databases...
This skill is a definitive reference for PostgreSQL 16, covering query construction, optimization, schema management, and system introspection.
DISTINCT ON, RETURNING, LATERAL, FILTER clauses) when they provide cleaner logic or better performance.snake_case for all identifiers. Only quote identifiers ("MyTable") if absolutely necessary; prefer lowercase unquoted names.$1, $2, …) for literal values. Never inject user input directly.SET LOCAL statement_timeout = '30s';.BEGIN and COMMIT blocks for multi-step operations.EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS) to diagnose bottlenecks.Seq Scan on large tables, high Buffers: shared hit (RAM usage), or Disk: read (I/O).=, <, >) queries.JSONB (@>) or arrays (&&), and full-text search.WITH) for readability. In PG16+, these are optimized (inlined) by default unless MATERIALIZED is specified.When exploring a new database, use these queries to understand the schema.
SELECT n.nspname AS schema,
c.relname AS table,
obj_description(c.oid) AS description
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY 1, 2;
SELECT a.attname AS column,
format_type(a.atttypid, a.atttypmod) AS type,
a.attnotnull AS not_null,
col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a
WHERE a.attrelid = 'public.target_table_name'::regclass
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY a.attnum;
count(*) FILTER (WHERE status = 'active')GROUP BY GROUPING SETS ((brand), (brand, category), ())any_value(col) (PG16+) returns an arbitrary value from the group.Perform calculations across a set of table rows related to the current row.
SELECT dept,
emp_no,
salary,
-- Rank employees by salary within department
dense_rank() OVER (PARTITION BY dept ORDER BY salary DESC) AS rank,
-- Running total of salaries
sum(salary) OVER (
PARTITION BY dept
ORDER BY salary
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM employees;
col LIKE 'foo%' (simple wildcard).col ILIKE 'foo%' (case-insensitive).col SIMILAR TO '[a-c]%' (SQL-regex style).col ~ '^[a-z]+$'col ~* 'foo'Standard SQL method for inserting, updating, or deleting based on join conditions (PG15+).
MERGE INTO wine_stock ws
USING wine_shipments s
ON s.winery_id = ws.winery_id
AND s.year = ws.year
WHEN MATCHED THEN
UPDATE SET stock = ws.stock + s.count
WHEN NOT MATCHED THEN
INSERT (winery_id, year, stock)
VALUES (s.winery_id, s.year, s.count);
Postgres-specific, often more concise for simple unique-key conflicts.
INSERT INTO kv_store (key, value)
VALUES ('config', '{"a":1}')
ON CONFLICT (key)
DO UPDATE SET value = EXCLUDED.value;
Return data from modified rows immediately.
DELETE FROM archived_logs
WHERE created_at < NOW() - INTERVAL '1 year'
RETURNING id, created_at;
Prefer jsonb over json for storage and indexing.
| Operator | Description | Example |
|---|---|---|
-> / ->> |
Get element (JSON / text) | data->'key' |
@> |
Contains (indexable) | data @> '{"tag": "urgent"}' |
? |
Key exists | data ? 'error' |
#- |
Delete path | data #- '{info, sensitive}' |
SQL/JSON path (PG12+):
-- Find all items with price > 10
SELECT jsonb_path_query(data, '$.items[*] ? (@.price > 10)')
FROM orders;
SELECT ARRAY[1,2,3]; -- Creation
SELECT (ARRAY[1,2,3])[1]; -- Access (1-based index)
SELECT 1 = ANY(arr_col); -- Check if value exists in array
SELECT unnest(arr_col) FROM t; -- Expand array to rows
Useful for scheduling and validity periods.
tstzrange: timestamp with time zone range.int4range, daterange: integer and date ranges.&&): checks if two ranges overlap.SELECT *
FROM reservations
WHERE duration && tstzrange('2023-01-01 10:00', '2023-01-01 12:00');
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'active'
AND pid <> pg_backend_pid()
AND query_start < NOW() - INTERVAL '5 minutes';
SELECT relname,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
pg_size_pretty(pg_relation_size(relid)) AS data_size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;
Navigating an organizational hierarchy.
WITH RECURSIVE subordinates AS (
-- Base case: the manager
SELECT employee_id, manager_id, full_name, 0 AS level
FROM employees
WHERE employee_id = $1
UNION ALL
-- Recursive step: direct reports
SELECT e.employee_id, e.manager_id, e.full_name, s.level + 1
FROM employees e
INNER JOIN subordinates s ON s.employee_id = e.manager_id
)
SELECT *
FROM subordinates;
Efficiently getting the latest 3 posts for each user.
SELECT u.username, p.title, p.created_at
FROM users u
CROSS JOIN LATERAL (
SELECT title, created_at
FROM posts
WHERE user_id = u.id
ORDER BY created_at DESC
LIMIT 3
) p
WHERE u.status = 'active';
Searching a blog table.
SELECT id,
title,
ts_rank(to_tsvector('english', title || ' ' || content), query) AS rank
FROM articles,
to_tsquery('english', 'postgres | optimization') query
WHERE to_tsvector('english', title || ' ' || content) @@ query
ORDER BY rank DESC;