{"course":{"slug":"data-and-sql","title":"Data, SQL and Getting to the Answer","url":"https://addaly.com/learn/data-and-sql","level":"beginner","track":"build"},"lesson":{"slug":"select-and-where","title":"SELECT and WHERE: asking for rows","url":"https://addaly.com/learn/data-and-sql/select-and-where","position":6,"of":66,"minutes":7,"answer":"WHERE tests one row at a time, in isolation; it never sees the other rows that share a customer.","keyIdea":"WHERE tests one row at a time, in isolation; it never sees the other rows that share a customer."},"body":{"format":"markdown","text":"## Every query has the same skeleton\n\n```sql\nSELECT   full_name, class_level\nFROM     students\nWHERE    class_level = 9\nORDER BY joined_on DESC\nLIMIT    20;\n```\n\n`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.\n\nHere 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:\n\n```sql\nSELECT full_name FROM students WHERE phone IS NOT NULL;\n```\n\nYou filtered on a column you never asked to see. Nothing strange happened. WHERE was looking at whole rows; SELECT trimmed them afterwards.\n\n## WHERE tests one row at a time\n\nThis 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.\n\nSo given a `payments` table where Ade has two rows: 3,000 naira in cash and 8,000 naira by transfer:\n\n```sql\nSELECT student_id FROM payments\nWHERE method = 'cash' AND amount > 5000;\n```\n\nAde 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.\n\n## Writing conditions\n\nText 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.\n\n```sql\nWHERE method = 'upi'                    -- exact match, case sensitive\nWHERE method IN ('upi', 'card', 'cash') -- any of these\nWHERE amount >= 5000 AND amount < 20000 -- a range\nWHERE full_name LIKE 'A%'               -- starts with A\nWHERE full_name ILIKE '%kumar%'         -- contains, case-insensitive (Postgres)\n```\n\nFor an apostrophe inside text, double it: `WHERE city = 'Coeur d''Alene'`.\n\n**The AND/OR trap.** AND binds tighter than OR, exactly like multiplication binds tighter than addition. This:\n\n```sql\nWHERE country = 'NG' OR country = 'KE' AND amount > 5000\n```\n\nmeans `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:\n\n```sql\nWHERE (country = 'NG' OR country = 'KE') AND amount > 5000\n```\n\n**Dates.** Prefer half-open ranges over BETWEEN when the column stores a time as well as a date:\n\n```sql\nWHERE paid_on >= '2026-03-01' AND paid_on < '2026-04-01'\n```\n\n`BETWEEN '2026-03-01' AND '2026-03-31'` looks equivalent and 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.\n\n## Look before you count\n\nThe 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'`.\n\nA count is a single number and a single number cannot look wrong. Twenty rows can look wrong immediately."},"check":{"question":"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?","options":[{"text":"Yes, because he has a cash payment and he has a payment over 5,000, so both conditions hold for him","correct":false,"tempting":"Reads the conditions as applying to the person rather than to a single row."},{"text":"No, because no single row of his satisfies both conditions at once","correct":true},{"text":"Yes, because the database combines a student's rows before applying the filter","correct":false,"tempting":"Believes grouping by student is implicit whenever a query mentions a student column."},{"text":"No, because AND requires the two conditions to refer to the same column","correct":false,"tempting":"Invents a restriction on AND rather than noticing that filtering happens per row."}]},"prev":{"slug":"one-fact-in-one-place","title":"Where each fact should live","url":"https://addaly.com/learn/data-and-sql/one-fact-in-one-place"},"next":{"slug":"sorting-limits-and-ties","title":"Sorting, limits, and the rows that move between runs","url":"https://addaly.com/learn/data-and-sql/sorting-limits-and-ties"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'SELECT and WHERE: asking for rows', in Data, SQL and Getting to the Answer. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/data-and-sql/select-and-where (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"SELECT and WHERE: asking for rows","courseTitle":"Data, SQL and Getting to the Answer","module":{"index":1,"slug":"tables-types-and-the-question","title":"Tables, types and the question you are actually asking"},"answer":null,"keyIdea":"WHERE tests one row at a time, in isolation; it never sees the other rows that share a customer.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"every-query-has-the-same-skeleton","runs":[{"text":"Every query has the same skeleton"}]},{"t":"code","lang":"sql","label":"SQL","text":"SELECT   full_name, class_level\nFROM     students\nWHERE    class_level = 9\nORDER BY joined_on DESC\nLIMIT    20;"},{"t":"p","runs":[{"text":"FROM","code":true},{"text":" names where the rows come from. "},{"text":"WHERE","code":true},{"text":" decides which rows survive. "},{"text":"SELECT","code":true},{"text":" decides which columns of the survivors you see. "},{"text":"ORDER BY","code":true},{"text":" sorts them. "},{"text":"LIMIT","code":true},{"text":" cuts the list short."}]},{"t":"p","runs":[{"text":"Here is the thing that clears up half of all beginner confusion: "},{"text":"you write SELECT first, but the database runs FROM first, then WHERE, then SELECT.","b":true},{"text":" The rows are chosen before the columns are picked. That is why this works perfectly well:"}]},{"t":"code","lang":"sql","label":"SQL","text":"SELECT full_name FROM students WHERE phone IS NOT NULL;"},{"t":"p","runs":[{"text":"You filtered on a column you never asked to see. Nothing strange happened. WHERE was looking at whole rows; SELECT trimmed them afterwards."}]},{"t":"h2","id":"where-tests-one-row-at-a-time","runs":[{"text":"WHERE tests one row at a time"}]},{"t":"p","runs":[{"text":"This is the mental model to hold. The database walks the table, takes one row, evaluates your condition against the values "},{"text":"in that row only","i":true},{"text":", 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."}]},{"t":"p","runs":[{"text":"So given a "},{"text":"payments","code":true},{"text":" table where Ade has two rows: 3,000 naira in cash and 8,000 naira by transfer:"}]},{"t":"code","lang":"sql","label":"SQL","text":"SELECT student_id FROM payments\nWHERE method = 'cash' AND amount > 5000;"},{"t":"p","runs":[{"text":"Ade does not appear. No single row of his is both cash and over 5,000. If you wanted “students who have paid cash "},{"text":"and","i":true},{"text":" who have some payment over 5,000”, that is a different question and needs the grouping you will meet in a later lesson."}]},{"t":"h2","id":"writing-conditions","runs":[{"text":"Writing conditions"}]},{"t":"p","runs":[{"text":"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."}]},{"t":"code","lang":"sql","label":"SQL","text":"WHERE method = 'upi'                    -- exact match, case sensitive\nWHERE method IN ('upi', 'card', 'cash') -- any of these\nWHERE amount >= 5000 AND amount < 20000 -- a range\nWHERE full_name LIKE 'A%'               -- starts with A\nWHERE full_name ILIKE '%kumar%'         -- contains, case-insensitive (Postgres)"},{"t":"p","runs":[{"text":"For an apostrophe inside text, double it: "},{"text":"WHERE city = 'Coeur d''Alene'","code":true},{"text":"."}]},{"t":"p","runs":[{"text":"The AND/OR trap.","b":true},{"text":" AND binds tighter than OR, exactly like multiplication binds tighter than addition. This:"}]},{"t":"code","lang":"sql","label":"SQL","text":"WHERE country = 'NG' OR country = 'KE' AND amount > 5000"},{"t":"p","runs":[{"text":"means "},{"text":"country = 'NG'","code":true},{"text":" "},{"text":"or","b":true},{"text":" "},{"text":"(country = 'KE' and amount > 5000)","code":true},{"text":". 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:"}]},{"t":"code","lang":"sql","label":"SQL","text":"WHERE (country = 'NG' OR country = 'KE') AND amount > 5000"},{"t":"p","runs":[{"text":"Dates.","b":true},{"text":" Prefer half-open ranges over BETWEEN when the column stores a time as well as a date:"}]},{"t":"code","lang":"sql","label":"SQL","text":"WHERE paid_on >= '2026-03-01' AND paid_on < '2026-04-01'"},{"t":"p","runs":[{"text":"BETWEEN '2026-03-01' AND '2026-03-31'","code":true},{"text":" looks equivalent and drops everything that happened on 31 March after midnight, because "},{"text":"2026-03-31 14:02","code":true},{"text":" is greater than "},{"text":"2026-03-31 00:00","code":true},{"text":". That bug has cost more than one company a month-end report."}]},{"t":"h2","id":"look-before-you-count","runs":[{"text":"Look before you count"}]},{"t":"p","runs":[{"text":"The habit that will save you the most time is boring. Before you aggregate anything, run the query with "},{"text":"LIMIT 20","code":true},{"text":" 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 "},{"text":"'N/A'","code":true},{"text":"."}]},{"t":"p","runs":[{"text":"A count is a single number and a single number cannot look wrong. Twenty rows can look wrong immediately."}]}],"check":{"question":[{"text":"A payments table has one row per payment. Ade has two rows: 3,000 in cash and 8,000 by transfer. Someone runs "},{"text":"SELECT student_id FROM payments WHERE method = 'cash' AND amount > 5000;","code":true},{"text":" Does Ade appear in the result?"}],"options":[{"text":[{"text":"Yes, because he has a cash payment and he has a payment over 5,000, so both conditions hold for him"}],"correct":false,"tempting":[{"text":"Reads the conditions as applying to the person rather than to a single row."}]},{"text":[{"text":"No, because no single row of his satisfies both conditions at once"}],"correct":true,"tempting":null},{"text":[{"text":"Yes, because the database combines a student’s rows before applying the filter"}],"correct":false,"tempting":[{"text":"Believes grouping by student is implicit whenever a query mentions a student column."}]},{"text":[{"text":"No, because AND requires the two conditions to refer to the same column"}],"correct":false,"tempting":[{"text":"Invents a restriction on AND rather than noticing that filtering happens per row."}]}],"order":[2,3,1,0],"why":null},"prev":{"slug":"one-fact-in-one-place","title":"Where each fact should live","minutes":9},"next":{"slug":"sorting-limits-and-ties","title":"Sorting, limits, and the rows that move between runs","minutes":7}}}