SELECT, WHERE and sorting — your first real queries
Retrieve exactly the rows and columns you need, filter with conditions, sort results, remove duplicates and limit output.
This lesson builds on: How relational databases work — tables, keys and types.
- Write SELECT … FROM queries that return chosen columns and calculated columns
- Filter rows with WHERE using comparison operators, AND/OR/NOT, IN, BETWEEN and LIKE
- Sort with ORDER BY, remove duplicates with DISTINCT, and limit rows with LIMIT (or TOP)
- Use aliases to make results readable
- Explain the logical order in which a query is evaluated
The shape of a query
SELECT columns you want
FROM table they come from
WHERE rows you keep
ORDER BY how to sort
LIMIT how many rows to show;
Only SELECT and FROM are required. SQL keywords are not case-sensitive (select = SELECT); writing them in capitals is a readability convention.
SELECT and FROM
Return three columns from products:
SELECT product_name, category, list_price
FROM products
LIMIT 5;| product_name | category | list_price |
|---|---|---|
| Headphones Basic | Electronics | 67.15 |
| Headphones Plus | Electronics | 79 |
| Headphones Pro | Electronics | 106.65 |
| Smartwatch Basic | Electronics | 169.15 |
| Smartwatch Plus | Electronics | 199 |
SELECT * returns every column. It is fine for exploring, but in reports and dashboards list columns explicitly — * breaks when someone adds a column and makes queries slower on wide tables.
Calculated columns and aliases
You can compute new columns. AS gives a column (or table) an alias:
SELECT product_name,
list_price,
unit_cost,
list_price - unit_cost AS unit_margin,
ROUND((list_price - unit_cost) / list_price * 100, 1) AS margin_pct
FROM products
LIMIT 5;| product_name | list_price | unit_cost | unit_margin | margin_pct |
|---|---|---|---|---|
| Headphones Basic | 67.15 | 29.75 | 37.4 | 55.7 |
| Headphones Plus | 79 | 35 | 44 | 55.7 |
| Headphones Pro | 106.65 | 47.25 | 59.4 | 55.7 |
| Smartwatch Basic | 169.15 | 76.5 | 92.65 | 54.8 |
| Smartwatch Plus | 199 | 90 | 109 | 54.8 |
Read row 1: the product sells for its list price, costs unit_cost, so each unit earns unit_margin before discounts, which is margin_pct of the price.
WHERE: keeping only the rows you need
WHERE keeps rows for which the condition is true. Comparison operators: =, <> (or !=), <, <=, >, >=.
SELECT store_name, city, open_date
FROM stores
WHERE region = 'East';| store_name | city | open_date |
|---|---|---|
| Orion Baku Central | Baku | 2019-04-01 |
| Orion Baku Riverside | Baku | 2020-09-15 |
| Orion Sumqayit | Sumqayit | 2018-11-10 |
| Orion Baku Airport | Baku | 2017-06-01 |
Text values go in single quotes. Dates stored in ISO format are compared as text and still work: order_date >= '2025-01-01'.
AND, OR, NOT
AND— both conditions must be true.OR— at least one must be true.NOT— reverses a condition.
SELECT order_id, order_date, channel, status, discount_pct
FROM orders
WHERE order_date >= '2025-11-01'
AND channel = 'online'
AND NOT status = 'completed'
LIMIT 6;| order_id | order_date | channel | status | discount_pct |
|---|---|---|---|---|
| 34555 | 2025-11-01 | online | returned | 0 |
| 34601 | 2025-11-02 | online | cancelled | 0 |
| 34617 | 2025-11-02 | online | returned | 0 |
| 34737 | 2025-11-05 | online | returned | 0 |
| 34749 | 2025-11-06 | online | returned | 0 |
| 34797 | 2025-11-07 | online | returned | 0 |
AND binds more tightly than OR. The condition
WHERE status = 'returned' OR status = 'cancelled' AND channel = 'online'
means returned (any channel) OR (cancelled AND online). If you meant "online orders that were returned or cancelled", write
WHERE (status = 'returned' OR status = 'cancelled') AND channel = 'online'. Use parentheses whenever you mix them.
IN, BETWEEN and LIKE
SELECT product_name, category, list_price
FROM products
WHERE category IN ('Electronics', 'Sports')
AND list_price BETWEEN 50 AND 100
AND product_name LIKE '%Pro'
ORDER BY list_price DESC
LIMIT 6;| product_name | category | list_price |
|---|---|---|
| Bluetooth Speaker Pro | Electronics | 87.75 |
| Tennis Racket Pro | Sports | 86.4 |
| Fitness Tracker Pro | Sports | 79.65 |
IN (…)is shorthand for severalOR … =comparisons.BETWEEN a AND bis inclusive of both ends (>= a AND <= b).LIKEmatches patterns:%= any sequence of characters,_= exactly one character.'%Pro'= ends with "Pro". (In SQLiteLIKEis case-insensitive for ASCII letters; in PostgreSQL it is case-sensitive — useILIKEthere.)
order_ts BETWEEN '2025-01-01' AND '2025-01-31' misses everything after midnight on 31 January if the column holds date and time. Use a half-open range: order_ts >= '2025-01-01' AND order_ts < '2025-02-01'. This habit works for every date type.
ORDER BY
Results have no guaranteed order unless you ask for one. ORDER BY sorts ascending (ASC, default) or descending (DESC); you can sort by several columns:
SELECT store_name, region, size_sqm
FROM stores
WHERE channel = 'store'
ORDER BY region ASC, size_sqm DESC;| store_name | region | size_sqm |
|---|---|---|
| Orion Istanbul Kadikoy | Central | 1500 |
| Orion Ankara | Central | 900 |
| Orion Baku Central | East | 1200 |
| Orion Baku Riverside | East | 850 |
| Orion Sumqayit | East | 640 |
| Orion Baku Airport | East | 220 |
DISTINCT
DISTINCT removes duplicate rows from the result — useful for listing the values a column takes:
SELECT DISTINCT status
FROM orders;| status |
|---|
| completed |
| returned |
| cancelled |
DISTINCT applies to the whole row you select: SELECT DISTINCT city, segment returns each unique combination.
LIMIT (and TOP)
LIMIT n returns at most n rows; combined with ORDER BY it gives "top N" lists. SQL Server uses SELECT TOP 5 … instead; standard SQL also has FETCH FIRST 5 ROWS ONLY.
SELECT product_name, list_price
FROM products
ORDER BY list_price DESC
LIMIT 5;| product_name | list_price |
|---|---|
| Tablet Pro | 471.15 |
| Tablet Plus | 349 |
| Tablet Basic | 296.65 |
| Smartwatch Pro | 268.65 |
| Smartwatch Plus | 199 |
LIMIT 5 without ORDER BY returns some five rows — which ones is not guaranteed and can change. Always pair "top N" with an explicit sort.
How SQL actually evaluates a query
You write SELECT first, but the database logically processes clauses in this order:
- FROM — which table(s)
- WHERE — filter rows
- GROUP BY — form groups (next lessons)
- HAVING — filter groups
- SELECT — compute output columns and aliases
- DISTINCT
- ORDER BY — sort
- LIMIT
This explains a classic error: you cannot use a SELECT alias inside WHERE (in most databases), because WHERE is evaluated before the alias exists. You can use it in ORDER BY.
Business application
A store manager asks: "Which Pro-variant products under 100 do we sell in Electronics? Most expensive first." That is one query: filter category, name pattern and price, then sort. Translating a sentence into WHERE conditions precisely — including what "under" means (< vs <=) — is the skill.
Practice — write the queries yourself
Use the editor to run your query before checking it. The answer checker executes your SQL and compares the result with the correct result, so any correct query is accepted.
Return the product_name and list_price of every product in the Beauty category, most expensive first.
How many orders were placed in December 2025 through the online channel? Return a single number.
List each distinct supplier in the products table (one column, one row per supplier).
Return order_id for orders paid by bnpl that were returned or cancelled and placed at store 5.
Log in to use the SQL playground.
Summary
SELECTchooses columns (including calculated ones with aliases);FROMchooses the table.WHEREfilters rows with comparisons,AND/OR/NOT(use parentheses!),IN,BETWEEN(inclusive) andLIKE.ORDER BYis the only way to guarantee order;DISTINCTremoves duplicate rows;LIMIT/TOPcaps output.- Logical order: FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT.