5 Common SQL Mistakes Every Beginner Makes
Learning From Mistakes
SQL is deceptively simple. The basic syntax looks straightforward, but there are subtle traps that catch nearly every beginner. Understanding these common mistakes early will save you hours of debugging and help you write better queries from the start.
1. Forgetting the WHERE Clause on UPDATE and DELETE
This is the most dangerous mistake a beginner can make, and nearly everyone does it at least once. Consider this query:
DELETE FROM customers;
That looks innocent, but it deletes every single row in the customers table. What you probably meant was:
DELETE FROM customers WHERE status = 'inactive';
The same applies to UPDATE. Running UPDATE users SET role = 'admin' without a WHERE clause promotes every user to admin. In a production database, this kind of mistake can be catastrophic.
Tip: Always write your WHERE clause first, then add the DELETE or UPDATE keyword. Some developers even run a SELECT with the same WHERE clause first to verify which rows will be affected.
2. Ignoring NULL Values
NULL in SQL doesn't mean zero or empty string — it means "unknown." This distinction causes endless confusion because NULL behaves differently from any other value.
-- This will NOT return rows where email is NULL
SELECT * FROM users WHERE email != 'test@example.com';
-- You need IS NULL to find NULL values
SELECT * FROM users WHERE email IS NULL;
NULL also breaks comparisons in surprising ways. NULL = NULL evaluates to NULL (not TRUE), and NULL != NULL is also NULL. Any arithmetic with NULL produces NULL: 5 + NULL is NULL, not 5.
Tip: Use COALESCE(column, default_value) when you want to treat NULL as a specific value, and always think about whether your columns can contain NULLs when writing conditions.
3. Using SELECT * in Production Queries
When you're exploring data, SELECT * is fine. But using it in application code is a bad habit for several reasons:
-- Don't do this in your app
SELECT * FROM orders;
-- Do this instead
SELECT id, customer_id, total, created_at FROM orders;
SELECT * fetches every column, including large text fields or binary data you might not need. This wastes network bandwidth and memory. It also makes your code fragile — if someone adds a column to the table, your application might break or behave unexpectedly.
Tip: Always list the specific columns you need. It makes your queries faster, your code more readable, and your application more resilient to schema changes.
4. Not Understanding JOIN Types
Many beginners learn INNER JOIN and use it everywhere, then wonder why rows are disappearing from their results.
-- INNER JOIN: only returns rows that match in BOTH tables
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- Users with no orders are EXCLUDED
-- LEFT JOIN: returns ALL users, even those without orders
SELECT u.name, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- Users with no orders appear with NULL for o.total
The difference is critical. If you're building a report of "all users and their order totals," an INNER JOIN silently drops users who haven't placed orders. A LEFT JOIN includes everyone.
Tip: Think carefully about which table's rows you need to preserve. Use LEFT JOIN when the left table should always appear in results, and INNER JOIN when you only want matching rows from both sides.
5. Writing Inefficient Subqueries Instead of JOINs
Beginners often write correlated subqueries that execute once per row in the outer query, when a simple JOIN or GROUP BY would be far more efficient:
-- Slow: subquery runs for EVERY row
SELECT name,
(SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count
FROM users;
-- Fast: single pass with JOIN and GROUP BY
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.name;
On small datasets the difference is negligible, but on tables with millions of rows, a correlated subquery can take minutes while the join version finishes in seconds.
Tip: If your subquery references a column from the outer query, it's correlated and potentially slow. Try rewriting it as a JOIN with GROUP BY first.
Building Good Habits
The best way to avoid these mistakes is to practice deliberately. Write queries, run them, examine the results, and ask yourself: "Is this really returning what I expect?" Use EXPLAIN ANALYZE to understand how PostgreSQL executes your queries, and don't be afraid to experiment in a sandbox environment.
Every expert was once a beginner who made these exact mistakes. The difference is that they learned from them and built better habits over time. You can too.