WHERE, ORDER BY, LIMIT, OFFSET, string functions, and date arithmetic — the tools you will use on every single query.
F-3 — Filtering, Sorting, and Finding What You Need
Who this module is for: You completed F-2, can create tables and perform basic INSERT/SELECT/UPDATE/DELETE. Now you need to find specific rows, sort results, and use the built-in functions that every SQL developer relies on daily. This module covers the tools you will use in almost every query you ever write.
The WHERE Clause — Your Primary Filter
WHERE filters rows before they are returned. Only rows where the condition is TRUE are included.
BETWEEN — Range Check
BETWEEN is inclusive — it includes the boundary values.
IN — Match Any Value in a List
LIKE and ILIKE — Pattern Matching
LIKE matches a pattern where % means "any sequence of characters" and _ means "any single character".
LIKE is case-sensitive. ILIKE is the PostgreSQL extension for case-insensitive matching:
IS NULL and IS NOT NULL
Combining Conditions — AND, OR, NOT
Parentheses Are Critical
Without parentheses, AND has higher precedence than OR — this catches many beginners:
Rule: when mixing AND and OR, always use parentheses to make intent explicit.
ORDER BY — Sorting Results
Without ORDER BY, PostgreSQL returns rows in no guaranteed order. Do not assume the order will be consistent between queries.
NULLS FIRST and NULLS LAST
When sorting a column that contains NULL, you control where nulls appear:
By default in PostgreSQL: NULL sorts last with ASC, first with DESC.
LIMIT and OFFSET — Pagination
⚠️ The performance trap with large OFFSET:
OFFSET 1000 means PostgreSQL reads 1000 rows and discards them before returning your 5. At large offsets, this gets slow. For pagination in real applications, use cursor-based pagination instead (covered in later modules).
Always use ORDER BY with LIMIT — without it, the rows you get are unpredictable and vary between runs.
DISTINCT — Remove Duplicate Rows
DISTINCT is applied across all selected columns — it removes rows where all selected columns are identical.
String Functions
These are the functions you will reach for constantly:
Real-world string examples
Date and Time Functions
Date arithmetic
Type Casting — Converting Between Types
Sometimes you need to explicitly convert a value from one type to another:
Practical Exercise: Product Search Queries
Using the products table from F-2, answer these questions with SQL:
Putting It All Together
A query can chain all of these together. The order of clauses is:
Summary
| Concept | What it does |
|---|---|
WHERE col = val | Exact match filter |
WHERE col BETWEEN a AND b | Range filter (inclusive) |
WHERE col IN (a, b, c) | Match any value in list |
WHERE col LIKE '%pattern%' | Case-sensitive pattern match |
WHERE col ILIKE '%pattern%' | Case-insensitive pattern match |
WHERE col IS NULL | Check for missing value |
AND / OR / NOT | Combine conditions |
ORDER BY col ASC/DESC | Sort results |
LIMIT n | Return at most n rows |
OFFSET n | Skip first n rows |
DISTINCT | Remove duplicate result rows |
UPPER() / LOWER() | Change string case |
LENGTH() | String length |
TRIM() | Remove whitespace |
CONCAT() / || | Join strings |
NOW() | Current timestamp |
EXTRACT() | Get part of a date/time |
DATE_TRUNC() | Round down to precision |
INTERVAL | Duration arithmetic |
::type | Type casting |
Module F-4 covers aggregation — GROUP BY, HAVING, COUNT, SUM, AVG, and the mental model that trips up almost every beginner learning SQL.
Next: F-4 — Aggregation — Summarising Your Data →
Question 1: A developer writes the following SQL query to find products that are either out of stock (in_stock = false) OR have no description, AND are priced under $50:
Which of the following statements accurately describes the issues with this query and how to correctly achieve the intended filtering?
- A) The condition
description = NULLis incorrect; it should bedescription IS NULL. Additionally, due to operator precedence, theANDclause binds tighter thanOR, requiring parentheses around theORconditions to group them correctly. - B) The condition
description = NULLis correct for checking nulls. The primary issue is thatORhas higher precedence thanAND, leading to(in_stock = false OR description = NULL)being evaluated first. - C) The condition
description = NULLis incorrect; it should bedescription IS NULL. However, parentheses are not needed becauseANDnaturally groupsdescription IS NULLwithprice < 50beforeORis applied. - D) The query's logic is fundamentally flawed because
NULLvalues cannot be combined withANDorORoperators, requiring separateWHEREclauses for each condition.
Reveal Answer
Correct Answer: A
Option A is correct. The lesson explicitly states that description = NULL does not work and returns 0 rows; the correct syntax is description IS NULL. Furthermore, SQL's operator precedence dictates that AND binds tighter than OR. Without parentheses, the query in_stock = false OR description = NULL AND price < 50 is interpreted as in_stock = false OR (description = NULL AND price < 50). To achieve the intended logic of (out of stock OR no description) AND under $50, parentheses are critical: (in_stock = false OR description IS NULL) AND price < 50;. Option B is incorrect because description = NULL is wrong and AND has higher precedence than OR. Option C is incorrect because parentheses are needed to override AND's higher precedence. Option D is incorrect because NULL values can be combined with AND/OR using IS NULL/IS NOT NULL.
Question 2: A high-traffic e-commerce platform uses PostgreSQL for its product catalog. The product listing page implements pagination using LIMIT and OFFSET to display 20 products per page. The query structure is SELECT id, name, price FROM products ORDER BY created_at DESC LIMIT 20 OFFSET X;. As the platform scales to millions of products, users report that navigating to deeper pages (e.g., page 5000, where OFFSET would be 99980) becomes progressively slower, eventually leading to timeouts.
Which of the following statements best explains this performance degradation and suggests the most appropriate architectural solution for production-grade pagination?
- A) The
ORDER BY created_at DESCclause is the primary bottleneck because sorting millions of rows is inherently slow. RemovingORDER BYwould resolve the performance issue, asLIMITandOFFSETare optimized for speed. - B) The
OFFSETclause forces the database to scan and discard a large number of rows before retrieving the desired subset. The recommended solution is cursor-based pagination, where theWHEREclause filters based on the last seencreated_atvalue from the previous page. - C) The
LIMITclause itself is inefficient when combined with large datasets, as it requires a full table scan to determine the total number of rows before limiting. A better approach is to pre-calculate page numbers and store them in a separate index. - D) The issue is likely due to insufficient memory allocated to PostgreSQL for caching intermediate results of the
OFFSEToperation. Increasingwork_memandshared_bufferswould mitigate the problem, allowingOFFSETto perform efficiently even at deep pages.
Reveal Answer
Correct Answer: B
Option B is correct. The lesson explicitly highlights the 'performance trap with large OFFSET', explaining that OFFSET N requires PostgreSQL to read and discard N rows before returning the requested LIMIT amount. This becomes extremely inefficient for deep pagination. The recommended solution for real-world applications is cursor-based pagination (also known as keyset pagination), which involves filtering results using a WHERE clause based on the values of the last row from the previous page (e.g., WHERE created_at < 'last_seen_timestamp' ORDER BY created_at DESC LIMIT 20). Option A is incorrect because ORDER BY is essential for consistent pagination results, and while sorting can be expensive, the primary issue described is the OFFSET's discarding behavior. Option C is incorrect; LIMIT does not require a full table scan to determine total rows, and pre-calculating page numbers is not a standard or scalable pagination strategy. Option D is incorrect; while memory tuning can help general query performance, it does not fundamentally address the algorithmic inefficiency of OFFSET discarding rows.
Question 3: A data analytics team needs to generate a report showing all unique product categories that have had at least one product added in the last 90 days. Additionally, they want to exclude any categories whose name (case-insensitive) contains the word "legacy". The final list of categories should be sorted alphabetically.
Given the products table with category (TEXT) and created_at (TIMESTAMPTZ) columns, which of the following SQL queries correctly fulfills these requirements?
- A) ```sql SELECT DISTINCT category FROM products WHERE created_at > NOW() - INTERVAL '90 days' AND category NOT LIKE '%legacy%' ORDER BY category ASC;
- C) ```sql SELECT DISTINCT category FROM products WHERE created_at > NOW() - INTERVAL '90 days' AND category NOT ILIKE '%legacy%' ORDER BY category ASC;
Reveal Answer
Correct Answer: C
Option C is correct. It correctly uses DISTINCT category to get unique categories. The date filter created_at > NOW() - INTERVAL '90 days' accurately identifies products created in the last 90 days. Most importantly, category NOT ILIKE '%legacy%' correctly performs a case-insensitive pattern match to exclude categories containing 'legacy', as ILIKE is the PostgreSQL-specific extension for case-insensitive matching, as taught in the lesson. Finally, ORDER BY category ASC sorts the results alphabetically. Option A is incorrect because LIKE is case-sensitive, violating the 'case-insensitive' requirement. Option B achieves case-insensitivity using LOWER(category) NOT LIKE '%legacy%', which is functionally correct but ILIKE (as used in C) is the more idiomatic and often more performant way in PostgreSQL for this specific task. Option D is incorrect because while GROUP BY category would also yield unique categories, DISTINCT is the more direct and appropriate clause for simply selecting unique values of columns, especially when no aggregation is involved. GROUP BY is typically used with aggregate functions, which are covered in the next module.
Discussion
0Join the discussion