Skip to content
OrionStats
Module 2 · SQL Fundamentals · Selecting and filtering rows

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.

Beginner50 min
Recommended first

This lesson builds on: How relational databases work — tables, keys and types.

Learning objectives
  • 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;
Result · 5 rows
product_namecategorylist_price
Headphones BasicElectronics67.15
Headphones PlusElectronics79
Headphones ProElectronics106.65
Smartwatch BasicElectronics169.15
Smartwatch PlusElectronics199

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;
Result · 5 rows
product_namelist_priceunit_costunit_marginmargin_pct
Headphones Basic67.1529.7537.455.7
Headphones Plus79354455.7
Headphones Pro106.6547.2559.455.7
Smartwatch Basic169.1576.592.6554.8
Smartwatch Plus1999010954.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';
Result · 4 rows
store_namecityopen_date
Orion Baku CentralBaku2019-04-01
Orion Baku RiversideBaku2020-09-15
Orion SumqayitSumqayit2018-11-10
Orion Baku AirportBaku2017-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;
Result · 6 rows
order_idorder_datechannelstatusdiscount_pct
345552025-11-01onlinereturned0
346012025-11-02onlinecancelled0
346172025-11-02onlinereturned0
347372025-11-05onlinereturned0
347492025-11-06onlinereturned0
347972025-11-07onlinereturned0
Mixing AND and OR without parentheses

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;
Result · 3 rows
product_namecategorylist_price
Bluetooth Speaker ProElectronics87.75
Tennis Racket ProSports86.4
Fitness Tracker ProSports79.65
  • IN (…) is shorthand for several OR … = comparisons.
  • BETWEEN a AND b is inclusive of both ends (>= a AND <= b).
  • LIKE matches patterns: % = any sequence of characters, _ = exactly one character. '%Pro' = ends with "Pro". (In SQLite LIKE is case-insensitive for ASCII letters; in PostgreSQL it is case-sensitive — use ILIKE there.)
BETWEEN with timestamps

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;
Result · first 6 rows
store_nameregionsize_sqm
Orion Istanbul KadikoyCentral1500
Orion AnkaraCentral900
Orion Baku CentralEast1200
Orion Baku RiversideEast850
Orion SumqayitEast640
Orion Baku AirportEast220

DISTINCT

DISTINCT removes duplicate rows from the result — useful for listing the values a column takes:

SELECT DISTINCT status
FROM orders;
Result · 3 rows
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;
Result · 5 rows
product_namelist_price
Tablet Pro471.15
Tablet Plus349
Tablet Basic296.65
Smartwatch Pro268.65
Smartwatch Plus199
LIMIT without ORDER BY

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:

  1. FROM — which table(s)
  2. WHERE — filter rows
  3. GROUP BY — form groups (next lessons)
  4. HAVING — filter groups
  5. SELECT — compute output columns and aliases
  6. DISTINCT
  7. ORDER BY — sort
  8. 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.

Log in to answer and get feedback

How many orders were placed in December 2025 through the online channel? Return a single number.

Log in to answer and get feedback

List each distinct supplier in the products table (one column, one row per supplier).

Log in to answer and get feedback

Return order_id for orders paid by bnpl that were returned or cancelled and placed at store 5.

Log in to answer and get feedback

Log in to use the SQL playground.

Summary

  • SELECT chooses columns (including calculated ones with aliases); FROM chooses the table.
  • WHERE filters rows with comparisons, AND/OR/NOT (use parentheses!), IN, BETWEEN (inclusive) and LIKE.
  • ORDER BY is the only way to guarantee order; DISTINCT removes duplicate rows; LIMIT/TOP caps output.
  • Logical order: FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT.