SQL has been around since the 1970s and it is not going anywhere. Nearly every application that stores data uses a relational database, and SQL is how you talk to it. Backend developers write it to build features, data analysts use it to answer business questions, and data scientists use it to extract training data. If you skip SQL, you'll hit a wall in almost every technical role. The good news: 90% of the work uses about 10 concepts.
What changed in 2026
- PostgreSQL is the undisputed default for new projects. SQLite for local development, PostgreSQL in production — this pair covers almost everything.
- pgvector added AI use cases — PostgreSQL now stores and queries vector embeddings natively, making it the database for AI apps too.
- ORM vs raw SQL debate matured. ORMs (SQLAlchemy, Prisma, Drizzle) handle routine queries; SQL is still required for analytics, complex joins, and performance tuning.
- SQL in AI workflows. LLMs generate SQL queries, but engineers still need to review, debug, and optimize them — you can't blindly trust AI-generated queries.
The data model you'll use for practice
All examples use three tables. Create them in SQLite or PostgreSQL:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
amount NUMERIC(10, 2),
status TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10, 2)
);
The essential queries
SELECT and WHERE
-- Get all users
SELECT * FROM users;
-- Get specific columns
SELECT name, email FROM users;
-- Filter with WHERE
SELECT * FROM orders WHERE status = 'completed';
-- Multiple conditions
SELECT * FROM orders WHERE status = 'completed' AND amount > 100;
ORDER BY and LIMIT
-- Most recent orders first
SELECT * FROM orders ORDER BY created_at DESC;
-- Top 10 largest orders
SELECT * FROM orders ORDER BY amount DESC LIMIT 10;
Aggregate functions
-- Total revenue
SELECT SUM(amount) AS total_revenue FROM orders;
-- Average order value
SELECT AVG(amount) AS avg_order FROM orders WHERE status = 'completed';
-- Count orders per status
SELECT status, COUNT(*) AS count FROM orders GROUP BY status;
GROUP BY and HAVING
-- Orders per user, only users with 3+ orders
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 3
ORDER BY order_count DESC;
JOIN — the key concept
JOINs combine data from multiple tables. The most common: INNER JOIN (only rows that match in both tables).
-- Get user name alongside each order
SELECT users.name, orders.amount, orders.status
FROM orders
INNER JOIN users ON orders.user_id = users.id;
A LEFT JOIN returns all rows from the left table even if there's no match:
-- All users, including those with no orders
SELECT users.name, COUNT(orders.id) AS order_count
FROM users
LEFT JOIN orders ON orders.user_id = users.id
GROUP BY users.name
ORDER BY order_count DESC;
JOIN types at a glance
| JOIN type |
Returns |
| INNER JOIN |
Only rows that match in both tables |
| LEFT JOIN |
All rows from left table, NULLs for unmatched right |
| RIGHT JOIN |
All rows from right table, NULLs for unmatched left |
| FULL JOIN |
All rows from both tables |
INNER JOIN and LEFT JOIN cover 95% of real queries. Learn those first.
WHERE vs HAVING
A common confusion point:
| Clause |
Filters |
When to use |
| WHERE |
Individual rows |
Before aggregation |
| HAVING |
Aggregated results |
After GROUP BY |
-- WHERE filters rows before counting
-- HAVING filters groups after counting
SELECT user_id, COUNT(*) AS orders
FROM orders
WHERE status = 'completed' -- filter rows first
GROUP BY user_id
HAVING COUNT(*) > 2; -- then filter groups
How to practice SQL
- Install SQLite (it's pre-installed on macOS) or create a free PostgreSQL instance on Supabase.
- Use a GUI tool: DB Browser for SQLite, TablePlus, or the Supabase web editor.
- Use real-ish data: download a dataset from Kaggle (movies, sales, flights) and query it.
- Practice sites: SQLZoo, Mode SQL Tutorial, and LeetCode's database problems.
Common mistakes
Using SELECT * in production queries. It fetches all columns even ones you don't need, wasting memory and bandwidth. Name your columns.
Forgetting WHERE in an UPDATE or DELETE. DELETE FROM users deletes every row. Always write WHERE first, test with a SELECT, then run the destructive query.
Not using indexes on columns you filter by. Without an index, a query on a million-row table scans every row. Add indexes on foreign keys and frequently-filtered columns.
Confusing = with IS NULL. WHERE email = NULL never matches anything. Use WHERE email IS NULL.
What to skip
- Stored procedures and triggers as a beginner — understand plain queries first; procedural SQL adds complexity before you need it.
- Vendor-specific SQL extensions early on — learn ANSI SQL first; the differences between PostgreSQL, MySQL, and SQLite are small and easy to look up.
- NoSQL as a replacement for SQL — NoSQL (MongoDB, DynamoDB) solves different problems; most apps need a relational database, and many need both.
FAQ
Do I need to know SQL if I use an ORM?
Yes. ORMs abstract away routine queries, but you'll write raw SQL for analytics, debugging slow queries, and anything the ORM can't express efficiently.
PostgreSQL or MySQL in 2026?
PostgreSQL. It has better standards compliance, more features (JSON, arrays, pgvector), and the community has been growing steadily. MySQL is fine for existing projects.
How long does SQL take to learn?
Basic SELECT/WHERE/JOIN: 1–2 weeks of daily practice. Production-level query optimization: months of real-world experience. The basics pay off very fast.
What's a primary key and why does it matter?
A primary key is a unique identifier for each row (usually an integer id). It's how tables link to each other (foreign keys reference it) and how queries find specific rows fast.
Where to go next
See What is an API in 2026, How to become a software engineer in 2026, and How to become a data analyst in 2026.