Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

SELECT and WHERE: asking for rows

Data, SQL and Getting to the Answer · lesson 2 of 9 · 7 min

Every query has the same skeleton

sql
SELECT   full_name, class_level
FROM     students
WHERE    class_level = 9
ORDER BY joined_on DESC
LIMIT    20;

FROM names where the rows come from. WHERE decides which rows survive. SELECT decides which columns of the survivors you see. ORDER BY sorts them. LIMIT cuts the list short.

Here is the thing that clears up half of all beginner confusion: you write SELECT first, but the database runs FROM first, then WHERE, then SELECT. The rows are chosen before the columns are picked. That is why this works perfectly well:

sql
SELECT full_name FROM students WHERE phone IS NOT NULL;

You filtered on a column you never asked to see. Nothing strange happened. WHERE was looking at whole rows; SELECT trimmed them afterwards.

WHERE tests one row at a time

This is the mental model to hold. The database walks the table, takes one row, evaluates your condition against the values *in that row only*, and keeps it if the answer is true. It cannot see the previous row. It cannot see the other rows belonging to the same customer. One row, in isolation, every time.

So given a payments table where Ade has two rows — 3,000 naira in cash and 8,000 naira by transfer:

sql
SELECT student_id FROM payments
WHERE method = 'cash' AND amount > 5000;

Ade does not appear. No single row of his is both cash and over 5,000. If you wanted "students who have paid cash *and* who have some payment over 5,000", that is a different question and needs the grouping you will meet in a later lesson.

Writing conditions

Text goes in single quotes. Double quotes mean something else entirely (a column name), and using them for text is one of the first errors you will hit.

sql
WHERE method = 'upi'                    -- exact match, case sensitive
WHERE method IN ('upi', 'card', 'cash') -- any of these
WHERE amount >= 5000 AND amount < 20000 -- a range
WHERE full_name LIKE 'A%'               -- starts with A
WHERE full_name ILIKE '%kumar%'         -- contains, case-insensitive (Postgres)

For an apostrophe inside text, double it: WHERE city = 'Coeur d''Alene'.

The AND/OR trap. AND binds tighter than OR, exactly like multiplication binds tighter than addition. This:

sql
WHERE country = 'NG' OR country = 'KE' AND amount > 5000

means country = 'NG' or (country = 'KE' and amount > 5000). Every Nigerian payment comes back, whatever the amount. The database is not wrong; you did not say what you meant. Put brackets in whenever you mix them, even when you are sure:

sql
WHERE (country = 'NG' OR country = 'KE') AND amount > 5000

Dates. Prefer half-open ranges over BETWEEN when the column stores a time as well as a date:

sql
WHERE paid_on >= '2026-03-01' AND paid_on < '2026-04-01'

BETWEEN '2026-03-01' AND '2026-03-31' looks equivalent and quietly drops everything that happened on 31 March after midnight, because 2026-03-31 14:02 is greater than 2026-03-31 00:00. That bug has cost more than one company a month-end report.

Look before you count

The habit that will save you the most time is boring. Before you aggregate anything, run the query with LIMIT 20 and read the rows with your eyes. Do the amounts look like amounts. Do the dates land in the range you expected. Is that column full of the string 'N/A'.

A count is a single number and a single number cannot look wrong. Twenty rows can look wrong immediately.

Before you move on

A payments table has one row per payment. Ade has two rows: 3,000 in cash and 8,000 by transfer. Someone runs `SELECT student_id FROM payments WHERE method = 'cash' AND amount > 5000;` Does Ade appear in the result?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly