A query asks the database to return rows. To read a query well, identify its source, its row filter, its groups, and its output. Then check whether the requested order is explicit. Many query mistakes come from getting one of those parts right and quietly assuming the rest.
Read a query in stages
Suppose orders has customer_id, status, and amount columns:
SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(amount) >= 100
ORDER BY total DESC, customer_id;
A useful logical reading is:
FROM ordersprovides the source rows.WHEREkeeps only paid orders.GROUP BYmakes one group per customer.HAVINGkeeps groups whose sum is at least 100.SELECTproduces a customer ID and sum for each surviving group.ORDER BYputs larger totals first and breaks ties by customer ID.
This is a way to reason about the result. It is not a step-by-step promise about the physical execution plan. The database may reorder work internally while preserving the query's meaning.
Rows can repeat
SQL query results are not automatically unique. If two orders have the same amount, SELECT amount FROM orders can return that amount twice. DISTINCT removes duplicate output rows when you ask for it. Use it only when duplicates are truly unwanted; sometimes repeated values are the point of the data.
SELECT * is useful when exploring a table. For code you maintain, naming the needed columns makes the result shape clear and avoids pulling data you do not use.
Missing values change logic
If status is NULL, status = 'paid' is unknown, so WHERE does not keep that row. NULL also affects joins, groups, and aggregates. Decide what a missing value means before adding a default with COALESCE.
Order is a separate request
The database does not guarantee a row order unless the query uses ORDER BY. A limit without a stable sort can return different rows as data or plans change. For pages of results, include a tie-breaker such as a unique ID.
Ask one clear question
Before writing SQL, say what one output row should represent. In the example, one output row represents one customer who spent at least 100 on paid orders. This sentence tells you why the query groups by customer and why status is filtered before the sum.
Check your understanding
- In the example, does
WHEREfilter orders or customers? - Why is
HAVINGused forSUM(amount)? - Could the result order be relied on if
ORDER BYwere removed? - What does one output row represent?