SELECT, WHERE, ORDER BY
Everything an analyst does in SQL is a variation on one statement. Even a 200-line report query with six JOINs and three window functions is still, structurally, SELECT ... FROM ... WHERE ... ORDER BY .... So it pays to know these four clauses extremely well before adding anything on top.
Every query below runs on the school.db from Chapter 1.
Choosing columns
SELECT answers the question "which columns do you want to see?" — and the columns come out in the order you name them, not in the order they sit in the table.
-- all columns (exploring only) SELECT * FROM students; -- named columns, in the order YOU choose SELECT last_name, first_name, city FROM students; -- a column can appear more than once, and constants are allowed SELECT first_name, first_name, 'student' FROM students;
Result 1
| student_id | first_name | last_name | gender | city | birth_date | enrolled_date |
|---|---|---|---|---|---|---|
| 1 | Shahnoza | Alimova | F | Bukhara | 1998-10-10 | 2025-09-03 |
| 2 | Doston | Umarov | M | Fergana | 1999-11-23 | 2025-02-13 |
| 3 | Zulfiya | Yusupova | F | Samarkand | 2002-07-30 | 2025-02-13 |
| 4 | Kamola | Karimova | F | Samarkand | 2002-08-14 | 2025-02-05 |
| 5 | Gulnora | Saidova | F | Namangan | 1998-03-21 | 2025-09-07 |
| 6 | Bekzod | Yusupov | M | Samarkand | 2002-02-21 | 2025-09-07 |
| 7 | Doston | Alimov | M | Karshi | 2004-02-08 | 2025-09-10 |
| 8 | Madina | Rahimova | F | Tashkent | 2000-05-29 | 2025-02-09 |
| 9 | Sardor | Alimov | M | Bukhara | 2003-05-08 | 2025-02-04 |
| 10 | Dilnoza | Alimova | F | Andijan | 2000-08-26 | 2025-09-08 |
| 11 | Jasur | Ergashev | M | Karshi | 1999-08-05 | 2025-09-07 |
| 12 | Rustam | Toshev | M | Tashkent | 2001-02-12 | 2025-09-08 |
| 13 | Bekzod | Qodirov | M | Namangan | 2002-05-05 | 2025-02-08 |
| 14 | Otabek | Saidov | M | Andijan | 2000-10-28 | 2025-02-09 |
| 15 | Madina | Umarova | F | Namangan | 2001-12-26 | 2025-02-05 |
… first 15 of 300 rows
300 rows
Result 2
| last_name | first_name | city |
|---|---|---|
| Alimova | Shahnoza | Bukhara |
| Umarov | Doston | Fergana |
| Yusupova | Zulfiya | Samarkand |
| Karimova | Kamola | Samarkand |
| Saidova | Gulnora | Namangan |
| Yusupov | Bekzod | Samarkand |
| Alimov | Doston | Karshi |
| Rahimova | Madina | Tashkent |
| Alimov | Sardor | Bukhara |
| Alimova | Dilnoza | Andijan |
| Ergashev | Jasur | Karshi |
| Toshev | Rustam | Tashkent |
| Qodirov | Bekzod | Namangan |
| Saidov | Otabek | Andijan |
| Umarova | Madina | Namangan |
… first 15 of 300 rows
300 rows
Result 3
| first_name | first_name | 'student' |
|---|---|---|
| Shahnoza | Shahnoza | student |
| Doston | Doston | student |
| Zulfiya | Zulfiya | student |
| Kamola | Kamola | student |
| Gulnora | Gulnora | student |
| Bekzod | Bekzod | student |
| Doston | Doston | student |
| Madina | Madina | student |
| Sardor | Sardor | student |
| Dilnoza | Dilnoza | student |
| Jasur | Jasur | student |
| Rustam | Rustam | student |
| Bekzod | Bekzod | student |
| Otabek | Otabek | student |
| Madina | Madina | student |
… first 15 of 300 rows
300 rows
2 ms
That last one looks pointless, but it teaches an important idea: SELECT builds a new result table. It does not "show you the table" — it constructs a fresh one, column by column, from whatever expressions you write.
The input is a table, the output is a table. That single fact is why a query can be nested inside another query (Chapter 6), and why a query can be joined to another query (Chapter 5). Say it early and repeat it often.
Computed columns (expressions)
Anything you can compute, you can put in SELECT. The result becomes a new column that exists only in the output — nothing is written back to the table.
SELECT title, price, price * 0.9 AS discounted_price, price / credits AS price_per_credit, credits * 30 AS total_hours FROM courses;
| title | price | discounted_price | price_per_credit | total_hours |
|---|---|---|---|---|
| Python Basics | 1 200 000 | 1 080 000 | 300 000 | 120 |
| SQL for Analytics | 800 000 | 720 000 | 266 666 | 90 |
| Statistics 101 | 1 200 000 | 1 080 000 | 300 000 | 120 |
| Data Visualization | 900 000 | 810 000 | 300 000 | 90 |
| Machine Learning | 1 450 000 | 1 305 000 | 290 000 | 150 |
| Web Development | 1 150 000 | 1 035 000 | 287 500 | 120 |
| Excel for Business | 1 100 000 | 990 000 | 550 000 | 60 |
| Project Management | 1 500 000 | 1 350 000 | 500 000 | 90 |
| UX Design | 850 000 | 765 000 | 283 333 | 90 |
| Graphic Design | 950 000 | 855 000 | 316 666 | 90 |
| Linear Algebra | 1 200 000 | 1 080 000 | 300 000 | 120 |
| Deep Learning | 1 550 000 | 1 395 000 | 310 000 | 150 |
| Java Basics | 1 150 000 | 1 035 000 | 287 500 | 120 |
| Financial Analysis | 1 500 000 | 1 350 000 | 500 000 | 90 |
| Probability | 1 250 000 | 1 125 000 | 312 500 | 120 |
15 rows
0 ms
:::gotcha ⚠️ Look at Data Visualization: 316666, not 316666.67
950000 / 3 gives 316666 because integer ÷ integer = integer in SQL — the decimal part is thrown away, not rounded. This silently corrupts averages and percentages. Force a decimal by multiplying by 1.0: price * 1.0 / credits, or cast: CAST(price AS REAL) / credits. We come back to this in Chapter 4, where it wrecks a percentage calculation.
:::
Joining text together
In SQLite and PostgreSQL, || glues strings:
SELECT first_name || ' ' || last_name AS full_name, last_name || ', ' || first_name AS sorted_name, UPPER(city) AS city_upper, LENGTH(first_name) AS name_length FROM students LIMIT 4;
| full_name | sorted_name | city_upper | name_length |
|---|---|---|---|
| Shahnoza Alimova | Alimova, Shahnoza | BUKHARA | 8 |
| Doston Umarov | Umarov, Doston | FERGANA | 6 |
| Zulfiya Yusupova | Yusupova, Zulfiya | SAMARKAND | 7 |
| Kamola Karimova | Karimova, Kamola | SAMARKAND | 6 |
4 rows
0 ms
:::gotcha ⚠️ NULL swallows the whole string
For Sevara, city is NULL, so UPPER(city) is NULL — and if you wrote city || ', Uzbekistan' the entire result would be NULL, not ', Uzbekistan'. Wrap it: COALESCE(city, 'unknown') || ', Uzbekistan'. Chapter 3 covers COALESCE properly.
:::
Aliases (AS)
An alias renames a column (or a table) in the output. Without one, the header of a computed column is the raw expression — unreadable in a report.
-- header becomes: price * 0.9 ← ugly SELECT title, price * 0.9 FROM courses; -- header becomes: discounted_price ← readable SELECT title, price * 0.9 AS discounted_price FROM courses; -- AS is optional, but write it anyway — it is far easier to read SELECT title, price * 0.9 discounted_price FROM courses; -- a name with a space needs double quotes (identifier quoting) SELECT title AS "Course Title", price AS "Price (UZS)" FROM courses;
Result 1
| title | price * 0.9 |
|---|---|
| Python Basics | 1 080 000 |
| SQL for Analytics | 720 000 |
| Statistics 101 | 1 080 000 |
| Data Visualization | 810 000 |
| Machine Learning | 1 305 000 |
| Web Development | 1 035 000 |
| Excel for Business | 990 000 |
| Project Management | 1 350 000 |
| UX Design | 765 000 |
| Graphic Design | 855 000 |
| Linear Algebra | 1 080 000 |
| Deep Learning | 1 395 000 |
| Java Basics | 1 035 000 |
| Financial Analysis | 1 350 000 |
| Probability | 1 125 000 |
15 rows
Result 2
| title | discounted_price |
|---|---|
| Python Basics | 1 080 000 |
| SQL for Analytics | 720 000 |
| Statistics 101 | 1 080 000 |
| Data Visualization | 810 000 |
| Machine Learning | 1 305 000 |
| Web Development | 1 035 000 |
| Excel for Business | 990 000 |
| Project Management | 1 350 000 |
| UX Design | 765 000 |
| Graphic Design | 855 000 |
| Linear Algebra | 1 080 000 |
| Deep Learning | 1 395 000 |
| Java Basics | 1 035 000 |
| Financial Analysis | 1 350 000 |
| Probability | 1 125 000 |
15 rows
Result 3
| title | discounted_price |
|---|---|
| Python Basics | 1 080 000 |
| SQL for Analytics | 720 000 |
| Statistics 101 | 1 080 000 |
| Data Visualization | 810 000 |
| Machine Learning | 1 305 000 |
| Web Development | 1 035 000 |
| Excel for Business | 990 000 |
| Project Management | 1 350 000 |
| UX Design | 765 000 |
| Graphic Design | 855 000 |
| Linear Algebra | 1 080 000 |
| Deep Learning | 1 395 000 |
| Java Basics | 1 035 000 |
| Financial Analysis | 1 350 000 |
| Probability | 1 125 000 |
15 rows
Result 4
| Course Title | Price (UZS) |
|---|---|
| Python Basics | 1 200 000 |
| SQL for Analytics | 800 000 |
| Statistics 101 | 1 200 000 |
| Data Visualization | 900 000 |
| Machine Learning | 1 450 000 |
| Web Development | 1 150 000 |
| Excel for Business | 1 100 000 |
| Project Management | 1 500 000 |
| UX Design | 850 000 |
| Graphic Design | 950 000 |
| Linear Algebra | 1 200 000 |
| Deep Learning | 1 550 000 |
| Java Basics | 1 150 000 |
| Financial Analysis | 1 500 000 |
| Probability | 1 250 000 |
15 rows
1 ms
Table aliases
Tables get aliases too. They become essential from Chapter 5 onward, when two tables both have a student_id column and you must say which one you mean.
SELECT s.first_name, s.city FROM students AS s WHERE s.city = 'Tashkent';
| first_name | city |
|---|---|
| Madina | Tashkent |
| Rustam | Tashkent |
| Aziza | Tashkent |
| Aziza | Tashkent |
| Zulfiya | Tashkent |
| Gulnora | Tashkent |
| Dilnoza | Tashkent |
| Aziz | Tashkent |
| Aziza | Tashkent |
| Malika | Tashkent |
| Shohrux | Tashkent |
| Nilufar | Tashkent |
| Otabek | Tashkent |
| Sardor | Tashkent |
| Gulnora | Tashkent |
… first 15 of 82 rows
82 rows
0 ms
:::tip 💡 Alias naming
Short but meaningful: s for students, e for enrollments, c for courses. Avoid a, b, t1, t2 — six months later nobody knows what t2 was.
:::
An alias is created at step 5 (SELECT), so ORDER BY (step 7) can use it but WHERE (step 2) cannot. This is the single most common "no such column" error beginners hit.
WHERE and comparison operators
WHERE keeps a row only when the condition evaluates to TRUE. FALSE rows are dropped — and so are UNKNOWN rows, which is the NULL problem from Chapter 1.
| Operator | Meaning | Example |
|---|---|---|
= |
Equal | WHERE city = 'Tashkent' |
<> or != |
Not equal | WHERE status <> 'active' |
> < >= <= |
Comparison | WHERE grade >= 80 |
BETWEEN a AND b |
Range, both ends included | WHERE grade BETWEEN 80 AND 90 |
IN (...) |
Matches any value in a list | WHERE city IN ('Tashkent','Bukhara') |
LIKE |
Pattern match on text | WHERE last_name LIKE '%ova' |
IS NULL / IS NOT NULL |
Tests for a missing value | WHERE grade IS NULL |
-- "Which enrollments scored below 60?" SELECT enrollment_id, student_id, course_id, grade, status FROM enrollments WHERE grade < 60;
| enrollment_id | student_id | course_id | grade | status |
|---|---|---|---|---|
| 17 | 75 | 15 | 52 | failed |
| 20 | 274 | 10 | 55 | failed |
| 26 | 33 | 8 | 56 | failed |
| 47 | 179 | 4 | 59 | failed |
| 53 | 93 | 3 | 46 | failed |
| 79 | 211 | 14 | 53 | failed |
| 82 | 212 | 5 | 58 | failed |
| 89 | 217 | 5 | 56 | failed |
| 116 | 235 | 8 | 57 | failed |
| 150 | 30 | 3 | 52 | failed |
| 153 | 56 | 3 | 51 | failed |
| 172 | 122 | 15 | 55 | failed |
| 179 | 27 | 3 | 48 | failed |
| 205 | 84 | 15 | 52 | failed |
| 222 | 68 | 15 | 57 | failed |
… first 15 of 192 rows
192 rows
1 ms
SELECT * FROM students WHERE city <> 'Tashkent'; returns 6 rows, not 7. Sevara, whose city is NULL, disappears — because NULL <> 'Tashkent' is UNKNOWN, not TRUE. If you want her included, you must say so explicitly:
WHERE city <> 'Tashkent' OR city IS NULL;
This is one of the most common causes of a silently wrong report, and it is worth showing students the row count both ways.
AND, OR, NOT — and precedence
-- both conditions must hold SELECT first_name, city, gender FROM students WHERE city = 'Tashkent' AND gender = 'M'; -- Jasur, Otabek -- either condition is enough SELECT first_name, city FROM students WHERE city = 'Bukhara' OR city = 'Fergana'; -- Dilnoza, Ulugbek -- negation SELECT first_name, status FROM enrollments WHERE NOT status = 'completed'; -- careful: NULL statuses would vanish
:::gotcha ⚠️ AND binds tighter than OR
Exactly like × binds tighter than + in arithmetic. These two queries are not the same:
-- reads as: Tashkent, OR (Samarkand AND female) -- returns 3 Tashkent students (both genders) + Zarina = 4 rows WHERE city = 'Tashkent' OR city = 'Samarkand' AND gender = 'F'; -- what was actually meant: (Tashkent OR Samarkand) AND female -- returns Aziza + Zarina = 2 rows WHERE (city = 'Tashkent' OR city = 'Samarkand') AND gender = 'F';
Rule for students: the moment a condition mixes AND with OR, put brackets in. Even where the brackets are redundant, they cost nothing and remove all doubt.
:::
ORDER BY
Rows in a table have no order, so if the output order matters you must ask for it. ASC (ascending) is the default; DESC reverses it.
-- one column SELECT first_name, enrolled_date FROM students ORDER BY enrolled_date DESC; -- several columns: sort by city, then by name inside each city SELECT city, first_name FROM students ORDER BY city ASC, first_name DESC; -- you may sort by an expression, or by an alias SELECT title, price / credits AS per_credit FROM courses ORDER BY per_credit DESC; -- you may even sort by a column you did NOT select SELECT first_name FROM students ORDER BY birth_date;
Result 1
| first_name | enrolled_date |
|---|---|
| Zulfiya | 2025-09-24 |
| Sardor | 2025-09-22 |
| Nilufar | 2025-09-21 |
| Otabek | 2025-09-19 |
| Shohrux | 2025-09-19 |
| Bekzod | 2025-09-17 |
| Sevara | 2025-09-16 |
| Bekzod | 2025-09-16 |
| Doston | 2025-09-15 |
| Doston | 2025-09-14 |
| Jasur | 2025-09-13 |
| Shahnoza | 2025-09-13 |
| Dilnoza | 2025-09-13 |
| Shohrux | 2025-09-12 |
| Bekzod | 2025-09-12 |
… first 15 of 300 rows
300 rows
Result 2
| city | first_name |
|---|---|
| None | Zulfiya |
| None | Ulug'bek |
| None | Shohrux |
| None | Madina |
| None | Jasur |
| None | Jasur |
| None | Doston |
| None | Akmal |
| Andijan | Ulug'bek |
| Andijan | Ulug'bek |
| Andijan | Ulug'bek |
| Andijan | Sevara |
| Andijan | Sevara |
| Andijan | Rustam |
| Andijan | Otabek |
… first 15 of 300 rows
300 rows
Result 3
| title | per_credit |
|---|---|
| Excel for Business | 550 000 |
| Project Management | 500 000 |
| Financial Analysis | 500 000 |
| Graphic Design | 316 666 |
| Probability | 312 500 |
| Deep Learning | 310 000 |
| Python Basics | 300 000 |
| Statistics 101 | 300 000 |
| Data Visualization | 300 000 |
| Linear Algebra | 300 000 |
| Machine Learning | 290 000 |
| Web Development | 287 500 |
| Java Basics | 287 500 |
| UX Design | 283 333 |
| SQL for Analytics | 266 666 |
15 rows
Result 4
| first_name |
|---|
| Ulug'bek |
| Gulnora |
| Aziz |
| Shohrux |
| Kamola |
| Kamola |
| Zulfiya |
| Doston |
| Doston |
| Aziz |
| Malika |
| Shahnoza |
| Otabek |
| Zulfiya |
| Jasur |
… first 15 of 300 rows
300 rows
1 ms
:::gotcha ⚠️ Where do NULLs sort?
ORDER BY city on our students puts Sevara (NULL) first in SQLite and MySQL, because they treat NULL as smaller than everything. PostgreSQL and Oracle put NULLs last by default. If the position matters, be explicit — PostgreSQL supports ORDER BY city NULLS LAST; a portable trick that works everywhere is:
ORDER BY (city IS NULL), city; -- 0 sorts before 1, so NULLs go last
:::
ORDER BY 2 means "sort by the second selected column". It works, but the day somebody inserts a column into the SELECT list, the query silently starts sorting by something else. Name the column.
LIMIT and OFFSET
LIMIT caps how many rows come back. It is the last step of all, so it applies after sorting.
-- top 3 grades SELECT enrollment_id, student_id, grade FROM enrollments WHERE grade IS NOT NULL ORDER BY grade DESC LIMIT 3;
| enrollment_id | student_id | grade |
|---|---|---|
| 135 | 247 | 100 |
| 322 | 125 | 100 |
| 500 | 114 | 100 |
3 rows
0 ms
enrollment_idstudent_idgrade
136100 6395 1192
OFFSET skips rows first — this is how pagination works:
SELECT first_name FROM students ORDER BY student_id LIMIT 3; -- page 1 SELECT first_name FROM students ORDER BY student_id LIMIT 3 OFFSET 3; -- page 2 SELECT first_name FROM students ORDER BY student_id LIMIT 3 OFFSET 6; -- page 3
Result 1
| first_name |
|---|
| Shahnoza |
| Doston |
| Zulfiya |
3 rows
Result 2
| first_name |
|---|
| Kamola |
| Gulnora |
| Bekzod |
3 rows
Result 3
| first_name |
|---|
| Doston |
| Madina |
| Sardor |
3 rows
1 ms
:::gotcha ⚠️ LIMIT without ORDER BY is meaningless
LIMIT 10 alone means "any 10 rows the database happens to hand me" — and that set can change between runs. LIMIT is only meaningful with ORDER BY. The same applies to pagination: without a stable sort, page 2 can repeat rows from page 1.
:::
On an unfamiliar table, always start with SELECT * FROM big_table LIMIT 5;. You see the column names and the shape of the data instantly, and you do not pull ten million rows into your session by accident.
DISTINCT
DISTINCT removes duplicate rows from the result. It is the fastest way to answer "what values actually occur in this column?"
SELECT DISTINCT city FROM students ORDER BY city;
city
NULL Andijan Bukhara Fergana Namangan Samarkand Tashkent
Seven rows from ten students. Note that DISTINCT keeps NULL as one of the values — it treats all NULLs as duplicates of each other, even though NULL = NULL is UNKNOWN. This is an inconsistency in SQL itself; just tell students it is a special case.
Students read SELECT DISTINCT city, gender FROM students as "distinct cities, plus gender". It is not. It returns every distinct combination of city and gender — Tashkent appears twice, once with M and once with F. There is no way to make DISTINCT apply to a single column while returning others; that job belongs to GROUP BY (Chapter 4) or a window function (Chapter 7).
To remove duplicates the database must sort or hash the entire result. On a large table that is expensive. If you find yourself adding DISTINCT to make a JOIN's duplicate rows go away, the JOIN is usually the real bug — see Chapter 5.
Practice set
Give these to students in order; each one adds exactly one new idea.
-- 1. Female students, alphabetically by last name SELECT first_name, last_name FROM students WHERE gender = 'F' ORDER BY last_name; -- 2. Courses costing more than 1,300,000, most expensive first SELECT title, price FROM courses WHERE price > 1300000 ORDER BY price DESC; -- 3. Full name + city, unknown city shown as 'unknown' SELECT first_name || ' ' || last_name AS full_name, COALESCE(city, 'unknown') AS city FROM students; -- 4. The 3 lowest grades that were actually given SELECT enrollment_id, grade FROM enrollments WHERE grade IS NOT NULL ORDER BY grade ASC LIMIT 3; -- 5. Which statuses exist in enrollments? SELECT DISTINCT status FROM enrollments; -- 6. Data-category courses with 4 or more credits SELECT title, credits FROM courses WHERE category = 'Data' AND credits >= 4 ORDER BY credits DESC, title; -- 7. Students from Tashkent or Samarkand who are female (brackets!) SELECT first_name, city FROM students WHERE (city = 'Tashkent' OR city = 'Samarkand') AND gender = 'F'; -- 8. Course price per credit, as a proper decimal SELECT title, ROUND(price * 1.0 / credits, 2) AS per_credit FROM courses ORDER BY per_credit DESC;
Result 1
| first_name | last_name |
|---|---|
| Shahnoza | Alimova |
| Dilnoza | Alimova |
| Aziza | Alimova |
| Gulnora | Alimova |
| Sevara | Alimova |
| Nilufar | Alimova |
| Kamola | Alimova |
| Nilufar | Alimova |
| Zulfiya | Alimova |
| Shahnoza | Alimova |
| Gulnora | Alimova |
| Sevara | Alimova |
| Zulfiya | Alimova |
| Nilufar | Alimova |
| Sevara | Ergasheva |
… first 15 of 154 rows
154 rows
Result 2
| title | price |
|---|---|
| Deep Learning | 1 550 000 |
| Project Management | 1 500 000 |
| Financial Analysis | 1 500 000 |
| Machine Learning | 1 450 000 |
4 rows
Result 3
| full_name | city |
|---|---|
| Shahnoza Alimova | Bukhara |
| Doston Umarov | Fergana |
| Zulfiya Yusupova | Samarkand |
| Kamola Karimova | Samarkand |
| Gulnora Saidova | Namangan |
| Bekzod Yusupov | Samarkand |
| Doston Alimov | Karshi |
| Madina Rahimova | Tashkent |
| Sardor Alimov | Bukhara |
| Dilnoza Alimova | Andijan |
| Jasur Ergashev | Karshi |
| Rustam Toshev | Tashkent |
| Bekzod Qodirov | Namangan |
| Otabek Saidov | Andijan |
| Madina Umarova | Namangan |
… first 15 of 300 rows
300 rows
Result 4
| enrollment_id | grade |
|---|---|
| 256 | 38 |
| 550 | 38 |
| 1 430 | 41 |
3 rows
Result 5
| status |
|---|
| completed |
| active |
| failed |
| dropped |
4 rows
Result 6
| title | credits |
|---|---|
| Deep Learning | 5 |
| Machine Learning | 5 |
2 rows
Result 7
| first_name | city |
|---|---|
| Zulfiya | Samarkand |
| Kamola | Samarkand |
| Madina | Tashkent |
| Shahnoza | Samarkand |
| Aziza | Tashkent |
| Aziza | Tashkent |
| Gulnora | Samarkand |
| Zulfiya | Tashkent |
| Gulnora | Tashkent |
| Dilnoza | Tashkent |
| Aziza | Tashkent |
| Gulnora | Samarkand |
| Malika | Tashkent |
| Nilufar | Tashkent |
| Malika | Samarkand |
… first 15 of 67 rows
67 rows
Result 8
| title | per_credit |
|---|---|
| Excel for Business | 550 000 |
| Project Management | 500 000 |
| Financial Analysis | 500 000 |
| Graphic Design | 316 666.67 |
| Probability | 312 500 |
| Deep Learning | 310 000 |
| Python Basics | 300 000 |
| Statistics 101 | 300 000 |
| Data Visualization | 300 000 |
| Linear Algebra | 300 000 |
| Machine Learning | 290 000 |
| Web Development | 287 500 |
| Java Basics | 287 500 |
| UX Design | 283 333.33 |
| SQL for Analytics | 266 666.67 |
15 rows
2 ms
Interview questions
Q: Can you use a column alias in WHERE?
No. WHERE runs at step 2 and SELECT — where the alias is created — runs at step 5. Repeat the expression in WHERE, or wrap the query in a CTE/subquery. ORDER BY (step 7) can use the alias.
Q: What is the difference between DISTINCT and GROUP BY?
For simply listing unique values they give the same answer, and most engines execute them identically. GROUP BY is more powerful: it can compute aggregates per group (COUNT, AVG) and be filtered with HAVING. Use DISTINCT when you only want unique rows; use GROUP BY when you want something calculated per group.
Q: Does DISTINCT apply to one column or all of them?
All the selected columns together. SELECT DISTINCT a, b returns unique pairs of (a, b).
Q: WHERE city != 'Tashkent' — will rows with a NULL city be returned?
No, and this is the classic trick question. NULL != 'Tashkent' is UNKNOWN, and WHERE keeps only TRUE. Write WHERE city != 'Tashkent' OR city IS NULL.
Q: What does LIMIT 10 return without ORDER BY?
Ten arbitrary rows — whichever ones the engine produced first. It is not guaranteed to be stable between runs, so it is only safe for eyeballing data, never for reporting.
Q: Why does SELECT 7/2 return 3?
Integer ÷ integer = integer; the fractional part is truncated, not rounded. Use 7 * 1.0 / 2 or CAST(7 AS REAL) / 2.
Q: What is the difference between ORDER BY name and ORDER BY 2?
The second sorts by the second column in the SELECT list, by position. It works but is fragile: editing the SELECT list silently changes the sort.
Summary
SELECTbuilds a new table: columns come out in the order you name them, and can be expressions, constants or concatenations.- Integer division truncates —
price / creditsloses the decimals. Multiply by1.0orCAST. - Aliases (
AS) rename output columns; they are usable inORDER BYbut never inWHERE. WHEREkeeps only rows that are TRUE. NULL rows are UNKNOWN, so<>silently drops them — addOR col IS NULLwhen you need them.ANDbinds tighter thanOR. Mixing them without brackets is a bug waiting to happen.ORDER BYis the only way to guarantee row order; NULL placement differs by database.LIMITis meaningless withoutORDER BY;LIMIT ... OFFSET ...is pagination.DISTINCTapplies to the whole selected row and keeps NULL as one value.
Next: Chapter 3 goes deeper into filtering — IN, BETWEEN, LIKE patterns, and the NULL traps that quietly break real reports.
Questions about this chapter?
The course, homework review and the other five books are in Uzbek. Questions in English are welcome on Telegram.