Learn by reading through in order

WHERE Deep Dive ② — BETWEEN and LIKE for Range and Pattern Filtering

The second WHERE deep-dive article. Cover ranges with BETWEEN, NOT BETWEEN, and prefix / suffix / contains matching with LIKE — combined with ORDER BY / LIMIT, all running on a CSV-loaded staff dataset.

Data we'll use — the staff table

The second WHERE deep-dive article covers range filtering (BETWEEN) and pattern matching (LIKE). Combine the compound conditions you built last time with AND / OR / NOT with BETWEEN / LIKE from this article and you'll be able to express most real-world filter requirements in a single WHERE.

Before the exercises, confirm the staff table's column definitions and a sample of the data.

① Run PRAGMA table_info(staff); to check column names, types, and primary keys.

② Run SELECT * FROM staff LIMIT 5; to preview the first 5 rows.

SQL Editor

Run a query to see results

BETWEEN — filter by value range

When you want to test whether a value falls inside a range — "salary between 5,000,000 and 6,000,000," "birthday between April 2020 and March 2025" — BETWEEN is the operator.

Writing column BETWEEN value1 AND value2 returns rows where the column is at least value1 and at most value2. Note that both endpoints are included. It means the same as column >= value1 AND column <= value2; people pick BETWEEN often just for readability.

Adding NOT BETWEEN returns the rows outside the range (less than value1 or greater than value2).

-- Staff with salary between 5,000,000 and 6,000,000 (endpoints included)
SELECT name, salary FROM staff
WHERE salary BETWEEN 5000000 AND 6000000;

-- Same meaning, written with >= and <= AND'd together
SELECT name, salary FROM staff
WHERE salary >= 5000000 AND salary <= 6000000;

-- Outside the range (under 5,000,000 OR over 6,000,000)
SELECT name, salary FROM staff
WHERE salary NOT BETWEEN 5000000 AND 6000000;
BETWEEN range (endpoints included)
BETWEEN range (endpoints included)salary3,800,0004,500,000(endpoint)5,500,0006,000,000(endpoint)BETWEEN4500000–6000000FALSETRUE(endpoint included)TRUETRUE(endpoint included)NOT BETWEEN4500000–6000000TRUEFALSEFALSEFALSE
BETWEEN 4500000 AND 6000000 is a closed interval that includes 4,500,000 and 6,000,000. NOT BETWEEN returns the outside instead. The dotted line tying the endpoints together represents the width of the range.

BETWEEN includes both endpoints

BETWEEN 5000000 AND 6000000 includes exactly 5,000,000 and exactly 6,000,000. If you want to exclude the endpoints, write > 5000000 AND < 6000000.

With dates, you can write BETWEEN '2020-04-01' AND '2025-03-31' — the literals get compared as strings. To include "all of March 2025," defining both endpoints with the actual end-of-month date is the safe approach. BETWEEN '2020-04-01' AND '2025-04-01' would also include `2025-04-01` itself, which is usually not what you want.

Imagine a leadership meeting needs the count of "mid-tier earners (salary 5,000,000 – 6,000,000)." (Once you run it correctly, the explanation will appear.)

① From staff, pull the name and salary columns.

② Filter to rows where `salary` is at least 5,000,000 and at most 6,000,000 (endpoints included).

③ Confirm the result is 3 rows (Bob 5,200,000 / Grace 5,500,000 / Jack 5,900,000).

SQL Editor

Run a query to see results

Imagine a request to "see the staff outside the mid-tier (4,500,000 – 6,000,000), sorted by highest salary first."

① From staff, pull the name and salary columns.

② Filter to rows where `salary` is outside the range 4,500,000 – 6,000,000 (i.e., under 4,500,000 or over 6,000,000).

③ Sort by `salary` descending.

④ Confirm the result is 6 rows, starting with Frank 7,200,000 and ending with Emi 3,800,000.

SQL Editor

Run a query to see results

LIKE — filter by pattern matching

When you want to filter by a string pattern — "names ending with Tanaka," "emails containing @example.com" — LIKE is the operator. Write column LIKE 'pattern'. Inside the pattern you can use the following two wildcards.

WildcardMeaning
%Matches any string of zero or more characters ("anything of any length")
_Matches exactly one character ("any single character at this position")

'A%' is starts with A, '%a' is ends with a, '%a%' is contains a, 'A___%' is starts with A and has at least three more characters — combine them as needed. Adding NOT LIKE returns rows that don't match the pattern.

