SELECT, and what a column really is
A select list is a list of expressions, not a list of columns. Once that lands, half of SQL stops being a special case.
8 min · runs against the shop dataset
A SELECT names expressions. A bare column is the simplest expression there is, but it is not a different kind of thing from total * 2 or lower(name) — and the moment you stop reading a select list as “a list of columns” and start reading it as “a list of expressions, evaluated once per row”, a great deal of SQL stops needing to be memorised.
Four expressions. Only one of them is a column.
| idinteger | totalnumeric(10,2) | doublednumeric | lowertext |
|---|---|---|---|
| 1 | 720.73 | 1441.46 | oslo |
| 2 | 1529.28 | 3058.56 | osaka |
| 3 | 1792.84 | 3585.68 | osaka |
| 4 | 1459.37 | 2918.74 | osaka |
| 5 | 1366.05 | 2732.10 | berlin |
5 rows · 114 examined · 2 pages read
Notice the last column is called lower. Postgres names an unaliased expression after the function that produced it, or ?column? when there is nothing to name it after. That is not a detail worth memorising, but it is worth recognising: a result set full of ?column? is telling you the query has expressions nobody has named yet.
The order clauses run in
Clauses are written in one order and evaluated in another. This is the single most useful piece of trivia in the language, because almost every “why can’t I use my alias there” question is answered by it.
FROM— decide which rows exist at all, joins included.WHERE— throw rows away. Aliases from the select list do not exist yet.GROUP BY— collapse what survived into groups.HAVING— throw groups away.SELECT— evaluate the expressions, and only now do the aliases exist.ORDER BY— which is why it can use an alias.LIMIT/OFFSET— take a slice of what is left.
ORDER BY can see the alias, because it runs after SELECT.
| doublednumeric |
|---|
| 3788.38 |
| 3773.18 |
| 3702.48 |
3 rows · 90 examined · 1 page read
Did it land?
- The expensive orders
Return the id and total of every order over 1000, most expensive first. Two columns, and an order the query actually promises.
- Label every order
For each order, return its id and a label: 'big' when the total is over 1000, 'unknown' when there is no total at all, and 'small' otherwise. Order by id. Call the second column size.