SELECT, FROM, WHERE — Your First Queries

8 min read

Three keywords do most of the work in SQL: SELECT, FROM, and WHERE. Almost every verification query you'll ever write is some combination of those three. SELECT picks the columns you want to see, FROM names the table to read from, and WHERE filters the rows down to just the ones you care about. Once those three click, the rest of the language is decoration on top.

How a SELECT actually runs

When you run SELECT name, email FROM users WHERE role = 'admin';, the database does three things in order:

Step 1 of 4

FROM users

Start with every row in the named table — the full users table is the input.

Even though you write the keywords as SELECT … FROM … WHERE …, they execute in the order FROM → WHERE → SELECT. That's a useful mental model: shrink the rows first, then pick the columns. Big real-world tables run faster when you follow that pattern (the database doesn't waste time formatting columns for rows it's about to throw away).

The simplest SELECT — every row, every column

SELECT * returns everything:

SELECT * FROM users;

Result:

| id | name        | email             | role   | is_active | created_at          |
|----|-------------|-------------------|--------|-----------|---------------------|
| 1  | Alice Khan  | alice@example.com | admin  | TRUE      | 2026-04-12 09:14:00 |
| 2  | Bob Patel   | bob@example.com   | tester | TRUE      | 2026-04-15 16:02:11 |
| 3  | Carol Singh | carol@example.com | tester | FALSE     | 2026-04-22 11:48:53 |
| 4  | Dan Müller  | dan@example.com   | viewer | TRUE      | 2026-05-01 08:30:27 |
| 5  | Esha Roy    | esha+qa@test.com  | tester | TRUE      | 2026-05-04 14:55:09 |

* means "every column." Useful for exploring an unfamiliar table; not great for production queries because it hauls back columns you don't need (slower, wider result, harder to scan).

Selecting specific columns

Most of the time you only want a few columns. Name them, comma-separated:

SELECT name, email FROM users;

Result:

| name        | email             |
|-------------|-------------------|
| Alice Khan  | alice@example.com |
| Bob Patel   | bob@example.com   |
| Carol Singh | carol@example.com |
| Dan Müller  | dan@example.com   |
| Esha Roy    | esha+qa@test.com  |

Cleaner output, faster query, and you can read the result without scrolling sideways. As a habit, list the columns you actually want.

WHERE — filtering rows

The WHERE clause is what turns "show me the table" into "show me the rows that matter." A condition follows WHERE, and only rows where the condition is true come back:

SELECT name, email, role FROM users WHERE role = 'admin';

Result:

| name       | email             | role  |
|------------|-------------------|-------|
| Alice Khan | alice@example.com | admin |

One row — Alice is the only admin in our sample data. Now four practical examples a tester runs all the time:

-- 1. All active admin users
SELECT name, email FROM users
WHERE role = 'admin' AND is_active = TRUE;
 
-- 2. All orders that are still pending
SELECT id, user_id, total FROM orders
WHERE status = 'pending';
 
-- 3. Products that are currently out of stock
SELECT name, price FROM products
WHERE in_stock = FALSE;
 
-- 4. Did the user we just created through the API actually save?
SELECT * FROM users
WHERE email = 'newuser@test.com';

That last query is one of the most common "why testers learn SQL" patterns: after a POST /api/users, run a SELECT to confirm the row really exists.

Quotes, numbers, and other little things

Three small but important rules about WHERE conditions:

  • Text values use single quotes. WHERE name = 'Alice', not WHERE name = "Alice". Most databases reject double-quoted strings (they treat double quotes as identifier quoting, like for column names with spaces).
  • Numbers don't get quoted. WHERE id = 1, WHERE price > 10.00. Quoting a number turns it into a string and may give you wrong (or zero) results.
  • Booleans: WHERE is_active = TRUE works in PostgreSQL and most modern databases. MySQL and SQLite store booleans as 0/1, so WHERE is_active = 1 also works there. Either way, be consistent.

Comparison operators — the tester's toolkit

The operators you'll meet in WHERE clauses:

OperatorMeaningExample
=equalsWHERE role = 'admin'
!= or <>not equalsWHERE status != 'cancelled'
> < >= <=numeric comparisonWHERE total > 100.00
IS NULLthe value is missingWHERE email IS NULL
IS NOT NULLthe value is setWHERE deleted_at IS NOT NULL

= is the workhorse. The two surprises for SQL beginners are != (not the JavaScript !==) and the NULL operators below.

NULL needs IS, not =

NULL is the database's "no value." Comparing it with = doesn't behave the way you expect:

-- This returns NOTHING, even for users with no email!
SELECT * FROM users WHERE email = NULL;
 
-- This is the correct way:
SELECT * FROM users WHERE email IS NULL;
 
-- And the inverse:
SELECT * FROM users WHERE email IS NOT NULL;

Why? Because in SQL, any comparison with NULL is itself NULL (not TRUE, not FALSE — NULL). So email = NULL is NULL for every row, and the WHERE clause only keeps rows where the condition is TRUE. The special operators IS NULL and IS NOT NULL exist precisely to handle this case.

A real verification flow

You ran a UI test that creates a new user with email qa-2026-05-06@test.com and role tester. Verify it landed in the database correctly:

SELECT id, name, email, role, is_active, created_at
FROM users
WHERE email = 'qa-2026-05-06@test.com';

Expected result — exactly one row, with the role 'tester', is_active = TRUE, and a created_at timestamp roughly equal to "now." If the row is missing, the API succeeded but the database didn't commit. If the role is wrong, the API saved a different value than the UI sent. Both are real bugs, and both were invisible without this query.

⚠️ Common Mistakes

  • WHERE column = NULL instead of WHERE column IS NULL. A query that quietly returns zero rows when it should return many. Always use IS NULL / IS NOT NULL for null checks.
  • Double quotes around strings. WHERE name = "Alice" works in some databases but fails in others (PostgreSQL treats double quotes as identifier quoting). Always single-quote strings to keep your SQL portable.
  • SELECT * in test assertions. A schema change adds a column and your test suddenly has different output. List the columns you actually verify — your tests become less brittle and easier to read.

🎯 Practice Task

20–30 minutes. Use the e-commerce database from Chapter 1, Lesson 4.

  1. Run SELECT * FROM products; to see all 10 products.
  2. Write a query that returns only the name and price of products in stock. (Hint: WHERE in_stock = TRUE.)
  3. Write a query for products priced over $50.
  4. Write a query for orders with status 'completed'.
  5. Write a query for users whose role is not 'admin'. (Hint: use !=.)
  6. Write a query that returns the user with email 'esha+qa@test.com' — confirm exactly one row comes back.
  7. Stretch: combine two conditions. Find products that are in stock and priced under $50 — these are the candidates for a "deals under $50" page on the storefront. Write the query and read the results.

In the next lesson we widen WHERE to handle ranges, patterns, and partial matches with comparison operators and LIKE.

// tip to track lessons you complete and pick up where you left off across devices.