-- 1) Prefix match: name starts with A
SELECT name FROM staff WHERE name LIKE 'A%';

-- 2) Suffix match: surname Tanaka
SELECT name FROM staff WHERE name LIKE '%Tanaka';

-- 3) Contains: name contains 'a' anywhere
SELECT name FROM staff WHERE name LIKE '%a%';

-- 4) Single character: 2nd char is 'a', the rest is anything
SELECT name FROM staff WHERE name LIKE '_a%';

-- 5) Negation: doesn't end with Tanaka
SELECT name FROM staff WHERE name NOT LIKE '%Tanaka';
LIKE wildcards (% and _)
PatternExample matchesMeaning'A%'prefixAlice / Adam /Applestarts with A'%Tanaka'suffixAlice Tanaka /Frank Tanakaends with Tanaka'%a%'containsAlice / David /Frank / Carolcontains a'_a%'_ + %Carol / Dave /Jack2nd char is a
% means "any length of anything"; _ means "exactly one character." Combining their positions expresses prefix / suffix / contains in three patterns. The dotted lines connect each pattern to its example matches and a plain-language description.

LIKE and `=` follow different case-sensitivity rules

In this course's console (SQLite), within ASCII, LIKE is case-insensitive. name LIKE '%a%' and name LIKE '%A%' return the same rows. To make it case-sensitive, run PRAGMA case_sensitive_like = 1; or normalize both sides with LOWER(...).

Meanwhile, string comparison with `=` (covered in the SELECT article) is case-sensitive — watch for the difference in behavior between = and LIKE.

Searching for literal `%` or `_` — the `ESCAPE` clause

If the string you're searching for actually contains a literal % or _ (say, a product code like 'A_001'), then by default the _ will be interpreted as a wildcard. The ESCAPE clause lets you specify an escape character, after which % / _ are treated as literal characters.

Example: WHERE code LIKE 'A\_001' ESCAPE '\'; (declares \ as the escape character, so \_ is treated as a literal underscore). The escape character doesn't have to be \ESCAPE '#' makes # the escape, ESCAPE '!' makes it !, etc. You'll reach for this when searching code columns that contain _ or %.

Run the three patterns — prefix, suffix, contains-via-_ — in a single console and compare the results.

① For each query, pull name, city, and salary from staff.

② Pull rows where `name` starts with `A` (prefix match 'A%').

③ Then pull rows where `name` ends with `Tanaka` (suffix match '%Tanaka').

④ Then pull rows where the 2nd character of `name` is `a` ('_a%', where _ is any single character).

⑤ Confirm the row counts are 1 / 4 / 3.

SQL Editor

Run a query to see results

Combining WHERE, ORDER BY, and LIMIT

Real-world queries usually combine filtering (WHERE) → sorting (ORDER BY) → row limit (LIMIT) in that exact order. The clause order is fixed, and swapping them around is a syntax error.

Clause order (left to right)
SELECTcolsFROMtableWHEREcond.ORDER BYcolLIMITNOFFSETM
Write SELECT cols → FROM table → WHERE condition → ORDER BY col → LIMIT N OFFSET M, in that order.

The DB's execution order is roughly FROM → WHERE → SELECT → ORDER BY → LIMIT — first narrow rows, pick the columns you need, sort them, then slice off the count you want.

Execution flow of WHERE → ORDER BY → LIMIT
1) WHEREname LIKE '%a%'→ filter to matching rows (9)2) ORDER BYsalary DESC→ sort by highest salary3) LIMITLIMIT 3→ take the first 3 rowsResultFrank 7,200,000David 6,800,000Henry 6,100,000
WHERE narrows rows, ORDER BY sorts, LIMIT slices off the first N. Both the writing order and the execution order line up like this — memorize once, never confused.

Imagine a dashboard showing a salary ranking for "staff with a in their name."

① From staff, pull the name and salary columns.

② Filter to rows where `name` contains `a` (contains match).

③ Sort by `salary` descending, and take only the first 3 rows.

④ Confirm the result is 3 rows: Frank Tanaka 7,200,000 / David Sato 6,800,000 / Henry Sato 6,100,000.

SQL Editor

Run a query to see results
QUIZ

Knowledge Check

Answer each question one by one.

Q1Which correctly describes SELECT * FROM staff WHERE salary BETWEEN 5000000 AND 6000000;?

Q2Which name matches the pattern name LIKE '%Tanaka'?

Q3Which of the following queries has the clauses in the right order?