SQL Joins Explained: Inner, Left, Right and Full
What INNER, LEFT, RIGHT and FULL joins actually do, why rows duplicate or disappear, and how to pick the right one for a report.
Short answer: Most slow queries come from one of a few causes: a full table scan where an index could help, a function wrapped around an indexed column that prevents the index from being used, or a huge join happening before a filter narrows things down.
EXPLAIN(orEXPLAIN ANALYZE) shows you the plan the database actually chose, and the fix is usually visible once you know what to look for.
A query that took two seconds last month and takes twenty seconds today almost never has a mysterious cause. The data grew, an index went missing, or a filter that used to be selective stopped being selective. The execution plan tells you which.
Before running a query, the database’s optimizer decides how to run it: which indexes to use, in what order to join tables, whether to scan a whole table or jump to specific rows. EXPLAIN shows you that plan without running the query; EXPLAIN ANALYZE runs it and shows you the actual time and row counts alongside the plan’s estimates.
EXPLAIN ANALYZE
SELECT customer_id, SUM(amount)
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY customer_id;
The output is a tree, read from the innermost (most indented) step outward. Each line names an operation — a scan, a join, a sort, an aggregate — along with an estimated cost and, with ANALYZE, the actual time and row count.
If EXPLAIN shows a sequential scan on a large table where you expected an index scan, that’s usually your answer. Common reasons an index isn’t used even though one exists:
WHERE YEAR(order_date) = 2026), which prevents most databases from using a plain index on order_date. Rewriting it as a range (WHERE order_date >= '2026-01-01' AND order_date < '2027-01-01') restores index use.EXPLAIN ANALYZE shows both. When they’re close, the optimizer had good statistics and likely chose a sensible plan. When they’re far apart — say, an estimate of 100 rows and an actual of 2 million — the optimizer was working from stale statistics and probably picked the wrong join order or join type as a result. The fix is usually to update statistics (ANALYZE the table, or your database’s equivalent), not to rewrite the query.
WHERE and JOIN ... ON clauses, especially on large tables.WHERE clause on a cleanly indexed column, applied as early as possible, gives the optimizer more room to filter before joining.SELECT * on a wide table. Pulling every column, including large text or blob fields you don’t need, adds I/O for no benefit. Select only the columns you use.varchar to an int, forces a conversion on every row and often disables an index in the process.EXPLAIN ANALYZE on the slow query.EXPLAIN ANALYZE after each change to confirm it actually helped. A plan can look better and still run about the same if you fixed the wrong step.EXPLAIN after adding it.My take: learn to read one execution plan properly before you learn a dozen performance tricks. Most “SQL is slow” problems trace back to the same three or four root causes above, and the plan tells you which one you’re looking at instead of leaving you to guess.
If your slowness shows up specifically inside Power BI rather than raw SQL, the causes overlap but the fixes differ — see Slow Power BI report? 7 fixes in order of payoff.
Part of: SQL & Local Analytics