SELECT وWHERE وORDER BY
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
Until now you have used SELECT only to peek at a table. This lesson makes it a precise tool: choose the columns, filter the rows with WHERE, sort them with ORDER BY, and keep only the first few with LIMIT. It is also where you meet NULL, the value that trips up almost every beginner.
In the library picture, this is the main skill of the module: asking the librarian a precise question. "Show me the books" gets you the whole catalog. "Show me titles and years of books published after 1990, newest first, but only five" gets you an answer you can use.
Nothing here changes data, so you can experiment freely. All queries in this lesson, and the rest of the module, run on a small seeded library: 5 authors, 11 books, 5 members, and 8 loans.
This lesson builds on Lessons 1 to 3 (SQL families, the library tables, and the WHERE habit).
Learning Objectives
By the end of this lesson you will be able to:
- Select all columns (
*) or a chosen list of columns, with aliases. - Filter rows with comparisons,
AND,OR,IN, andLIKE, using parentheses correctly. - Sort by one or more columns, ascending or descending.
- Limit the number of rows returned and explain why
LIMITneedsORDER BY. - Explain
NULLand test for it withIS NULL. - Predict the result of a query on a small table.
Written Explanation
The shape of a query
SELECT columns
FROM table
WHERE condition
ORDER BY columns
LIMIT n;You write the clauses in this order. Only SELECT and FROM are required. Conceptually the database first finds the rows in the table (FROM), keeps those that pass WHERE, then picks the columns (SELECT), sorts (ORDER BY), and cuts (LIMIT). That order explains several rules below.
Choosing columns
SELECT *returns every column. It is handy for exploring; in real code name the columns you need, so the result does not change when someone adds a column.SELECT title, published_year FROM books;returns just those two, in that order.ASgives a column a different name in the result:SELECT title AS book FROM books;.
Filtering with WHERE
WHERE keeps the rows for which the condition is true.
| Operator | Meaning | Example |
|---|---|---|
= <> | equal, not equal | author_id = 2, published_year <> 1974 |
< <= > >= | comparisons | published_year > 1990 |
AND, OR | combine conditions | author_id = 1 AND published_year < 1970 |
IN (...) | matches any value in a list | author_id IN (1, 4) |
LIKE | text pattern: % is any run of characters | title LIKE 'The%' |
(The manual lists != as an alias for <>. This course uses <>.) Text and dates are written in single quotes: joined_on >= '2024-01-01'. In PostgreSQL, LIKE is case-sensitive; ILIKE ignores case, but that is a PostgreSQL extension.
AND binds tighter than OR. a OR b AND c means a OR (b AND c). When you mix them, add parentheses to say what you mean, as Example 2 shows.
NULL: "unknown"
NULL marks a missing or unknown value, such as the publication year of an anonymous poem or the return date of a book still out. It is not zero and not an empty string. Because it means "unknown", comparing anything to it gives "unknown", not true. The PostgreSQL manual says that 7 = NULL yields null, and tells you not to write expression = NULL. Test for it with:
WHERE returned_on IS NULL -- rows with no value
WHERE returned_on IS NOT NULL -- rows with a valueA consequence: a condition like published_year <> 1974 is also "unknown" for a NULL year, so those rows are not returned. If NULL rows matter, say so in the condition.
Sorting with ORDER BY
ORDER BY published_year -- ascending (smallest first) is the default
ORDER BY published_year DESC -- descending (largest first)
ORDER BY author_id, title DESC -- by author_id ascending, ties broken by title descendingWith several columns, later ones only break ties among rows equal in the earlier ones, and each column has its own direction. In PostgreSQL, NULL sorts as if larger than any value, so it comes last in ascending order and first in descending order (NULLS FIRST or NULLS LAST can override this).
Without ORDER BY, the order of the rows is not guaranteed. The manual says that it must not be relied on. A table that "looks sorted" today may not look sorted tomorrow. If order matters, say so.
Limiting rows with LIMIT
LIMIT 5 keeps at most five rows; OFFSET 3 skips the first three before counting. LIMIT is not part of the core SQL standard (PostgreSQL, MySQL, and SQLite support it, and the standard has its own FETCH FIRST syntax; as in Lesson 1, check your product). The PostgreSQL manual says that you should use ORDER BY with LIMIT to get a predictable subset; otherwise the subset is unpredictable. "The top five" only means something once you have said "by what".
Examples
All results below are real output from PostgreSQL 18.3 on the seeded library.
Example 1: choosing, filtering, sorting, limiting (simple)
Books published after 1990, newest first, at most five:
SELECT title, published_year
FROM books
WHERE published_year > 1990
ORDER BY published_year DESC
LIMIT 5; title | published_year
----------------------+----------------
Lavinia | 2008
Fledgling | 2005
Parable of the Sower | 1993
(3 rows)LIMIT 5 is a maximum: only three books qualify. Notice also that "Collected Verses", whose year is NULL, is not in the result: NULL > 1990 is not true.
Example 2: AND, OR, and parentheses (practical)
Two queries that differ only by parentheses:
SELECT title, published_year FROM books
WHERE author_id = 2 OR author_id = 3 AND published_year < 1960
ORDER BY title; title | published_year
-------------------+----------------
Arrow of God | 1964
Palace Walk | 1956
Things Fall Apart | 1958
(3 rows)Read as author_id = 2 OR (author_id = 3 AND published_year < 1960): all of author 2's books, plus author 3's early ones, so Arrow of God (1964) sneaks in. If you wanted "authors 2 or 3, and published before 1960":
SELECT title, published_year FROM books
WHERE (author_id = 2 OR author_id = 3) AND published_year < 1960
ORDER BY title; title | published_year
-------------------+----------------
Palace Walk | 1956
Things Fall Apart | 1958
(2 rows)The same condition could be written author_id IN (2, 3) AND published_year < 1960, which avoids the trap.
Example 3: the NULL trap on loans (realistic)
Which loans are still out? A book still out has no return date. First, the wrong way:
SELECT id FROM loans WHERE returned_on = NULL;This returns (0 rows). No error, no warning, and no rows, so a report built on it would say "nothing is overdue". The right way:
SELECT id, loaned_on, due_on
FROM loans
WHERE returned_on IS NULL
ORDER BY due_on; id | loaned_on | due_on
----+------------+------------
2 | 2025-09-01 | 2025-09-15
3 | 2025-09-20 | 2025-10-04
5 | 2025-10-10 | 2025-10-24
7 | 2025-10-20 | 2025-11-03
(4 rows)Sorting by due_on puts the longest-outstanding loan first, which is the order a librarian would chase them in.
One more check on NULL in sorting. With ORDER BY published_year, "Collected Verses" appeared last; with DESC it appeared first, as the PostgreSQL manual describes.
Backend Insight
Almost every read your backend performs is a SELECT with a WHERE (find this user, list this customer's orders) and often an ORDER BY and LIMIT (newest first, one page at a time). The "page" in an API such as "orders, 20 per page" is usually built with ORDER BY plus LIMIT and OFFSET, so a stable, unique sort order is a correctness requirement, not decoration.
NULL behaviour is a classic source of production bugs: a filter that quietly drops rows with missing values, or a report that undercounts. Decide for each nullable column what "missing" should mean in your queries, and test with a row that has NULL.
Common Mistakes
Mistake 1: = NULL
- Incorrect:
WHERE returned_on = NULL - Correct:
WHERE returned_on IS NULL
The first returns no rows and no error.
Mistake 2: Mixing AND and OR without parentheses
The result is valid SQL that answers a different question than you meant (Example 2). Parenthesize.
Mistake 3: Assuming a natural order
Rows came out "in id order" in your test, so you assumed they always will. Without ORDER BY, no order is promised.
Mistake 4: LIMIT without ORDER BY
"Top five" with no sort returns an arbitrary five.
Mistake 5: Forgetting quotes, or using the wrong ones
- Incorrect:
WHERE title = The DispossessedorWHERE title = "The Dispossessed" - Correct:
WHERE title = 'The Dispossessed'
A misspelled column fails loudly: SELECT titel FROM books; gave ERROR: column "titel" does not exist, with a hint suggesting books.title. Read the hint.
Exercises
Try each exercise yourself before opening the solutions. Use the seeded library if you have one; otherwise reason from the tables in the lesson.
Level A: Foundation
A1. Write a query that lists the name and joined_on of every member.
أظهر الحل A1
A1. SELECT name, joined_on FROM members;
A2. Write a query for books published in or after 1970 by author 1.
أظهر الحل A2
A2. SELECT title FROM books WHERE author_id = 1 AND published_year >= 1970;
A3. Which query finds members who joined in 2024 or later, newest first?
أظهر الحل A3
A3. SELECT name, joined_on FROM members WHERE joined_on >= '2024-01-01' ORDER BY joined_on DESC;
Level B: Application
B1. List the titles of books whose title contains the word of, sorted alphabetically. (On the seeded data, four titles match.)
أظهر الحل B1
B1. SELECT title FROM books WHERE title LIKE '%of%' ORDER BY title; The % on both sides means "anywhere in the title". (It also matches words that contain of inside them, such as a title with "Profile".)
B2. List loans that were returned late, as far as you can tell from one table: returned_on after due_on. Which loan ids do you expect, using the seeded data: loan 6 has due_on 2025-10-26 and returned_on 2025-10-30?
أظهر الحل B2
B2. SELECT id FROM loans WHERE returned_on > due_on; A NULL returned_on is excluded because the comparison is unknown. Loan 6 is returned 4 days late; with the seeded data it is the only match.
B3. Show the three oldest books by published_year, ignoring books with an unknown year.
أظهر الحل B3
B3. SELECT title, published_year FROM books WHERE published_year IS NOT NULL ORDER BY published_year LIMIT 3; (Without the filter, NULL sorts last in ascending order in PostgreSQL, so it would not appear in the first three anyway, but the explicit filter states your intention.)
Level C: Challenge
C1. Explain why SELECT title FROM books WHERE published_year <> 1974; returns 9 rows from a table of 11 books, where one book was published in 1974.
أظهر الحل C1
C1. The table has 11 books. One was published in 1974, so that is excluded by <>. A second book has a NULL year, and NULL <> 1974 is unknown, not true, so it is excluded too. That leaves 9.
C2. Write a query that returns the books with unknown year and the books published before 1960, in a single result, sorted by title. Say what the OR and IS NULL do.
أظهر الحل C2
C2. SELECT title FROM books WHERE published_year IS NULL OR published_year < 1960 ORDER BY title; IS NULL selects unknown years, < 1960 selects the early books (which never include NULL), and OR merges the two sets.
Mini Challenge
The librarian asks three questions. Write one query for each, run them if you have the seeded library, and note the number of rows.
- "Which members joined before 2025, in alphabetical order?"
- "Which are the two most recently published books, with their years?" (Think about the
NULLyear.) - "Which loans have not been returned, oldest due date first?"
Check your work: every query has a WHERE or LIMIT where needed, query 2 states ORDER BY ... DESC and handles NULL (it sorts first when descending), and query 3 uses IS NULL and not = NULL.
Summary & Next Lesson
Key takeaways
SELECT columns FROM table WHERE condition ORDER BY columns LIMIT n;is the core query shape.WHEREkeeps rows whose condition is true; use parentheses when mixingANDandOR.NULLmeans unknown: useIS NULLandIS NOT NULL. A comparison withNULLis never true.ORDER BYdefaults to ascending; addDESCfor descending. Without it, order is not guaranteed.LIMITis meaningful only withORDER BY.
Next lesson
You can now pull out individual rows. The next lesson, "Aggregates, GROUP BY, HAVING", summarizes many rows at once: counting, averaging, and grouping, and filtering on those summaries.
Self-check
- How do you test for a missing return date?
- What does
a OR b AND cmean? - Which comes first in ascending order in PostgreSQL:
NULLor 5? - Why add
ORDER BYto aLIMITquery? - What does
LIKE 'The%'match?
أظهر الإجابات
WHERE returned_on IS NULL.a OR (b AND c), becauseANDbinds tighter.- 5;
NULLsorts last in ascending order by default. - Without it, the rows that make up "the first N" are arbitrary.
- Any text that begins with
The.
References
- PostgreSQL documentation, Sorting Rows (ORDER BY). Supports ascending as the default, per-column directions,
NULLordering, and that unsorted order must not be relied on. - PostgreSQL documentation, LIMIT and OFFSET. Supports the syntax, the need for
ORDER BYfor predictable results, and OFFSET skipping first. ThatLIMITis outside the core SQL standard is general knowledge that I could not confirm on a fetched page, so it is hedged in the text. - PostgreSQL documentation, Comparison Functions and Operators. Supports
<>with!=as an alias, comparisons withNULLyielding null, andIS NULL. - All query results were produced on PostgreSQL 18.3. The
LIKE/ILIKEandAND-before-ORremarks were checked by running queries, not on a fetched page.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.