← Back to PostgreSQL Mastery
Intermediate22 min read

SQL Queries

Write joins, aggregations, subqueries, and common table expressions in PostgreSQL.

SELECT and Joins

PostgreSQL supports INNER, LEFT, RIGHT, FULL OUTER, and CROSS joins. FULL OUTER is useful for reconciliation reports finding rows in either table without match.

Use explicit JOIN syntax rather than comma-FROM with WHERE conditions—clearer and safer against accidental Cartesian products.

SELECT o.id, o.total, u.email
FROM orders o
INNER JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid'
ORDER BY o.created_at DESC
LIMIT 20;

Aggregations

GROUP BY with HAVING filters grouped results. PostgreSQL supports FILTER clause for conditional aggregates—cleaner than CASE inside COUNT/SUM.

Distinct aggregates and ordered-set aggregates (percentile_cont) extend analytics capabilities beyond basic SQL.

SELECT
  user_id,
  COUNT(*) FILTER (WHERE status = 'paid') AS paid_orders,
  SUM(total) AS revenue
FROM orders
GROUP BY user_id
HAVING SUM(total) > 1000;

Subqueries

Scalar, row, and table subqueries nest in SELECT, FROM, and WHERE. LATERAL joins correlate subqueries per outer row—powerful for top-N per group patterns.

EXISTS often outperforms IN for large subquery results. Materialized subqueries in FROM should be named aliases.

SELECT u.email
FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.total > 500
);

Common Table Expressions

WITH clauses define readable multi-step queries. Recursive CTEs traverse hierarchies—org charts, bill of materials, graph paths.

CTEs in PostgreSQL 12+ can be inlined or materialized with NOT MATERIALIZED / MATERIALIZED hints when you understand planner behavior.

  • Recursive CTEs need UNION ALL and termination condition
  • Use CTEs for readability; verify EXPLAIN for performance on huge datasets
  • Multiple CTEs chain in one WITH clause comma-separated
WITH regional_sales AS (
  SELECT region, SUM(amount) AS total FROM sales GROUP BY region
)
SELECT region, total
FROM regional_sales
WHERE total > (SELECT AVG(total) FROM regional_sales);

Pagination and Performance

Prefer keyset pagination on indexed columns over OFFSET for large tables. Use cursor-based APIs returning last seen id.

Analyze queries with EXPLAIN (ANALYZE, BUFFERS) for actual timings and I/O. Create indexes matching WHERE and ORDER BY together.

SELECT * FROM products
WHERE id > $1
ORDER BY id
LIMIT 50;

Get In Touch


Ready to discuss your next project? Drop me a message.