Joins combine tables: INNER keeps matching rows only, LEFT keeps every row from the left side (with NULLs where nothing matched). GROUP BY collapses rows into groups for aggregates (COUNT, SUM, AVG); HAVING filters groups after aggregation, WHERE filters rows before.
A Common Table Expression names an intermediate result: WITH recent AS (...) SELECT ... FROM recent. Chain several to read a query top to bottom like a pipeline. Recursive CTEs walk hierarchies.
Why it matters for AI engineering: evaluation results, traces and cost logs all end up in tables, and pgvector puts your embeddings right next to them in Postgres.
WITH per_model AS (
SELECT model, COUNT(*) AS calls, SUM(cost_usd) AS cost
FROM llm_calls
WHERE created_at >= now() - interval '7 days'
GROUP BY model
)
SELECT model, calls, cost, cost / calls AS cost_per_call
FROM per_model
ORDER BY cost DESC;Going deeper
EXPLAIN ANALYZE shows how Postgres executes a query: sequential vs index scans, join algorithms (nested loop, hash, merge) and where the time goes. Reading plans is the fastest way to fix a slow query.
Recursive CTEs (WITH RECURSIVE) walk trees and graphs: org charts, category hierarchies, dependency chains.
Common pitfalls
COUNT(col)skips NULLs;COUNT(*)doesn't.- LEFT JOIN followed by a WHERE on the right table silently turns it into an INNER JOIN.
Best resources for this lesson
- DocsWITH queries (Common Table Expressions) · PostgreSQL manual, including recursive CTEs
- InteractiveSQLBolt interactive lessons