SQL theory · page 2 of 10

How a query is evaluated

The logical order of a SELECT, three-valued logic and types.

A SELECT is written in one order and evaluated in another. Knowing the logical order explains most error messages, and why some queries that look right are not.

StepClauseWhat happens
1FROM, JOINthe rows of the tables, combined
2WHERErows that fail the condition are dropped
3GROUP BYrows are gathered into groups
4HAVINGgroups that fail the condition are dropped
5SELECTthe output columns are computed, and named
6DISTINCTduplicate output rows are removed
7ORDER BYthe result is sorted
8LIMIT, OFFSETa page of the result is kept

So a column alias made in SELECT cannot be used in WHERE (step 2 runs before step 5), but can be used in ORDER BY (step 7 runs after).

SELECT policy_number, ROUND(annual_premium / 12, 2) AS monthly
FROM policies
WHERE annual_premium / 12 > 150
ORDER BY monthly DESC
LIMIT 5;
WHERE monthly > 150 would fail: monthly does not exist yet when WHERE runs. Try it in the playground

Three-valued logic

A comparison with null is neither true nor false but unknown, and WHERE keeps only the rows for which the condition is true. So broker_id = NULL matches nothing, not even the rows where it is null: test with IS NULL and IS NOT NULL.

aba and ba or b
trueunknownunknowntrue
falseunknownfalseunknown
unknownunknownunknownunknown
SELECT COUNT(*) AS direct,
       COUNT(*) FILTER (WHERE broker_id = NULL) AS compared_with_equals
FROM policies
WHERE broker_id IS NULL;
10,033 policies were sold directly; = NULL finds none of them. Try it in the playground
NOT IN (list) returns no rows at all if the list contains a null. Prefer NOT EXISTS for 'rows without a match'.

Types and casts

Every value has a type, and PostgreSQL converts between types only when it is safe. Convert explicitly with ::: '2025-12-31'::DATE, COUNT(*)::NUMERIC. Dividing two integers gives an integer, so 7 / 2 is 3; 7 / 2.0 or 7::NUMERIC / 2 is 3.5.

Functions

Built-in functions compute a value from each row: ROUND(x, 2), COALESCE(a, b) (the first value that is not null), UPPER(TEXT), AGE(DATE, DATE), EXTRACT(YEAR FROM DATE). Date arithmetic works directly: subtracting two dates gives the days between them.

Practise it on Day 2

Check your understanding

Answer every question, then check.

1. Why does WHERE monthly > 150 fail when monthly is an alias defined in the select list?

2. What does broker_id = NULL return for a row whose broker_id is null?

3. What is 7 / 2 in PostgreSQL?

An unhandled error has occurred. Reload 🗙

Reconnecting…

Your connection to Data Analytics SQL Lab dropped, probably a Wi-Fi blip. Hold on while it reconnects. Your work is kept.

The connection could not be restored. The page will reload in a few seconds.