Surfalytics
SQL course overview

Basics of selection I · Topic 6 Free

IS NULL, BETWEEN, IN

Data model — the tables you will query
N 1 N 1 N 1 N 1 cohorts id PK INT name TEXT start_date DATE format TEXT students id PK INT name TEXT city TEXT country TEXT cohort_id FK INT background TEXT joined_at DATE courses id PK INT title TEXT category TEXT difficulty TEXT hours INT enrollments id PK INT student_id FK INT course_id FK INT enrolled_at DATE completed_at DATE score INT job_offers id PK INT student_id FK INT company TEXT role TEXT city TEXT salary_usd INT remote BOOLEAN offer_date DATE

How the tables join

Relationship Cardinality Join condition
students.cohort_id → cohorts.id many students → one cohort JOIN cohorts ON students.cohort_id = cohorts.id
enrollments.student_id → students.id many enrollments → one student JOIN students ON enrollments.student_id = students.id
enrollments.course_id → courses.id many enrollments → one course JOIN courses ON enrollments.course_id = courses.id
job_offers.student_id → students.id many job offers → one student JOIN students ON job_offers.student_id = students.id

In short: each student belongs to one cohort. Students enroll in courses through enrollments, and their job offers land in job_offers.

Three small operators that make WHERE clauses shorter and clearer.

NULL and IS NULL

NULL means “no value”. It is not zero, not an empty string — it is the absence of data. In our dataset, enrollments.completed_at is NULL while the course is still in progress.

The trap every beginner hits:

WHERE completed_at = NULL    -- WRONG: always returns nothing
WHERE completed_at IS NULL   -- correct

Any comparison with NULL (=, <>, >, …) returns NULL — neither true nor false — so the row is filtered out. The only correct tests are:

WHERE score IS NULL
WHERE score IS NOT NULL

Useful companion: COALESCE(score, 0) returns the first non-NULL argument — a common way to substitute a default.

BETWEEN

A shorthand for a range check, inclusive on both ends:

WHERE salary_usd BETWEEN 90000 AND 110000
-- same as: salary_usd >= 90000 AND salary_usd <= 110000

Works with dates too:

WHERE joined_at BETWEEN '2024-01-01' AND '2024-12-31'

Careful with timestamps: BETWEEN ... AND '2024-12-31' cuts off anything after midnight on the 31st. For date ranges, >= start AND < next_day is safer.

IN

Checks membership in a list. Cleaner than a chain of ORs:

WHERE city IN ('Vancouver', 'Toronto', 'Calgary')
-- same as: city = 'Vancouver' OR city = 'Toronto' OR city = 'Calgary'

NOT IN excludes the list — but beware: if the list contains a NULL, NOT IN returns no rows at all. This becomes important with subqueries later.

Practice

A real Postgres database runs in your browser. Solve the tasks, or just experiment.